From: Jean Privat Date: Thu, 4 Jun 2020 12:22:43 +0000 (-0400) Subject: Merge: Astbuilder improvement X-Git-Url: http://nitlanguage.org?hp=-c Merge: Astbuilder improvement This pr : - move `do_all` method into ASTBuilder. - generalize `create_callsite` to take into consideration AAttrPropdef Pull-Request: #2825 --- 4daa2b04d5936edb192521a0216db036ee6f13af diff --combined src/astbuilder.nit index 8644213,46b8265..0b911c6 --- a/src/astbuilder.nit +++ b/src/astbuilder.nit @@@ -203,11 -203,11 +203,11 @@@ class ASTBuilde # Build a callsite to call the `mproperty` in the current method `caller_method`. # `is_self_call` indicate if the method caller is a property of `self` - fun create_callsite(modelbuilder: ModelBuilder, caller_method : AMethPropdef, mproperty: MMethod, is_self_call: Bool): CallSite + fun create_callsite(modelbuilder: ModelBuilder, caller_property: APropdef, mproperty: MMethod, is_self_call: Bool): CallSite do # FIXME It's not the better solution to call `TypeVisitor` here to build a model entity, but some make need to have a callsite - var type_visitor = new TypeVisitor(modelbuilder, caller_method.mpropdef.as(not null)) - var callsite = type_visitor.build_callsite_by_property(caller_method, mproperty.intro_mclassdef.bound_mtype, mproperty, is_self_call) + var type_visitor = new TypeVisitor(modelbuilder, caller_property.mpropdef.as(not null)) + var callsite = type_visitor.build_callsite_by_property(caller_property, mproperty.intro_mclassdef.bound_mtype, mproperty, is_self_call) assert callsite != null return callsite end @@@ -418,6 -418,17 +418,17 @@@ redef class AMethPropde self.mpropdef = mmethoddef if mpropdef != null then self.location = mmethoddef.location end + + # Execute all method verification scope flow and typing. + # It also execute an ast validation to define all parents and all locations + fun do_all(toolcontext: ToolContext) + do + self.validate + # FIXME: The `do_` usage it is maybe to much (verification...). Solution: Cut the `do_` methods into simpler parts + self.do_scope(toolcontext) + self.do_flow(toolcontext) + self.do_typing(toolcontext.modelbuilder) + end end redef class AAssertExpr @@@ -774,17 -785,6 +785,17 @@@ redef class APara end end +redef class AFormaldef + + private init make(mparameter: MParameterType, t: AType) + do + _n_id = new TClassid + _n_id.text = mparameter.name + _n_type = t + _mtype = mparameter + end +end + redef class ABlockExpr private init make(t: nullable MType) do @@@ -945,214 -945,3 +956,214 @@@ redef class AAnnotatio _n_args = n_args end end + +redef class MEntity + # Build a ANode from `self` + # + # Allows the creation of an AST node from a model entity. + fun create_ast_representation(astbuilder: nullable ASTBuilder): ANode is abstract +end + +redef class MPropDef + redef fun create_ast_representation(astbuilder: nullable ASTBuilder): APropdef is abstract +end + +redef class MClassDef + redef fun create_ast_representation(astbuilder: nullable ASTBuilder): AStdClassdef do + if astbuilder == null then astbuilder = new ASTBuilder(mmodule) + var n_propdefs = new Array[APropdef] + for mpropdef in self.mpropdefs do + n_propdefs.add(mpropdef.create_ast_representation(astbuilder)) + end + var n_formaldefs = new Array[AFormaldef] + for mparameter in self.mclass.mparameters do n_formaldefs.add(mparameter.create_ast_representation(astbuilder)) + + return astbuilder.make_class(self, visibility.create_ast_representation(astbuilder), n_formaldefs, null, n_propdefs, null) + end +end + +redef class MAttributeDef + redef fun create_ast_representation(astbuilder: nullable ASTBuilder): AAttrPropdef do + if astbuilder == null then astbuilder = new ASTBuilder(mclassdef.mmodule) + var ntype = null + if self.static_mtype != null then ntype = static_mtype.create_ast_representation(astbuilder) + return astbuilder.make_attribute("_" + self.name, ntype, self.visibility.create_ast_representation(astbuilder), null, null, self, null, null) + end +end + +redef class MMethodDef + redef fun create_ast_representation(astbuilder: nullable ASTBuilder): AMethPropdef do + if astbuilder == null then astbuilder = new ASTBuilder(mclassdef.mmodule) + var tk_redef = null + if self.mproperty.intro != self then tk_redef = new TKwredef + var n_signature = if self.msignature == null then new ASignature else self.msignature.create_ast_representation(astbuilder) + return astbuilder.make_method(visibility.create_ast_representation(astbuilder), tk_redef, self, n_signature) + end +end + +redef class MVisibility + fun create_ast_representation(astbuilder: nullable ASTBuilder): AVisibility do + if self.to_s == "public" then + return new APublicVisibility + else if self.to_s == "private" then + return new APrivateVisibility + else if self.to_s == "protected" then + return new AProtectedVisibility + else + return new AIntrudeVisibility + end + end +end + +redef class MSignature + redef fun create_ast_representation(astbuilder: nullable ASTBuilder): ASignature do + var nparams = new Array[AParam] + for mparam in mparameters do nparams.add(mparam.create_ast_representation(astbuilder)) + var return_type = null + if self.return_mtype != null then return_type = self.return_mtype.create_ast_representation(astbuilder) + return new ASignature.init_asignature(null, nparams, null, return_type) + end +end + +redef class MParameter + redef fun create_ast_representation(astbuilder: nullable ASTBuilder): AParam do + var variable = new Variable(self.name) + variable.declared_type = self.mtype + return new AParam.make(variable, self.mtype.create_ast_representation(astbuilder)) + end +end + +redef class MParameterType + redef fun create_ast_representation(astbuilder: nullable ASTBuilder): AFormaldef do + var n_type = super + return new AFormaldef.make(self, n_type) + end +end + +redef class MType + redef fun create_ast_representation(astbuilder: nullable ASTBuilder): AType do + return new AType.make(self) + end +end + +redef class ModelBuilder + # Try to get MMethod property if exist in the given mclassdef. return new `MMethod` if not exist. + private fun get_mmethod(name: String, mclassdef: MClassDef, visibility: nullable MVisibility): MMethod do + visibility = visibility or else public_visibility + var mproperty = try_get_mproperty_by_name(null, mclassdef, name).as(nullable MMethod) + if mproperty == null then mproperty = new MMethod(mclassdef, name, mclassdef.location, visibility) + return mproperty + end + + # Creation of a new method (AST and model representation) with the given name. + # See `create_method_from_property` for more information. + fun create_method_from_name(name: String, mclassdef: MClassDef, is_abstract: Bool, msignature: nullable MSignature, visibility: nullable MVisibility): AMethPropdef do + var mproperty = get_mmethod(name, mclassdef, visibility) + return create_method_from_property(mproperty, mclassdef, is_abstract, msignature) + end + + # Creation of a new method (AST and model representation) with the given MMethod. + # Take care, if `is_abstract == false` the AMethPropdef returned has an empty body (potential error if the given signature has an return type). + fun create_method_from_property(mproperty: MMethod, mclassdef: MClassDef, is_abstract: Bool, msignature: nullable MSignature): AMethPropdef do + var m_def = new MMethodDef(mclassdef, mproperty, mclassdef.location) + + if msignature == null then msignature = new MSignature(new Array[MParameter]) + + m_def.msignature = msignature + m_def.is_abstract = true + var n_def = m_def.create_ast_representation + # Association new npropdef to mpropdef + unsafe_add_mpropdef2npropdef(m_def,n_def) + + if not is_abstract then + n_def.mpropdef.is_abstract = false + n_def.n_block = new ABlockExpr.make + end + + return n_def + end + + # Creation of a new attribute (AST and model representation) with the given name. + # See `create_attribute_from_property` for more information. + fun create_attribute_from_name(name: String, mclassdef: MClassDef, mtype: MType, visibility: nullable MVisibility): AAttrPropdef do + if visibility == null then visibility = public_visibility + var mattribute = try_get_mproperty_by_name(null, mclassdef, name) + if mattribute == null then mattribute = new MAttribute(mclassdef, name, mclassdef.location, visibility) + return create_attribute_from_property(mattribute.as(MAttribute), mclassdef, mtype) + end + + # Creation of a new attribute (AST and model representation) with the given MAttribute. + # See `create_attribute_from_propdef` for more information. + fun create_attribute_from_property(mattribute: MAttribute, mclassdef: MClassDef, mtype: MType): AAttrPropdef do + var attribut_def = new MAttributeDef(mclassdef, mattribute, mclassdef.location) + attribut_def.static_mtype = mtype + return create_attribute_from_propdef(attribut_def) + end + + # Creation of a new attribute (AST representation) with the given MAttributeDef. + fun create_attribute_from_propdef(mattribut_def: MAttributeDef): AAttrPropdef + is + expect(mclassdef2node(mattribut_def.mclassdef) != null) + do + var n_attribute = mattribut_def.create_ast_representation + + var nclass = mclassdef2node(mattribut_def.mclassdef) + + n_attribute.location = mattribut_def.location + n_attribute.validate + + nclass.n_propdefs.unsafe_add_all([n_attribute]) + nclass.validate + + n_attribute.build_read_property(self, mattribut_def.mclassdef) + n_attribute.build_read_signature + + mpropdef2npropdef[mattribut_def] = n_attribute + return n_attribute + end + + # Creation of a new class (AST and model representation) with the given name. + # `visibility` : Define the visibility of the method. If it's `null` the default is `public_visibility` + # See `create_class_from_mclass` for more information. + fun create_class_from_name(name: String, super_type: Array[MClassType], mmodule: MModule, visibility: nullable MVisibility): AStdClassdef do + if visibility == null then visibility = public_visibility + var mclass = try_get_mclass_by_name(null, mmodule, name) + if mclass == null then mclass = new MClass(mmodule, name, mmodule.location, new Array[String], concrete_kind, visibility) + return create_class_from_mclass(mclass, super_type, mmodule) + end + + # Creation of a new class (AST and model representation) with the given MClass. + # This method creates a new concrete class definition `MClassDef`, and adds it to the class hierarchy. + # See `create_class_from_mclassdef` for more information. + fun create_class_from_mclass(mclass: MClass, super_type: Array[MClassType], mmodule: MModule): AStdClassdef do + var mclassdef = new MClassDef(mmodule, mclass.mclass_type, mmodule.location) + mclassdef.set_supertypes(super_type) + mclassdef.add_in_hierarchy + + return create_class_from_mclassdef(mclassdef, mmodule) + end + + # Creation of a new class (AST representation) with the given MClassDef. + # Note all the properties of our MClassDef will also generate an AST representation. + # Make an error if the attribute already has a representation in the modelbuilder. + # This method also create the default constructor. + fun create_class_from_mclassdef(mclassdef: MClassDef, mmodule: MModule): AStdClassdef do + var n_classdef = mclassdef.create_ast_representation + n_classdef.location = mclassdef.location + n_classdef.validate + + for n_propdef in n_classdef.n_propdefs do + var mpropdef = n_propdef.mpropdef + assert mpropdef != null + + var p_npropdef = mpropdef2node(mpropdef) + if p_npropdef != null then error(null, "The property `{mpropdef.name}` already has a representation in the AST.") + unsafe_add_mpropdef2npropdef(mpropdef, n_propdef) + end + + process_default_constructors(n_classdef) + unsafe_add_mclassdef2nclassdef(mclassdef, n_classdef) + + return n_classdef + end +end diff --combined src/contracts.nit index 3a3c3a7,1825ec1..0055c45 --- a/src/contracts.nit +++ b/src/contracts.nit @@@ -18,10 -18,10 +18,10 @@@ # FIXME Split the module in three parts: extension of the modele, building phase and the "re-driving" module contracts import parse_annotations import phase import semantize +intrude import astbuilder intrude import modelize_property intrude import scope intrude import typing @@@ -65,11 -65,10 +65,11 @@@ redef class AModul end end -# This visitor checks the `AMethPropdef` and the `AClassDef` to check if they have a contract annotation or it's a redefinition with a inheritance contract +# Visitor to build all contracts. private class ContractsVisitor super Visitor + # Instance of the toolcontext var toolcontext: ToolContext # The main module @@@ -91,8 -90,6 +91,8 @@@ var current_location: Location is noinit # Is the contrat is an introduction or not? + # This attribute has the same value as the `is_intro` of the propdef attached to the contract. + # Note : For MClassDef `is_intro_contract == false`. This is due to the fact that a method for checking invariants is systematically added to the root object class. var is_intro_contract: Bool is noinit # Actual visited class @@@ -101,11 -98,6 +101,11 @@@ # is `no_contract` annotation was found var find_no_contract = false + # The reference to the `in_contract` attribute. + # This attribute is used to disable contract verification when you are already in a contract verification. + # Keep the `in_contract` attribute to avoid searching at each contrat + var in_contract_attribute: nullable MAttribute = null + redef fun visit(node) do node.create_contracts(self) @@@ -144,9 -136,9 +144,9 @@@ # Verification if the construction of the contract is necessary. # Three cases are checked for `expect`: # - # - Is the `--full-contract` option it's use? - # - Is the method is in the main package - # - Is the method is in a direct imported package. + # - Was the `--full-contract` option passed? + # - Is the method is in the main package? + # - Is the method is in a direct imported package? # fun check_usage_expect(actual_mmodule: MModule): Bool do @@@ -162,95 -154,20 +162,95 @@@ # Verification if the construction of the contract is necessary. # Two cases are checked for `ensure`: # - # - Is the `--full-contract` option it's use? - # - Is the method is in the main package + # - Was the `--full-contract` option passed? + # - Is the method is in the main package? # fun check_usage_ensure(actual_mmodule: MModule): Bool do return toolcontext.opt_full_contract.value or mainmodule.mpackage == actual_mmodule.mpackage end + # Inject the incontract attribute (`_in_contract_`) in the `Sys` class + # This attribute allows to check if the contract need to be executed + private fun inject_incontract_in_sys + do + # If the `in_contract_attribute` already know just return + if in_contract_attribute != null then return + + var sys_class = toolcontext.modelbuilder.get_mclass_by_name(visited_module, mainmodule, "Sys") + + # Try to get the `in_contract` property, if it has already defined in a previously visited module. + var in_contract_property = toolcontext.modelbuilder.try_get_mproperty_by_name(visited_module, sys_class.intro, "__in_contract_") + if in_contract_property != null then + self.in_contract_attribute = in_contract_property.as(MAttribute) + return + end + + var bool_false = new AFalseExpr.make(mainmodule.bool_type) + var n_in_contract_attribute = toolcontext.modelbuilder.create_attribute_from_name("__in_contract_", sys_class.intro, mainmodule.bool_type, public_visibility).create_setter(toolcontext.modelbuilder, true).define_default(bool_false) + + in_contract_attribute = n_in_contract_attribute.mpropdef.mproperty + end + + # Return the `_in_contract_` attribute. + # If the attribute `_in_contract_` does not exist it's injected with `inject_incontract_in_sys` + private fun get_incontract: MAttribute + do + if self.in_contract_attribute == null then inject_incontract_in_sys + return in_contract_attribute.as(not null) + end + + # Return an `AIfExpr` with the contract encapsulated by an `if` to check if it's already in a contract verification. + # + # Example: + # ~~~nitish + # class A + # fun bar([...]) is except([...]) + # + # fun _contract_bar([...]) + # do + # if not sys._in_contract_ then + # sys._in_contract_ = true + # _bar_expect([...]) + # sys._in_contract_ = false + # end + # bar([...]) + # end + # + # fun _bar_expect([...]) + # end + # ~~~~ + # + private fun encapsulated_contract_call(visited_method: AMethPropdef, call_to_contracts: Array[ACallExpr]): AIfExpr + do + var sys_property = toolcontext.modelbuilder.model.get_mproperties_by_name("sys").first.as(MMethod) + var callsite_sys = ast_builder.create_callsite(toolcontext.modelbuilder, visited_method, sys_property, true) + + var incontract_attribute = get_incontract + + var callsite_get_incontract = ast_builder.create_callsite(toolcontext.modelbuilder, visited_method, incontract_attribute.getter.as(MMethod), false) + var callsite_set_incontract = ast_builder.create_callsite(toolcontext.modelbuilder, visited_method, incontract_attribute.setter.as(MMethod), false) + + var n_condition = ast_builder.make_not(ast_builder.make_call(ast_builder.make_call(new ASelfExpr, callsite_sys, null), callsite_get_incontract, null)) + + var n_if = ast_builder.make_if(n_condition, null) + + var if_then_block = n_if.n_then.as(ABlockExpr) + + if_then_block.add(ast_builder.make_call(ast_builder.make_call(new ASelfExpr, callsite_sys, null), callsite_set_incontract, [new ATrueExpr.make(mainmodule.bool_type)])) + if_then_block.add_all(call_to_contracts) + if_then_block.add(ast_builder.make_call(ast_builder.make_call(new ASelfExpr, callsite_sys, null), callsite_set_incontract, [new AFalseExpr.make(mainmodule.bool_type)])) + + return n_if + end end # This visitor checks the `callsite` to see if the target `mpropdef` has a contract. private class CallSiteVisitor super Visitor + + # Instance of the toolcontext var toolcontext: ToolContext # Actual visited method @@@ -288,10 -205,10 +288,10 @@@ en redef class AAnnotation - # Returns the conditions of annotation parameters in the form of and expr - # exemple: - # the contract ensure(true, i == 10, f >= 1.0) - # return this condition (true and i == 10 and f >= 1.0) + # Returns the conditions of annotation parameters. If there are several parameters, the result is an `AAndExpr` + # Example: + # the contract `ensure(true, i == 10, f >= 1.0)` + # return this condition `(true and i == 10 and f >= 1.0)` private fun construct_condition(v : ContractsVisitor): AExpr do var n_condition = n_args.first @@@ -321,10 -238,10 +321,10 @@@ abstract class MContrac private fun adapt_block_to_contract(v: ContractsVisitor, n_mpropdef: AMethPropdef) is abstract # Adapt the msignature specifically for the contract method - private fun adapt_specific_msignature(m_signature: MSignature): MSignature do return m_signature.adapt_to_condition + private fun adapt_specific_msignature(m_signature: MSignature): MSignature do return m_signature.adapt_to_contract # Adapt the nsignature specifically for the contract method - private fun adapt_specific_nsignature(n_signature: ASignature): ASignature do return n_signature.adapt_to_condition(null) + private fun adapt_specific_nsignature(n_signature: ASignature): ASignature do return n_signature.adapt_to_contract # Adapt the `m_signature` to the contract # If it is not null call the specific adapt `m_signature` for the contract @@@ -352,13 -269,10 +352,13 @@@ # Create the initial contract (intro) # All contracts have the same implementation for the introduction. # + # Example: + # ~~~nitish # fun contrat([...]) # do # assert contract_condition # end + # ~~~ # private fun create_intro_contract(v: ContractsVisitor, n_condition: nullable AExpr, mclassdef: MClassDef): AMethPropdef do @@@ -405,7 -319,7 +405,7 @@@ class MExpec # because if no contract is defined at the introduction the added # contracts will not cause any error even if they are not satisfied. # - # exemple + # Example: # ~~~nitish # class A # fun bar [...] @@@ -419,8 -333,7 +419,8 @@@ # redef fun bar is expect(contract_condition) # redef fun _bar_expect([...]) # do - # if not (contract_condition) then super + # if (contract_condition) then return + # super # end # end # ~~~~ @@@ -482,9 -395,7 +482,9 @@@ abstract class BottomMContrac return n_block end - # Inject the result variable in the `n_block` of the given `n_mpropdef`. + # Inject the `result` variable into the `n_block` of the given n_mpropdef`. + # + # The purpose of the variable is to capture return values to use it in contracts. private fun inject_result(v: ContractsVisitor, n_mpropdef: AMethPropdef, ret_type: MType): Variable do var actual_block = n_mpropdef.n_block @@@ -776,17 -687,6 +776,6 @@@ en redef class AMethPropdef - # Execute all method verification scope flow and typing. - # It also execute an ast validation to define all parents and all locations - private fun do_all(toolcontext: ToolContext) - do - self.validate - # FIXME: The `do_` usage it is maybe to much (verification...). Solution: Cut the `do_` methods into simpler parts - self.do_scope(toolcontext) - self.do_flow(toolcontext) - self.do_typing(toolcontext.modelbuilder) - end - # Entry point to create a contract (verification of inheritance, or new contract). redef fun create_contracts(v) do @@@ -849,26 -749,25 +838,26 @@@ en redef class MSignature - # Adapt signature for a expect condition - # Removed the return type is it not necessary - private fun adapt_to_condition: MSignature do return new MSignature(mparameters.to_a, null) + # Adapt signature for an contract + # + # The returned `MSignature` is the copy of `self` without return type. + private fun adapt_to_contract: MSignature do return new MSignature(mparameters.to_a, null) - # Adapt signature for a ensure condition + # Adapt signature for a ensure contract # - # Create new parameter with the return type + # The returned `MSignature` is the copy of `self` without return type. + # The return type is replaced by a new parameter `result` private fun adapt_to_ensurecondition: MSignature do var rtype = return_mtype - var msignature = adapt_to_condition + var msignature = adapt_to_contract if rtype != null then msignature.mparameters.add(new MParameter("result", rtype, false)) end return msignature end - # Adapt signature for a expect condition - # Removed the return type is it not necessary + # The returned `MSignature` is the exact copy of `self`. private fun clone: MSignature do return new MSignature(mparameters.to_a, return_mtype) end @@@ -888,26 -787,26 +877,26 @@@ redef class ASignatur return args end - # Return a copy of self adapted for the expect condition - # npropdef it is use to define the parent of the parameters - private fun adapt_to_condition(return_type: nullable AType): ASignature + # Create a new ASignature adapted for contract + # + # The returned `ASignature` is the copy of `self` without return type. + private fun adapt_to_contract: ASignature do var adapt_nsignature = self.clone - adapt_nsignature.n_type = return_type + if adapt_nsignature.n_type != null then adapt_nsignature.n_type.detach return adapt_nsignature end - # Return a copy of self adapted for postcondition on npropdef + # Create a new ASignature adapted for ensure + # + # The returned `ASignature` is the copy of `self` without return type. + # The return type is replaced by a new parameter `result` private fun adapt_to_ensurecondition: ASignature do - var nsignature = adapt_to_condition(null) + var nsignature = adapt_to_contract if ret_type != null then - var n_id = new TId - n_id.text = "result" - var new_param = new AParam - new_param.n_id = n_id - new_param.variable = new Variable(n_id.text) - new_param.variable.declared_type = ret_type - nsignature.n_params.add new_param + var variable = new Variable("result") + variable.declared_type = ret_type + nsignature.n_params.add new AParam.make(variable, ret_type.create_ast_representation) end return nsignature end