FIXME better doc
FIXME split this module into submodules
FIXME add missing error checks
nitc :: ModelBuilder
A model builder knows how to load nit source files and build the associated modelnitc :: modelbuilder_base $ AIntrudeVisibility
An explicit intrude visibility modifiernitc :: modelbuilder_base $ APrivateVisibility
An explicit private visibility modifiernitc :: modelbuilder_base $ AProtectedVisibility
An explicit protected visibility modifiernitc :: modelbuilder_base $ APublicVisibility
An implicit or explicit public visibility modifiernitc :: modelbuilder_base $ AQclassid
A potentially qualified class identifierfoo::bar::Baz
nitc :: modelbuilder_base $ AIntrudeVisibility
An explicit intrude visibility modifiernitc :: modelbuilder_base $ APrivateVisibility
An explicit private visibility modifiernitc :: modelbuilder_base $ AProtectedVisibility
An explicit protected visibility modifiernitc :: modelbuilder_base $ APublicVisibility
An implicit or explicit public visibility modifiernitc :: modelbuilder_base $ AQclassid
A potentially qualified class identifierfoo::bar::Baz
Serializable::inspect
to show more useful information
more_collections :: more_collections
Highly specific, but useful, collections-related classes.serialization :: serialization_core
Abstract services to serialize Nit objects to different formatscore :: union_find
union–find algorithm using an efficient disjoint-set data structurenitc :: toolcontext
Common command-line tool infrastructure than handle options and error messagesnitc :: actors_generation_phase
Generate a support module for each module that contain a class annotated withis actor
nitc :: actors_injection_phase
Injects model for the classes annotated with "is actor" sonitc :: api_metrics
nitc :: astbuilder
Instantiation and transformation of semantic nodes in the AST of expressions and statementscflags
and ldflags
to specify
nitc :: commands_ini
nitc :: detect_variance_constraints
Collect metrics about detected variances constraints on formal types.extra_java_files
to compile extra java files
nitc :: i18n_phase
Basic support of internationalization through the generation of id-to-string tablesnitc :: light_only
Compiler support for the light FFI only, detects unsupported usage of callbacksnitc
.
nitc :: modelbuilder
nitc :: nitmetrics
A program that collects various metrics on nit programs and librariesnitc :: nitrestful
Tool generating boilerplate code linking RESTful actions to Nit methodsthreaded
annotation
nitc :: separate_erasure_compiler
Separate compilation of a Nit program with generic type erasurenitc :: serialization_code_gen_phase
Phase generating methods (code) to serialize Nit objectsnitc :: serialization_model_phase
Phase generating methods (model-only) to serialize Nit objectsclone
method of the astbuilder tool
nitc :: uml_module
Services for generation of a UML package diagram based on aModel
# Load nit source files and build the associated model
#
# FIXME better doc
#
# FIXME split this module into submodules
# FIXME add missing error checks
module modelbuilder_base
import model
import toolcontext
import parser
private import more_collections
###
redef class ToolContext
# The modelbuilder 1-to-1 associated with the toolcontext
fun modelbuilder: ModelBuilder do return modelbuilder_real.as(not null)
private var modelbuilder_real: nullable ModelBuilder = null
end
# A model builder knows how to load nit source files and build the associated model
class ModelBuilder
# The model where new modules, classes and properties are added
var model: Model
# The toolcontext used to control the interaction with the user (getting options and displaying messages)
var toolcontext: ToolContext
# Instantiate a modelbuilder for a model and a toolcontext
# Important, the options of the toolcontext must be correctly set (parse_option already called)
init
do
assert toolcontext.modelbuilder_real == null
toolcontext.modelbuilder_real = self
end
# Return a class named `name` visible by the module `mmodule`.
# Visibility in modules is correctly handled.
# If no such a class exists, then null is returned.
# If more than one class exists, then an error on `anode` is displayed and null is returned.
# FIXME: add a way to handle class name conflict
fun try_get_mclass_by_name(anode: nullable ANode, mmodule: MModule, name: String): nullable MClass
do
var classes = model.get_mclasses_by_name(name)
if classes == null then
return null
end
var res: nullable MClass = null
for mclass in classes do
if not mmodule.in_importation <= mclass.intro_mmodule then continue
if not mmodule.is_visible(mclass.intro_mmodule, mclass.visibility) then continue
if res == null then
res = mclass
else
error(anode, "Error: ambiguous class name `{name}`; conflict between `{mclass.full_name}` and `{res.full_name}`.")
return null
end
end
return res
end
# Return a class identified by `qid` visible by the module `mmodule`.
# Visibility in modules and qualified names are correctly handled.
#
# If more than one class exists, then null is silently returned.
# It is up to the caller to post-analysis the result and display a correct error message.
# The method `class_not_found` can be used to display such a message.
fun try_get_mclass_by_qid(qid: AQclassid, mmodule: MModule): nullable MClass
do
var name = qid.n_id.text
var classes = model.get_mclasses_by_name(name)
if classes == null then
return null
end
var res: nullable MClass = null
for mclass in classes do
if not mmodule.in_importation <= mclass.intro_mmodule then continue
if not mmodule.is_visible(mclass.intro_mmodule, mclass.visibility) then continue
if not qid.accept(mclass) then continue
if res == null then
res = mclass
else
return null
end
end
return res
end
# Like `try_get_mclass_by_name` but display an error message when the class is not found
fun get_mclass_by_name(node: nullable ANode, mmodule: MModule, name: String): nullable MClass
do
var mclass = try_get_mclass_by_name(node, mmodule, name)
if mclass == null then
error(node, "Type Error: missing primitive class `{name}'.")
end
return mclass
end
# Return a property named `name` on the type `mtype` visible in the module `mmodule`.
# Visibility in modules is correctly handled.
# Protected properties are returned (it is up to the caller to check and reject protected properties).
# If no such a property exists, then null is returned.
# If more than one property exists, then an error on `anode` is displayed and null is returned.
# FIXME: add a way to handle property name conflict
fun try_get_mproperty_by_name2(anode: nullable ANode, mmodule: MModule, mtype: MType, name: String): nullable MProperty
do
var props = self.model.get_mproperties_by_name(name)
if props == null then
return null
end
var cache = self.try_get_mproperty_by_name2_cache[mmodule, mtype, name]
if cache != null then return cache
var res: nullable MProperty = null
var ress: nullable Array[MProperty] = null
for mprop in props do
if not mtype.has_mproperty(mmodule, mprop) then continue
if not mmodule.is_visible(mprop.intro_mclassdef.mmodule, mprop.visibility) then continue
# new-factories are invisible outside of the class
if mprop isa MMethod and mprop.is_new and (not mtype isa MClassType or mprop.intro_mclassdef.mclass != mtype.mclass) then
continue
end
if res == null then
res = mprop
continue
end
# Two global properties?
# First, special case for init, keep the most specific ones
if res isa MMethod and mprop isa MMethod and res.is_init and mprop.is_init then
var restype = res.intro_mclassdef.bound_mtype
var mproptype = mprop.intro_mclassdef.bound_mtype
if mproptype.is_subtype(mmodule, null, restype) then
# found a most specific constructor, so keep it
res = mprop
continue
end
end
# Ok, just keep all prop in the ress table
if ress == null then
ress = new Array[MProperty]
ress.add(res)
end
ress.add(mprop)
end
# There is conflict?
if ress != null and res isa MMethod and res.is_init then
# special case forinit again
var restype = res.intro_mclassdef.bound_mtype
var ress2 = new Array[MProperty]
for mprop in ress do
var mproptype = mprop.intro_mclassdef.bound_mtype
if not restype.is_subtype(mmodule, null, mproptype) then
ress2.add(mprop)
else if not mprop isa MMethod or not mprop.is_init then
ress2.add(mprop)
end
end
if ress2.is_empty then
ress = null
else
ress = ress2
ress.add(res)
end
end
if ress != null then
assert ress.length > 1
var s = new Array[String]
for mprop in ress do s.add mprop.full_name
self.error(anode, "Error: ambiguous property name `{name}` for `{mtype}`; conflict between {s.join(" and ")}.")
end
self.try_get_mproperty_by_name2_cache[mmodule, mtype, name] = res
return res
end
private var try_get_mproperty_by_name2_cache = new HashMap3[MModule, MType, String, nullable MProperty]
# Alias for try_get_mproperty_by_name2(anode, mclassdef.mmodule, mclassdef.mtype, name)
fun try_get_mproperty_by_name(anode: nullable ANode, mclassdef: MClassDef, name: String): nullable MProperty
do
return try_get_mproperty_by_name2(anode, mclassdef.mmodule, mclassdef.bound_mtype, name)
end
# Helper function to display an error on a node.
# Alias for `self.toolcontext.error(n.hot_location, text)`
#
# This automatically sets `n.is_broken` to true.
fun error(n: nullable ANode, text: String)
do
var l = null
if n != null then
l = n.hot_location
n.is_broken = true
end
self.toolcontext.error(l, text)
end
# Helper function to display a warning on a node.
# Alias for: `self.toolcontext.warning(n.hot_location, text)`
fun warning(n: nullable ANode, tag, text: String)
do
var l = null
if n != null then l = n.hot_location
self.toolcontext.warning(l, tag, text)
end
# Helper function to display an advice on a node.
# Alias for: `self.toolcontext.advice(n.hot_location, text)`
fun advice(n: nullable ANode, tag, text: String)
do
var l = null
if n != null then l = n.hot_location
self.toolcontext.advice(l, tag, text)
end
# Force to get the primitive method named `name` on the type `recv` or do a fatal error on `n`
fun force_get_primitive_method(n: nullable ANode, name: String, recv: MClass, mmodule: MModule): MMethod
do
var res = mmodule.try_get_primitive_method(name, recv)
if res == null then
var l = null
if n != null then l = n.hot_location
self.toolcontext.fatal_error(l, "Fatal Error: `{recv}` must have a property named `{name}`.")
abort
end
return res
end
# Return the static type associated to the node `ntype`.
#
# `mclassdef` is the context where the call is made (used to understand
# formal types).
# In case of problem, an error is displayed on `ntype` and null is returned.
#
# Same as `resolve_mtype_unchecked3`, but get the context (module, class and
# anchor) from `mclassdef`.
#
# SEE: `resolve_mtype`
# SEE: `resolve_mtype3_unchecked`
#
# FIXME: Find a better name for this method.
fun resolve_mtype_unchecked(mclassdef: MClassDef, ntype: AType, with_virtual: Bool): nullable MType
do
return resolve_mtype3_unchecked(
mclassdef.mmodule,
mclassdef.mclass,
mclassdef.bound_mtype,
ntype,
with_virtual
)
end
# Return the static type associated to the node `ntype`.
#
# `mmodule`, `mclass` and `anchor` compose the context where the call is
# made (used to understand formal types).
# In case of problem, an error is displayed on `ntype` and null is returned.
#
# Note: The “3” is for 3 contextual parameters.
#
# SEE: `resolve_mtype`
# SEE: `resolve_mtype_unchecked`
#
# FIXME: Find a better name for this method.
fun resolve_mtype3_unchecked(mmodule: MModule, mclass: nullable MClass, anchor: nullable MClassType, ntype: AType, with_virtual: Bool): nullable MType
do
var qid = ntype.n_qid
var name = qid.n_id.text
var res: MType
# Check virtual type
if anchor != null and with_virtual then
var prop = try_get_mproperty_by_name2(ntype, mmodule, anchor, name).as(nullable MVirtualTypeProp)
if prop != null then
if not ntype.n_types.is_empty then
error(ntype, "Type Error: formal type `{name}` cannot have formal parameters.")
end
res = prop.mvirtualtype
if ntype.n_kwnullable != null then res = res.as_nullable
ntype.mtype = res
return res
end
end
# Check parameter type
if mclass != null then
for p in mclass.mparameters do
if p.name != name then continue
if not ntype.n_types.is_empty then
error(ntype, "Type Error: formal type `{name}` cannot have formal parameters.")
end
res = p
if ntype.n_kwnullable != null then res = res.as_nullable
ntype.mtype = res
return res
end
end
# Check class
var found_class = try_get_mclass_by_qid(qid, mmodule)
if found_class != null then
var arity = ntype.n_types.length
if arity != found_class.arity then
if arity == 0 then
error(ntype, "Type Error: `{found_class.signature_to_s}` is a generic class.")
else if found_class.arity == 0 then
error(ntype, "Type Error: `{name}` is not a generic class.")
else
error(ntype, "Type Error: expected {found_class.arity} formal argument(s) for `{found_class.signature_to_s}`; got {arity}.")
end
return null
end
if arity == 0 then
res = found_class.mclass_type
if ntype.n_kwnullable != null then res = res.as_nullable
ntype.mtype = res
return res
else
var mtypes = new Array[MType]
for nt in ntype.n_types do
var mt = resolve_mtype3_unchecked(mmodule, mclass, anchor, nt, with_virtual)
if mt == null then return null # Forward error
mtypes.add(mt)
end
res = found_class.get_mtype(mtypes)
if ntype.n_kwnullable != null then res = res.as_nullable
ntype.mtype = res
return res
end
end
# If everything fail, then give up with class by proposing things.
#
# TODO Give hints on formal types (param and virtual)
class_not_found(qid, mmodule)
ntype.is_broken = true
return null
end
# Print an error and suggest hints when the class identified by `qid` in `mmodule` is not found.
#
# This just print error messages.
fun class_not_found(qid: AQclassid, mmodule: MModule)
do
var name = qid.n_id.text
var qname = qid.full_name
if bad_class_names[mmodule].has(qname) then
error(qid, "Error: class `{qname}` not found in module `{mmodule}`.")
return
end
bad_class_names[mmodule].add(qname)
var all_classes = model.get_mclasses_by_name(name)
var hints = new Array[String]
# Look for conflicting classes.
if all_classes != null then for c in all_classes do
if not mmodule.is_visible(c.intro_mmodule, c.visibility) then continue
if not qid.accept(c) then continue
hints.add "`{c.full_name}`"
end
if hints.length > 1 then
error(qid, "Error: ambiguous class name `{qname}` in module `{mmodule}`. Conflicts are between {hints.join(",", " and ")}.")
return
end
hints.clear
# Look for imported but invisible classes.
if all_classes != null then for c in all_classes do
if not mmodule.in_importation <= c.intro_mmodule then continue
if mmodule.is_visible(c.intro_mmodule, c.visibility) then continue
if not qid.accept(c) then continue
error(qid, "Error: class `{c.full_name}` not visible in module `{mmodule}`.")
return
end
# Look for not imported but known classes from importable modules
if all_classes != null then for c in all_classes do
if mmodule.in_importation <= c.intro_mmodule then continue
if c.intro_mmodule.in_importation <= mmodule then continue
if c.visibility <= private_visibility then continue
if not qid.accept(c) then continue
hints.add "`{c.intro_mmodule.full_name}`"
end
if hints.not_empty then
error(qid, "Error: class `{qname}` not found in module `{mmodule}`. Maybe import {hints.join(",", " or ")}?")
return
end
# Look for classes with an approximative name.
var bests = new BestDistance[MClass](qname.length - name.length / 2) # limit up to 50% name change
for c in model.mclasses do
if not mmodule.in_importation <= c.intro_mmodule then continue
if not mmodule.is_visible(c.intro_mmodule, c.visibility) then continue
var d = qname.levenshtein_distance(c.name)
bests.update(d, c)
d = qname.levenshtein_distance(c.full_name)
bests.update(d, c)
end
if bests.best_items.not_empty then
for c in bests.best_items do hints.add "`{c.full_name}`"
error(qid, "Error: class `{qname}` not found in module `{mmodule}`. Did you mean {hints.join(",", " or ")}?")
return
end
error(qid, "Error: class `{qname}` not found in module `{mmodule}`.")
end
# List of already reported bad class names.
# Used to not perform and repeat hints again and again.
private var bad_class_names = new MultiHashMap[MModule, String]
# Return the static type associated to the node `ntype`.
#
# `mclassdef` is the context where the call is made (used to understand
# formal types).
# In case of problem, an error is displayed on `ntype` and null is returned.
#
# Same as `resolve_mtype3`, but get the context (module, class and ) from
# `mclassdef`.
#
# SEE: `resolve_mtype3`
# SEE: `resolve_mtype_unchecked`
#
# FIXME: Find a better name for this method.
fun resolve_mtype(mclassdef: MClassDef, ntype: AType): nullable MType
do
return resolve_mtype3(
mclassdef.mmodule,
mclassdef.mclass,
mclassdef.bound_mtype,
ntype
)
end
# Return the static type associated to the node `ntype`.
#
# `mmodule`, `mclass` and `anchor` compose the context where the call is
# made (used to understand formal types).
# In case of problem, an error is displayed on `ntype` and null is returned.
#
# Note: The “3” is for 3 contextual parameters.
#
# SEE: `resolve_mtype`
# SEE: `resolve_mtype_unchecked`
#
# FIXME: Find a better name for this method.
fun resolve_mtype3(mmodule: MModule, mclass: nullable MClass, anchor: nullable MClassType, ntype: AType): nullable MType
do
var mtype = ntype.mtype
if mtype == null then mtype = resolve_mtype3_unchecked(mmodule, mclass, anchor, ntype, true)
if mtype == null then return null # Forward error
if ntype.checked_mtype then return mtype
if mtype isa MGenericType then
var found_class = mtype.mclass
for i in [0..found_class.arity[ do
var intro = found_class.try_intro
if intro == null then return null # skip error
var bound = intro.bound_mtype.arguments[i]
var nt = ntype.n_types[i]
var mt = resolve_mtype3(mmodule, mclass, anchor, nt)
if mt == null then return null # forward error
if not check_subtype(nt, mmodule, anchor, mt, bound) then
error(nt, "Type Error: expected `{bound}`, got `{mt}`.")
return null
end
end
end
ntype.checked_mtype = true
return mtype
end
# Check that `sub` is a subtype of `sup`.
# Do not display an error message.
#
# This method is used a an entry point for the modelize phase to test static subtypes.
# Some refinements could redefine it to collect statictics.
fun check_subtype(node: ANode, mmodule: MModule, anchor: nullable MClassType, sub, sup: MType): Bool
do
return sub.is_subtype(mmodule, anchor, sup)
end
# Check that `sub` and `sup` are equvalent types.
# Do not display an error message.
#
# This method is used a an entry point for the modelize phase to test static equivalent types.
# Some refinements could redefine it to collect statictics.
fun check_sametype(node: ANode, mmodule: MModule, anchor: nullable MClassType, sub, sup: MType): Bool
do
return sub.is_subtype(mmodule, anchor, sup) and sup.is_subtype(mmodule, anchor, sub)
end
end
redef class ANode
# The indication that the node did not pass some semantic verifications.
#
# This simple flag is set by a given analysis to say that the node is broken and unusable in
# an execution.
# When a node status is set to broken, it is usually associated with a error message.
#
# If it is safe to do so, clients of the AST SHOULD just skip broken nodes in their processing.
# Clients that do not care about the executability (e.g. metrics) MAY still process the node or
# perform specific checks to determinate the validity of the node.
#
# Note that the broken status is not propagated to parent or children nodes.
# e.g. a broken expression used as argument does not make the whole call broken.
var is_broken = false is writable
redef fun dump_info(v) do
var res = super
if is_broken then
res += v.red("*broken*")
end
return res
end
end
redef class AType
# The mtype associated to the node
var mtype: nullable MType = null
# Is the mtype a valid one?
var checked_mtype: Bool = false
redef fun dump_info(v) do
var res = super
var mtype = self.mtype
if mtype != null then
res += v.yellow(":{mtype}")
end
return res
end
end
redef class AVisibility
# The visibility level associated with the AST node class
fun mvisibility: MVisibility is abstract
end
redef class AIntrudeVisibility
redef fun mvisibility do return intrude_visibility
end
redef class APublicVisibility
redef fun mvisibility do return public_visibility
end
redef class AProtectedVisibility
redef fun mvisibility do return protected_visibility
end
redef class APrivateVisibility
redef fun mvisibility do return private_visibility
end
redef class ADoc
private var mdoc_cache: nullable MDoc
# Convert `self` to a `MDoc`
fun to_mdoc: MDoc
do
var res = mdoc_cache
if res != null then return res
res = new MDoc(location)
for c in n_comment do
var text = c.text
if text.length < 2 then
res.content.add ""
continue
end
assert text.chars[0] == '#'
if text.chars[1] == ' ' then
text = text.substring_from(2) # eat starting `#` and space
else
text = text.substring_from(1) # eat atarting `#` only
end
if text.chars.last == '\n' then text = text.substring(0, text.length-1) # drop \n
res.content.add(text)
end
mdoc_cache = res
return res
end
end
redef class AQclassid
# The name of the package part, if any
fun mpackname: nullable String do
var nqualified = n_qualified
if nqualified == null then return null
var nids = nqualified.n_id
if nids.length <= 0 then return null
return nids[0].text
end
# The name of the module part, if any
fun mmodname: nullable String do
var nqualified = n_qualified
if nqualified == null then return null
var nids = nqualified.n_id
if nids.length <= 1 then return null
return nids[1].text
end
# Does `mclass` match the full qualified name?
fun accept(mclass: MClass): Bool
do
if mclass.name != n_id.text then return false
var mpackname = self.mpackname
if mpackname != null then
var mpackage = mclass.intro_mmodule.mpackage
if mpackage == null then return false
if mpackage.name != mpackname then return false
var mmodname = self.mmodname
if mmodname != null and mclass.intro_mmodule.name != mmodname then return false
end
return true
end
# The pretty name represented by self.
fun full_name: String
do
var res = n_id.text
var nqualified = n_qualified
if nqualified == null then return res
var ncid = nqualified.n_classid
if ncid != null then res = ncid.text + "::" + res
var nids = nqualified.n_id
if nids.not_empty then for n in nids.reverse_iterator do
res = n.text + "::" + res
end
return res
end
end
src/modelbuilder_base.nit:17,1--666,3