def _resolve_property(name: str, prop_ref: _Any, arity: int) -> Optional[PropertyDef]: """ Resolve the ``prop`` property reference (if any, built in the DSL) to the referenced property. If it is present, check its signature. :param name: Name of the property in the DSL construct. Used to format the error message. :param prop_ref: Property reference to resolve. :param arity: Expected number of entity arguments for this property ("Self" included). """ from langkit.expressions import FieldAccess # First, resolve the property prop: PropertyDef if prop_ref is None: return None elif isinstance(prop_ref, FieldAccess): node_data = prop_ref.resolve_field() if isinstance(node_data, PropertyDef): prop = node_data else: error(f"{name} must be a property") elif isinstance(prop_ref, T.Defer): prop = prop_ref.get() elif isinstance(prop_ref, PropertyDef): prop = prop_ref else: error( f"{name} must be either a FieldAccess resolving to a property," " or a direct reference to a property") # Second, check its signature prop = prop.root_property assert prop.struct check_source_language( prop.struct.matches(T.root_node), f"{name} must belong to a subtype of {T.root_node.dsl_name}", ) # Check that it takes the expected number of arguments. "Self" counts # as an implicit argument, so we expect at least ``arity - 1`` natural # arguments. n_args = arity - 1 entity_args = prop.natural_arguments[:n_args] extra_args = prop.natural_arguments[n_args:] check_source_language( len(entity_args) == n_args and all(arg.type.is_entity_type for arg in entity_args), f"{name} property must accept {n_args} entity arguments (only" f" {len(entity_args)} found)", ) # The other argumenst must be optional check_source_language( all(arg.default_value is not None for arg in extra_args), f"extra arguments for {name} must be optional", ) # Check the property return type check_source_language( prop.type.matches(T.root_node.entity), f"{name} must return a subtype of {T.entity.dsl_name}", ) # Check that all dynamic variables for this property are bound in the # current expression context. DynamicVariable.check_call_bindings(prop, f"In call to {{prop}} as {name}") # Third, generate a functor for this property, so that equations can # refer to it. from langkit.compile_context import get_context get_context().do_generate_logic_functors(prop, arity) return prop
def construct(self): check_multiple([ (self.pred_property.type.matches(T.Bool), 'Predicate property must return a boolean, got {}'.format( self.pred_property.type.dsl_name)), (self.pred_property.struct.matches(T.root_node), 'Predicate property must belong to a subtype of {}'.format( T.root_node.dsl_name)), ]) # Separate logic variable expressions from extra argument expressions exprs = [construct(e) for e in self.exprs] logic_var_exprs, closure_exprs = funcy.lsplit_by( lambda e: e.type == T.LogicVar, exprs) check_source_language( len(logic_var_exprs) > 0, "Predicate instantiation should have at " "least one logic variable expression") check_source_language( all(e.type != T.LogicVar for e in closure_exprs), 'Logic variable expressions should be grouped at the beginning,' ' and should not appear after non logic variable expressions') # Make sure this predicate will work on clean logic variables logic_var_exprs = [ResetLogicVar(expr) for expr in logic_var_exprs] # Compute the list of arguments to pass to the property (Self # included). args = ( [Argument(names.Name('Self'), self.pred_property.struct.entity)] + self.pred_property.natural_arguments) # Then check that 1) all extra passed actuals match what the property # arguments expect and that 2) arguments left without an actual have a # default value. default_passed_args = 0 for i, (expr, arg) in enumerate(zip_longest(exprs, args)): if expr is None: check_source_language( arg.default_value is not None, 'Missing an actual for argument #{} ({})'.format( i, arg.name.lower)) default_passed_args += 1 continue check_source_language( arg is not None, 'Too many actuals: at most {} expected, got {}'.format( len(args), len(exprs))) if expr.type == T.LogicVar: check_source_language( arg.type.matches(T.root_node.entity), "Argument #{} of predicate " "is a logic variable, the corresponding property formal " "has type {}, but should be a descendent of {}".format( i, arg.type.dsl_name, T.root_node.entity.dsl_name)) else: check_source_language( expr.type.matches(arg.type), "Argument #{} of predicate " "has type {}, should be {}".format(i, expr.type.dsl_name, arg.type.dsl_name)) DynamicVariable.check_call_bindings(self.pred_property, 'In predicate property {prop}') # Append dynamic variables to embed their values in the closure closure_exprs.extend( construct(dynvar) for dynvar in self.pred_property.dynamic_vars) pred_id = self.pred_property.do_generate_logic_predicate( tuple(e.type for e in closure_exprs), default_passed_args) args = " ({})".format(', '.join( ["{}" for _ in range(len(closure_exprs))])) if closure_exprs else "" predicate_expr = untyped_literal_expr( f"Create_{pred_id}_Predicate{args}", operands=closure_exprs) return Predicate.Expr(self.pred_property, pred_id, logic_var_exprs, predicate_expr, abstract_expr=self)
def construct(self): from langkit.compile_context import get_context self.resolve_props() get_context().do_generate_logic_binder(self.conv_prop, self.eq_prop) # We have to wait for the construct pass for the following checks # because they rely on type information, which is not supposed to be # computed before this pass. if self.conv_prop: check_multiple([ (self.conv_prop.type.matches(T.root_node.entity), 'Bind property must return a subtype of {}'.format( T.root_node.entity.dsl_name)), (self.conv_prop.struct.matches(T.root_node), 'Bind property must belong to a subtype of {}'.format( T.root_node.dsl_name)), ]) DynamicVariable.check_call_bindings(self.conv_prop, "In Bind's conv_prop {prop}") # Those checks are run in construct, because we need the eq_prop to be # prepared already, which is not certain in do_prepare (order # dependent). if self.eq_prop: args = self.eq_prop.natural_arguments check_multiple([ (self.eq_prop.type == T.Bool, 'Equality property must return boolean'), (self.eq_prop.struct.matches(T.root_node), 'Equality property must belong to a subtype of {}'.format( T.root_node.dsl_name)), (len(args) == 1, 'Equality property: expected 1 argument, got {}'.format( len(args))), ]) other_type = args[0].type check_source_language( other_type.is_entity_type, "First arg of equality property should be an entity type") check_source_language( other_type.element_type == self.eq_prop.struct, "Self and first argument should be of the same type") DynamicVariable.check_call_bindings(self.eq_prop, "In Bind's eq_prop {prop}") cprop_uid = (self.conv_prop.uid if self.conv_prop else "Default") eprop_uid = (self.eq_prop.uid if self.eq_prop else "Default") if self.conv_prop: pred_func = Bind.Expr.dynamic_vars_to_holder( self.conv_prop, 'Logic_Converter_{}'.format(cprop_uid)) else: pred_func = untyped_literal_expr('No_Logic_Converter_Default') # Left operand must be a logic variable. Make sure the resulting # equation will work on a clean logic variable. lhs = ResetLogicVar(construct(self.from_expr, T.LogicVar)) # Second one can be either a logic variable or an entity (or an AST # node that is promoted to an entity). rhs = construct(self.to_expr) if rhs.type.matches(T.LogicVar): # For this operand too, make sure it will work on a clean logic # variable. rhs = ResetLogicVar(rhs) elif rhs.type.matches(T.root_node): from langkit.expressions import make_as_entity rhs = make_as_entity(rhs) else: check_source_language( rhs.type.matches(T.root_node.entity), 'Right operand must be either a logic variable or an entity,' ' got {}'.format(rhs.type.dsl_name)) # Because of Ada OOP typing rules, for code generation to work # properly, make sure the type of `rhs` is the root node entity. if (rhs.type.matches(T.root_node.entity) and rhs.type is not T.root_node.entity): from langkit.expressions import Cast rhs = Cast.Expr(rhs, T.root_node.entity) return Bind.Expr(self.conv_prop, self.eq_prop, cprop_uid, eprop_uid, lhs, rhs, pred_func, abstract_expr=self)