diff --git a/RELEASENOTES-1.4.md b/RELEASENOTES-1.4.md index 071b7fa9..cd8c091b 100644 --- a/RELEASENOTES-1.4.md +++ b/RELEASENOTES-1.4.md @@ -408,3 +408,18 @@ overridable. See the documentation of these templates for more details. - `release 7 7129` - `release 6 6444` +- `note 6` Added non-`shared` abstract method declarations, e.g. + ``` + method m(uint8 a) -> (int); + ``` + Similarly to untyped abstract parameter declarations, an abstract method + declaration may be specified regardless of whether it's in the context of a + template definition, and regardless of what other declarations of the same + method exists (except that all declarations must share the same signature.) +- `note 6` Added a provisional feature `explicit_method_decls`, which can be + enabled per file by a statement `provisional explicit_method_decls;`. + This feature adds a syntax `method m() :{ ... }`, which signifies that the method + is *not* intended as an override. The existing syntax `method m() { ... }`` + is re-purposed to signify that the method *is* intended as an override, + Inconsistent usage will trigger compile errors. This is useful to + catch misspelled overrides. diff --git a/doc/1.4/language.md b/doc/1.4/language.md index 31039c43..e949623c 100644 --- a/doc/1.4/language.md +++ b/doc/1.4/language.md @@ -1798,13 +1798,37 @@ handled: * A method can only be overridden by another method if it is declared `default`. +All declarations of the same method in an object must share the same signature: +every declaration must share input parameters, return value types, agree on +whether the method throws, and agree on every method qualifier except +`shared` (such as [`independent`](#independent-methods)). + > [!NOTE] -> An overridable built-in method is defined by a template -> named as the object type. So, if you want to write a template that -> overrides the `read` method of a register, and want to make -> your implementation overridable, then your template must explicitly -> instantiate the `register` template using a statement `is -> register;`. +> Overridable built-in methods are often defined by a template named as the +> object type. For example, if you want to write a template that overrides the +> `read_register` method of a register, then your template must explicitly +> instantiate the `register` template using +> template name is (register) { ... }. + +### Abstract method declarations +A method may be declared abstractly, imposing a requirement that some definition +of that method, by some part of the device model, is provided to the object the +abstract declaration is a member of. If that requirement is not satisfied then +the device model will be rejected by the compiler. + +The following is an example of an abstract method declarations: +``` +method m(uint32 a, bool b) -> (uint8, uint16) throws; +``` + +Similarly to [untyped abstract parameter declarations](#parameters-detailed), a +non-`shared` abstract method declaration may be specified regardless of what +other declarations of that method there are in the model, except that it is +still subject to the requirement that every declaration of the same method must +share the same signature. In contrast, [`shared` abstract method +declarations](#shared-methods) may only be specified if there is no previous +([lower or equal ranked](#resolution-of-overrides)) `shared` declaration of that +method. ### Calling Methods @@ -2829,19 +2853,21 @@ incomplete description can be found in the [section on templates](#templates). *dominates* the set if it has higher rank than all other declarations in the set. Abstract `param` declarations (param name; or param name : - type;) and abstract method definitions (method + type;) and abstract method declarations (method name(args...);) are excluded here; they cannot dominate a set, and a dominating declaration in a set does not need to have higher declaration than any abstract `param` or `method` declaration in the set. -* There may be any number of *untyped* abstract definitions of a +* There may be any number of *untyped* abstract declarations of a parameter (param name;). * There may be at most one *typed* abstract definition of a parameter (param name : type;) -* There may be at most one abstract shared definition of a method. Any +* There may be any number of *non-shared* abstract declarations of a + method. +* There may be at most one abstract shared declaration of a method. Any other *shared* definition of this method must have higher rank than the abstract definition, but any rank is permitted for non-shared - definitions. For instance: + declarations. For instance: ``` template a { @@ -2855,7 +2881,7 @@ incomplete description can be found in the [section on templates](#templates). shared method m(); } template bb is b { - // Error: abstract shared definition overrides non-abstract + // Error: abstract shared declaration overrides non-abstract shared method m(); } ``` diff --git a/py/dead_dml_methods.py b/py/dead_dml_methods.py index a1c9ae44..0478352d 100644 --- a/py/dead_dml_methods.py +++ b/py/dead_dml_methods.py @@ -91,14 +91,14 @@ def traverse_ast(ast): if any(stmt.kind == 'error' for stmt in body.args[0]): # poisoned method, apparently meant to be dead ignored = True - yield (ast.site.lineno, ast.args[4].lineno, ast.args[0], ignored) + yield (ast.site.lineno, ast.args[5].lineno, ast.args[0], ignored) elif ast.kind == 'sharedmethod': - body = ast.args[6] + body = ast.args[7] if body is None: # abstract method, no code generated return assert body.kind == 'compound' - yield (ast.site.lineno, ast.args[7].lineno, ast.args[0], False) + yield (ast.site.lineno, ast.args[8].lineno, ast.args[0], False) elif ast.kind in {'toplevel_if', 'hashif'}: (_, t, f) = ast.args for block in [t, f]: diff --git a/py/dml/dmlparse.py b/py/dml/dmlparse.py index c2ab5fc7..ab2d8761 100644 --- a/py/dml/dmlparse.py +++ b/py/dml/dmlparse.py @@ -551,7 +551,7 @@ def object_method_noinparams(t): body = t[6] t[0] = ast.method(site(t), name, (inp, outp, throws, [], body), - t[5], t[2], lex_end_site(t, -1)) + t[5], t[2], False, lex_end_site(t, -1)) @prod_dml12 @@ -578,7 +578,7 @@ def object_method(t): body = t[10] t[0] = ast.method(site(t), name, (inp, outp, throws, [], body), - t[9], t[2], lex_end_site(t, -1)) + t[9], t[2], False, lex_end_site(t, -1)) def method_qualifiers_check(site, qualifiers, inp, outp, throws, default): @@ -603,22 +603,47 @@ def method_qualifiers_check(site, qualifiers, inp, outp, throws, default): "startup methods may not be declared 'default'")) return (inp, outp) +@prod_dml14 +def maybe_colon_yes(t): + '''maybe_colon : COLON''' + if not site(t).provisional_enabled(provisional.explicit_method_decls): + report(ESYNTAX(site(t), ':', "expected '{' or 'default'")) + t[0] = False + else: + t[0] = True + +@prod +def maybe_colon_no(t): + '''maybe_colon : ''' + t[0] = False @prod_dml14 def object_method(t): - '''method : method_qualifiers METHOD objident method_params_typed maybe_default compound_statement''' + '''method : method_qualifiers METHOD objident method_params_typed maybe_colon maybe_default compound_statement''' name = t[3] (inp, outp, throws) = t[4] - body = t[6] + body = t[7] + (inp, outp) = method_qualifiers_check(site(t), t[1], inp, outp, throws, + t[6]) + t[0] = ast.method(site(t), name, + (inp, outp, throws, t[1], body), + t[6], False, t[5], lex_end_site(t, -1)) + +@prod_dml14 +def object_method_abstract(t): + '''method : method_qualifiers METHOD objident method_params_typed SEMI''' + name = t[3] + (inp, outp, throws) = t[4] + body = None (inp, outp) = method_qualifiers_check(site(t), t[1], inp, outp, throws, - t[5]) + False) t[0] = ast.method(site(t), name, (inp, outp, throws, t[1], body), - t[5], False, lex_end_site(t, -1)) + True, False, None, site(t, 5)) @prod_dml14 def object_inline_method(t): - '''method : INLINE METHOD objident method_params_maybe_untyped maybe_default compound_statement''' + '''method : INLINE METHOD objident method_params_maybe_untyped maybe_colon maybe_default compound_statement''' name = t[3] (inp, outp, throws) = t[4] if all(typ for (_, asite, name, typ) in inp): @@ -626,10 +651,10 @@ def object_inline_method(t): # We forbid it as a way to strongly discourage unneeded use of inline. report(ESYNTAX(site(t, 2), 'inline', 'only use inline if there are untyped arguments')) - body = t[6] + body = t[7] t[0] = ast.method(site(t), name, (inp, outp, throws, [], body), - t[5], False, lex_end_site(t, -1)) + t[6], False, t[5], lex_end_site(t, -1)) @prod_dml12 @@ -730,12 +755,13 @@ def template_statement_obj(t): @prod_dml14 def template_statement_shared_method(t): '''template_stmt : SHARED method_qualifiers METHOD shared_method''' - (name, (inp, outp, throws), overridable, body, rbrace_site) = t[4] + (name, (inp, outp, throws), overridable, explicit_decl, body, + rbrace_site) = t[4] default = overridable and body is not None (inp, outp) = method_qualifiers_check(site(t), t[2], inp, outp, throws, default) t[0] = [ast.sharedmethod(site(t), name, inp, outp, throws, t[2], - overridable, body, rbrace_site)] + overridable, explicit_decl, body, rbrace_site)] @prod_dml14 def template_statement_shared_hook(t): @@ -765,23 +791,24 @@ def method_qualifiers(t): @prod_dml12 def trait_method(t): '''trait_method : METHOD shared_method''' - (name, (inp, outp, throws), overridable, body, rbrace_site) = t[2] + (name, (inp, outp, throws), overridable, explicit_decl, body, + rbrace_site) = t[2] t[0] = ast.sharedmethod(site(t), name, inp, outp, throws, [], overridable, - body, rbrace_site) + explicit_decl, body, rbrace_site) @prod def shared_method_abstract(t): '''shared_method : ident method_params_typed SEMI''' - t[0] = (t[1], t[2], True, None, site(t, 3)) + t[0] = (t[1], t[2], True, False, None, site(t, 3)) @prod def shared_method_default(t): - '''shared_method : ident method_params_typed DEFAULT compound_statement''' - t[0] = (t[1], t[2], True, t[4], lex_end_site(t, -1)) + '''shared_method : ident method_params_typed maybe_colon DEFAULT compound_statement''' + t[0] = (t[1], t[2], True, t[3], t[5], lex_end_site(t, -1)) @prod def shared_method_final(t): - '''shared_method : ident method_params_typed compound_statement''' - t[0] = (t[1], t[2], False, t[3], lex_end_site(t, -1)) + '''shared_method : ident method_params_typed maybe_colon compound_statement''' + t[0] = (t[1], t[2], False, t[3], t[4], lex_end_site(t, -1)) @prod_dml12 def trait_param(t): diff --git a/py/dml/messages.py b/py/dml/messages.py index 7dd55689..53e29604 100644 --- a/py/dml/messages.py +++ b/py/dml/messages.py @@ -198,9 +198,9 @@ def log(self): class EAMETH(DMLError): """ - An abstract method cannot override another method. + A shared abstract method cannot override another method. """ - fmt = "abstract method %s overrides existing method" + fmt = "shared abstract method %s overrides existing method" def __init__(self, site, prev_site, name): DMLError.__init__(self, site, name) @@ -247,6 +247,13 @@ def log(self): self.print_site_message( self.decl_site, "abstract declaration") +class EABSMETH(DMLError): + """ + An (abstractly) declared method never has any definition made for it. + """ + version = "1.4" + fmt = "declared method %s is never implemented" + class EIMPORT(DMLError): """ The file to imported could not be found. Use the `-I` @@ -1133,7 +1140,7 @@ class EAUTOPARAM(DMLError): library, and they may not be overridden.""" fmt = "bad declaration of automatic parameter '%s'" -class ENOVERRIDE(DMLError): +class ENOVERRIDEPARAM(DMLError): """When the `explict_param_decls` provisional feature is enabled, parameter definitions written using `=` and `default` are only accepted if the parameter has already been declared. @@ -1153,7 +1160,7 @@ def log(self): "enabled by the explicit_param_decls provisional feature") -class EOVERRIDE(DMLError): +class EOVERRIDEPARAM(DMLError): """When the `explict_param_decls` provisional feature is enabled, any parameter declared via `:=` or `:default` may not already have been declared. This means `:=` or `:default` syntax can't be used @@ -1270,6 +1277,42 @@ def log(self): if self.othersite: self.print_site_message(self.othersite, "conflicting definition") +class ENOVERRIDEMETH(DMLError): + """When the `explict_method_decls` provisional feature is enabled, method + definitions written using `{ ... }` and `default { ... }` are only accepted + if the method has already been declared. + + To declare and define a new method not already declared, use the `:{ ... }` + or `:default { ... }` syntax. + """ + fmt = ("method '%s' not declared previously." + " To declare and define a new method, use the ':%s{...}' syntax.") + + def log(self): + from . import provisional + DMLError.log(self) + prov_site = self.site.provisional_enabled( + provisional.explicit_method_decls) + self.print_site_message( + prov_site, + "enabled by the explicit_method_decls provisional feature") + +class EOVERRIDEMETH(DMLError): + """When the `explict_method_decls` provisional feature is enabled, + any method declared via `:{ ... }` or `:default { ... }` may not already + have been declared. This means `:{ ... }` or `:default { ... }` syntax + can't be used to override existing parameter declarations (not even those + lacking a definition of the parameter.) + """ + fmt = ("the method '%s' has already been declared " + + "(':%s{ ... }' syntax may not be used for method overrides)") + def __init__(self, site, other_site, name, token): + super().__init__(site, name, token) + self.other_site = other_site + def log(self): + DMLError.log(self) + self.print_site_message(self.other_site, "existing declaration") + class EIMPLMEMBER(DMLError): """ A method in an `implement` object corresponds to a struct member diff --git a/py/dml/provisional.py b/py/dml/provisional.py index 1b36754e..91095718 100644 --- a/py/dml/provisional.py +++ b/py/dml/provisional.py @@ -84,6 +84,60 @@ class explicit_param_decls(ProvisionalFeature): short = "Require := syntax for defining new params" stable = True +@feature +class explicit_method_decls(ProvisionalFeature): + ''' + This feature extends the DML syntax for methods to distinguish between an + intent to declare a new method, and an intent to override an existing + method/provide a definition for an abstract method. + This distinction allows DML to capture misspelled parameter overrides as + compile errors. + + This provisional feature introduces new syntax for the following purposes: + * Abstract method declarations + ``` + method m(...) [-> (...)] [throws]; + ``` + + This declaration establishes a requirement that a method of that name and + signature is defined in the same object as the abstract method + declaration. This is similar to the existing abstract `shared` method + declaration, but unlike abstract `shared` methods have no restrictions + or semantic implications beyond that (except for the fact that it + declares the method to exist.) In other words, it's semantically + analagous to untyped abstract parameter declarations (`param p;`). + + * Simultaneously declaring and defining a new method + ``` + method m(...) [-> (...)] [throws] :{ ... } + method m(...) [-> (...)] [throws] :default { ... } + ``` + + DMLC rejects a declaration of this form if the method has already been + declared, because this form signifies that the declaration was not + intended as an override. + + `explicit_metod_decls` also changes the meaning of the traditional form + of method definitions (e.g. `method m() {}` or `method m() default {}`) + such that DMLC will reject them if the method has not been declared + previously (either abstractly or with an overridable definition.) + + In some rare cases, you may need to declare a method without + knowing if it's an override or a new declaration. In this case, one + can accompany an overriding definition (e.g. `method m() {}` + or `method() default {}`) with an abstract method declaration (e.g. + `method m();`) in the same scope/rank. This marks that the method + definition may either be for a previously declared method or a new method + entirely, and no error will be printed. + + Enabling the `explicit_method_decls` feature in a file only affects + the method definitions specified in that file; in other words, it will not + require other files to use the `:{` syntax in order to declare novel + methods. + ''' + short = "Require :{ ... } syntax for defining new methods" + stable = False + @feature class simics_util_vect(ProvisionalFeature): diff --git a/py/dml/structure.py b/py/dml/structure.py index 93a3f467..020f9794 100644 --- a/py/dml/structure.py +++ b/py/dml/structure.py @@ -477,10 +477,11 @@ def wrap_sites(spec, issite, tname): for stmt in shallow: asttype = stmt.kind if asttype == 'method': - (_, site, name, value, overridable, export, rsite) = stmt + (_, site, name, value, overridable, explicit_decl, export, + rsite) = stmt shallow_wrapped.append(ast.method( TemplateSite(site, issite, tname), name, value, - overridable, export, rsite)) + overridable, explicit_decl, export, rsite)) elif asttype == 'error': (_, site, msg) = stmt shallow_wrapped.append( @@ -676,14 +677,14 @@ def decl_is_default(decl): if not declared_as_override and parent_ranks: [parent, *_] = (parent for (parent_rank, parent) in params if parent_rank in parent_ranks) - report(EOVERRIDE(type_info.site, parent.site, name, - 'default' if is_default else '=')) + report(EOVERRIDEPARAM(type_info.site, parent.site, name, + 'default' if is_default else '=')) if not declared_as_override and rank in decls: - report(EOVERRIDE(type_info.site, decls[rank].site, name, - 'default' if is_default else '=')) + report(EOVERRIDEPARAM(type_info.site, decls[rank].site, name, + 'default' if is_default else '=')) elif (not parent_ranks and declared_as_override and rank not in decls): - report(ENOVERRIDE( + report(ENOVERRIDEPARAM( p.site, name, 'default' if is_default else '=')) [(rank0, param0)] = superior @@ -711,9 +712,9 @@ def decl_is_default(decl): def typecheck_method_override(m1, m2, location): '''check that m1 can override m2''' assert m1.kind == m2.kind == 'method' - (_, (inp1, outp1, throws1, qualifiers1, _), _, _, _) \ + (_, (inp1, outp1, throws1, qualifiers1, _), _, _, _, _) \ = m1.args - (_, (inp2, outp2, throws2, qualifiers2, _), _, _, _) \ + (_, (inp2, outp2, throws2, qualifiers2, _), _, _, _, _) \ = m2.args (independent1, startup1, memoized1) = ('independent' in qualifiers1, 'startup' in qualifiers1, @@ -788,14 +789,10 @@ def typecheck_method_override(m1, m2, location): report(PINARGTYPE(m1.site, 'method')) def qualifier_check(qualifier_name, qualifier1, qualifier2): - if qualifier1 > qualifier2: + if qualifier1 != qualifier2: raise EMETH(m1.site, m2.site, - (f"overriding method is declared {qualifier_name}, " - + "but the overridden method is not")) - elif qualifier1 < qualifier2: - raise EMETH(m1.site, m2.site, - (f"overridden method is declared {qualifier_name}, " - + "but the overriding method is not")) + (f"one declaration is qualified as {qualifier_name}, " + + "but the other is not")) qualifier_check('independent', independent1, independent2) qualifier_check('startup', startup1, startup2) @@ -815,23 +812,6 @@ def report_poverride(sup, inf, obj_specs): else: report(POVERRIDE_IMPORT(sup_obj.site, inf.desc.text)) -def sort_method_implementations(implementations, obj_specs): - if dml.globals.dml_version == (1, 2) and len(implementations) == 2: - # Backward compatibility: If there is exactly one default and - # one non-default implementation, then disregard template - # instantiation relations. - [m1, m2] = implementations - # create fake ranks to make sure methods end up in the right order - if m1.overridable and not m2.overridable: - if logging.show_porting: - report_poverride(m2.obj_spec.rank, m1.obj_spec.rank, obj_specs) - m2.rank = Rank({m1.rank}, m2.rank.desc) - elif not m1.overridable and m2.overridable: - if logging.show_porting: - report_poverride(m1.rank, m2.rank, obj_specs) - m1.rank = Rank({m2.rank}, m1.rank.desc) - return traits.sort_method_implementations(implementations) - def merge_subobj_defs(def1, def2, parent): (objtype, name, arrayinfo, obj_specs1) = def1 (objtype2, name2, arrayinfo2, obj_specs2) = def2 @@ -1434,51 +1414,149 @@ def wrap_method_body_in_try(site, overridden_site, obj, name, body, class ObjMethodHandle(traits.MethodHandle): shared = False def __init__(self, method_ast, obj_spec): - (name, (_, _, throws, _, _), overridable, _, _) = method_ast.args + (name, (inp, outp, throws, qualifiers, body), overridable, + _extern, explicit_decl, _rbrace_site) = method_ast.args super(ObjMethodHandle, self).__init__( - method_ast.site, name, obj_spec, overridable) + method_ast.site, name, obj_spec, overridable, body is None, + explicit_decl, inp, outp, throws, 'independent' in qualifiers, + 'startup' in qualifiers, 'memoized' in qualifiers) self.method_ast = method_ast - self.throws = throws class TraitMethodHandle(traits.MethodHandle): shared = True def __init__(self, trait_method, obj_spec): super(TraitMethodHandle, self).__init__( - trait_method.site, trait_method.name, - obj_spec, trait_method.overridable) + trait_method.site, trait_method.name, obj_spec, + trait_method.overridable, False, None, trait_method.inp, + trait_method.outp, trait_method.throws, trait_method.independent, + trait_method.startup, trait_method.memoized) self.trait_method = trait_method - self.throws = trait_method.throws -def process_method_implementations(obj, name, implementations, - shared_impl_traits, obj_specs, - vtable_nothrow_dml14): - # A method can have both shared and non-shared implementations. +class AbstractTraitMethodHandle(traits.MethodHandle): + shared = True + def __init__(self, name, vtable_trait): + (site, *sig) = vtable_trait.vtable_methods[name] + obj_spec = dml.globals.templates[vtable_trait.name].spec + super(AbstractTraitMethodHandle, self).__init__( + site, name, obj_spec, True, True, None, *sig) + +def process_method_declarations(obj, name, declarations, + shared_impl_traits, shared_absdecl_traits, + obj_specs, vtable_nothrow_dml14): + # A method can have both shared and non-shared declarations. # We will create a MethodHandle object for either, which # sort_method_implementations() uses to resolve override order. unshared_methods = [ ObjMethodHandle(method_ast, obj_spec) - for (obj_spec, method_ast) in implementations] + for (obj_spec, method_ast) in declarations] shared_methods = [ TraitMethodHandle( impl_trait.method_impls[name], dml.globals.templates[impl_trait.name].spec) for impl_trait in shared_impl_traits] + shared_abstract_methods = [ + AbstractTraitMethodHandle(name, vtable_trait) + for vtable_trait in shared_absdecl_traits] - (default_map, method_order) = sort_method_implementations( - unshared_methods + shared_methods, obj_specs) + methods = unshared_methods + shared_methods + shared_abstract_methods + impls = [meth for meth in methods if not meth.abstract] + + if dml.globals.dml_version == (1, 2) and len(impls) == 2: + # Backward compatibility: If there is exactly one default and + # one non-default implementation, then disregard template + # instantiation relations. + [m1, m2] = impls + # create fake ranks to make sure methods end up in the right order + if m1.overridable and not m2.overridable: + if logging.show_porting: + report_poverride(m2.obj_spec.rank, m1.obj_spec.rank, obj_specs) + m2.rank = Rank({m1.rank}, m2.rank.desc) + elif not m1.overridable and m2.overridable: + if logging.show_porting: + report_poverride(m1.rank, m2.rank, obj_specs) + m1.rank = Rank({m2.rank}, m1.rank.desc) + + rank_to_methods = {} + for meth in methods: + rank_to_methods.setdefault(meth.rank, []).append(meth) + + minimal_ancestry = traits.calc_minimal_ancestry(frozenset(rank_to_methods)) + + for (r, meths) in rank_to_methods.items(): + [impl, *others] = sorted(meths, key=lambda meth: meth.abstract) + if impl.abstract: + continue + + for other in others: + if not other.abstract: + # two conflicting method definitions in the same block + raise ENAMECOLL(other.site, impl.site, other.name) + + if (not impl.shared # Handled separately + and impl.site.provisional_enabled( + provisional.explicit_method_decls)): + existing = others or [m for anc in minimal_ancestry[r] + for m in rank_to_methods[anc]] + if impl.explicit_decl: + if existing: + report(EOVERRIDEMETH(impl.site, existing[0].site, + impl.name, + 'default ' * impl.overridable)) + elif not existing: + report(ENOVERRIDEMETH(impl.site, impl.name, + 'default ' * impl.overridable)) + + (default_map, method_order) = traits.sort_method_implementations(impls) location = Location(obj, static_indices(obj)) - impl_to_method = {} - for (default_level, impl) in reversed(list(enumerate( - method_order))): + nonshared_impls = [] + + for impl in method_order: if impl.shared: if default_map[impl]: # shared method overrides a non-shared method report(ETMETH( default_map[impl][0].site, impl.site, name)) - # handled separately - continue + else: + nonshared_impls.append(impl) + + abstract_decls = [meth for meth in methods + if meth.abstract and not meth.shared] + + # Typecheck nonshared abstract decls against each other + for (meth0, meth1) in itertools.pairwise(abstract_decls): + typecheck_method_override(meth0.method_ast, meth1.method_ast, + location) + + # Then typecheck any shared abstract declaration -- or, failing that, + # the highest ranking implementation -- against some arbitrary abstract + # decl + # This prioritization is because we consider the trait authorative for what + # the type should be. + if abstract_decls: + if shared_abstract_methods: + traits.typecheck_method_override( + abstract_decls[0].signature, + shared_abstract_methods[0].signature) + elif method_order: + impl = method_order[0] + if impl.shared: + traits.typecheck_method_override( + impl.signature, abstract_decls[0].signature) + else: + typecheck_method_override( + impl.method_ast, abstract_decls[0].method_ast, location) + else: + # This only done in this path because we favor reporting + # EABSTEMPLATE (done by the caller) over EABSMETH + report(EABSMETH(abstract_decls[0].site, name)) + + if not nonshared_impls: + return None + + impl_to_method = {} + for (default_level, impl) in reversed(list(enumerate(nonshared_impls))): defaults = default_map[impl] if len(defaults) == 0: default = InvalidDefault(traits.NoDefaultSymbol(impl.site)) @@ -1492,7 +1570,7 @@ def process_method_implementations(obj, name, implementations, default = InvalidDefault(traits.AmbiguousDefaultSymbol( [m.site for m in defaults])) (name, (inp_ast, outp_ast, throws, qualifiers, body), _, - _, rbrace_site) = impl.method_ast.args + _, _, rbrace_site) = impl.method_ast.args independent = 'independent' in qualifiers startup = 'startup' in qualifiers memoized = 'memoized' in qualifiers @@ -1545,8 +1623,10 @@ def process_method_implementations(obj, name, implementations, # 1.2 and 1.4, pass else: + annotation = ("no"*(dml.globals.dml_version != (1, 4)) + + "throw") raise EMETH(impl.site, overridden.site, - "different nothrow annotations") + f"different {annotation} annotations") template = (impl.obj_spec.parent_template if isinstance(impl.obj_spec, InstantiatedTemplateSpec) @@ -1565,8 +1645,8 @@ def process_method_implementations(obj, name, implementations, obj.template_method_impls[(template, name)] = method if dml.globals.dml_version == (1, 2): - for (_, method_ast) in implementations: - (_, msite, _, _, _, exported, _) = method_ast + for (_, method_ast) in declarations: + (_, msite, _, _, _, exported, _, _) = method_ast if exported: if not method.fully_typed: raise EEXTERN(method.site) @@ -1652,7 +1732,7 @@ def mkobj2(obj, obj_specs, params, each_stmts): _, esite, msg = s raise EERRSTMT(esite, msg or "explicit error") elif s.kind == 'method': - (name, _, _, _, _) = s.args + (name, _, _, _, _, _) = s.args if name not in method_asts: if name in symbols: report(ENAMECOLL(s.site, symbols[name], name)) @@ -1782,7 +1862,7 @@ def mkobj2(obj, obj_specs, params, each_stmts): break else: vtable_nothrow_dml14 = False - implementations = method_asts[name] + declarations = method_asts[name] if (dml.globals.dml_version != (1, 2) and name in { 'register': {'read', 'write', 'read_field', 'write_field'}, @@ -1790,19 +1870,31 @@ def mkobj2(obj, obj_specs, params, each_stmts): 'get', 'set'}, }.get(obj.objtype, set())): if dml.globals.traits[name] not in ancestors: - (_, mast) = implementations[0] + (_, mast) = declarations[0] report(WNOIS(mast.site, name)) + trait_impls = trait_method_impls.get(name, []) + trait_abstract_decls = Set() + # Right now, calculating trait_abstract_decls is only relevant when + # trait_impls is empty, so we don't calculate it otherwise + if not trait_impls: + for t in explicit_traits: + decl = t.member_declaration(name) + if (decl is not None + and t.member_kind(name) == 'method'): + trait_abstract_decls.add(t.vtable_trait(name)) + try: - method = process_method_implementations( - obj, name, implementations, - trait_method_impls.get(name, []), + method = process_method_declarations( + obj, name, declarations, + trait_method_impls.get(name, []), trait_abstract_decls, obj_specs, dml.globals.dml_version == (1, 2) and vtable_nothrow_dml14) except DMLError as e: report(e) else: - obj.add_component(method) + if method is not None: + obj.add_component(method) if logging.show_porting: report_pbefaft(obj, method_asts) diff --git a/py/dml/traits.py b/py/dml/traits.py index 714174e6..9a4e0e86 100644 --- a/py/dml/traits.py +++ b/py/dml/traits.py @@ -9,7 +9,7 @@ import abc import os from . import objects, logging, crep, codegen, toplevel, topsort -from . import breaking_changes +from . import breaking_changes, provisional from .logging import * from .codegen import * from .symtab import * @@ -41,7 +41,7 @@ def process_trait(site, name, subasts, ancestors, template_symbols): hooks = {} def check_namecoll(name, site): if name in methods: - (othersite, _, _, _, _, _, _, _, _, _) = methods[name] + (othersite, _, _, _, _, _, _, _, _, _, _) = methods[name] raise ENAMECOLL(site, othersite, name) if name in params: (othersite, _) = params[name] @@ -57,7 +57,7 @@ def check_namecoll(name, site): try: if ast.kind == 'sharedmethod': (mname, inp_asts, outp_asts, throws, qualifiers, - overridable, body, rbrace_site) = ast.args + overridable, explicit_decl, body, rbrace_site) = ast.args independent = 'independent' in qualifiers startup = 'startup' in qualifiers memoized = 'memoized' in qualifiers @@ -71,8 +71,8 @@ def check_namecoll(name, site): outp = eval_method_outp(outp_asts, None, global_scope) check_namecoll(mname, ast.site) methods[mname] = (ast.site, inp, outp, throws, independent, - startup, memoized, overridable, body, - rbrace_site) + startup, memoized, overridable, + explicit_decl, body, rbrace_site) elif ast.kind in {'session', 'saved'}: (decls, _) = ast.args for decl_ast in decls: @@ -327,7 +327,8 @@ def mktrait(site, tname, ancestors, methods, params, sessions, hooks, bad_methods = set() for (name, (msite, inp, outp, throws, independent, startup, memoized, - overridable, body, rbrace_site)) in list(methods.items()): + overridable, explicit_decl, body, rbrace_site) + ) in list(methods.items()): argnames = set() for p in inp: if p.ident: @@ -335,9 +336,12 @@ def mktrait(site, tname, ancestors, methods, params, sessions, hooks, report(EARGD(msite, p.ident)) bad_methods.add(name) argnames.add(p.ident) + + some_coll = False for ancestor in direct_parents: coll = ancestor.member_declaration(name) if coll: + some_coll = True (orig_site, orig_trait) = coll if orig_trait.member_kind(name) != 'method': # cannot override non-method with method @@ -358,14 +362,22 @@ def mktrait(site, tname, ancestors, methods, params, sessions, hooks, report(EDMETH(msite, orig_trait.method_impls[name].site, name)) bad_methods.add(name) + elif explicit_decl: + report(EOVERRIDEMETH(msite, orig_site, name, + 'default '*overridable)) + bad_methods.add(name) elif name not in ancestor_vtables: raise ICE(msite, 'ancestor is overridable but not in vtable') - # Type-checking of overrides is done later, after typedefs # have been populated with all template types. # See Trait.typecheck_methods() + if (body is not None and not some_coll and not explicit_decl + and msite.provisional_enabled(provisional.explicit_method_decls)): + report(ENOVERRIDEMETH(msite, name, 'default '*overridable)) + bad_methods.add(name) + for name in bad_methods: del methods[name] @@ -403,7 +415,8 @@ def typecheck_method_override(left, right): if len(outp0) != len(outp1): raise EMETH(site0, site1, "different number of output arguments") if throws0 != throws1: - raise EMETH(site0, site1, "different nothrow annotations") + annotation = "no"*(dml.globals.dml_version != (1, 4)) + "throw" + raise EMETH(site0, site1, f"different {annotation} annotations") for (p0, p1) in zip(inp0, inp1): t0 = safe_realtype_unconst(p0.typ) t1 = safe_realtype_unconst(p1.typ) @@ -424,14 +437,10 @@ def typecheck_method_override(left, right): "mismatching types in output argument %d" % (i + 1,)) def qualifier_check(qualifier_name, qualifier0, qualifier1): - if qualifier0 > qualifier1: + if qualifier0 != qualifier1: raise EMETH(site0, site1, - (f"overriding method is declared {qualifier_name}, " - + "but the overridden method is not")) - elif qualifier0 < qualifier1: - raise EMETH(site0, site1, - (f"overridden method is declared {qualifier_name}, " - + "but the overriding method is not")) + (f"one declaration is qualified as {qualifier_name}, " + + "but the other is not")) qualifier_check('independent', independent0, independent1) qualifier_check('startup', startup0, startup1) @@ -504,12 +513,28 @@ def merge_method_impl_maps(site, parents): return merged_impls class MethodHandle(object): - def __init__(self, site, name, obj_spec, overridable): + def __init__(self, site, name, obj_spec, overridable, abstract, + explicit_decl, + inp, outp, throws, independent, startup, memoized): self.site = site self.name = name self.obj_spec = obj_spec self.overridable = overridable self.rank = obj_spec.rank + self.abstract = abstract + self.explicit_decl = explicit_decl + self.inp = inp + self.outp = outp + self.throws = throws + self.independent = independent + self.startup = startup + self.memoized = memoized + + @property + def signature(self): + '''Used to simplify calls to typecheck_method_override''' + return (self.site, self.inp, self.outp, self.throws, self.independent, + self.startup, self.memoized) def get_highest_ranks(ranks): '''Given a set of ranks, return the subset of highest unrelated ranks''' @@ -549,13 +574,10 @@ def sort_method_implementations(implementations): ast.method to list of ast.method it overrides, and method_order is a topological ordering of methods based on this graph.''' - rank_to_method = {} - for impl in implementations: - if impl.rank in rank_to_method: - # two conflicting method definitions in the same block - raise ENAMECOLL(impl.site, rank_to_method[impl.rank].site, - impl.name) - rank_to_method[impl.rank] = impl + if not implementations: + return ({}, []) + + rank_to_method = { impl.rank: impl for impl in implementations } minimal_ancestry = calc_minimal_ancestry(frozenset(rank_to_method)) @@ -712,7 +734,7 @@ def __init__(self, site, name, ancestors, methods, params, sessions, hooks, overridable, body, self, name, ancestor_method_impls.get(name, []), rbrace_site) for (name, (msite, inp, outp, throws, independent, startup, - memoized, overridable, body, rbrace_site)) + memoized, overridable, _, body, rbrace_site)) in list(methods.items()) if body is not None} @@ -734,8 +756,8 @@ def __init__(self, site, name, ancestors, methods, params, sessions, hooks, # methods and parameters that are direct members of this trait's vtable self.vtable_methods = { name: (msite, inp, outp, throws, independent, startup, memoized) - for (name, (msite, inp, outp, throws, independent, startup, memoized, - overridable, _, _)) + for (name, (msite, inp, outp, throws, independent, startup, + memoized, overridable, _, _, _)) in list(methods.items()) if overridable and name not in ancestor_vtables} self.vtable_params = params diff --git a/py/dml/traits_test.py b/py/dml/traits_test.py index e78aca21..34f24d6d 100644 --- a/py/dml/traits_test.py +++ b/py/dml/traits_test.py @@ -44,7 +44,7 @@ def test_one_default_method(self): body = dml.ast.compound(self.site, [], self.site) t = Trait(self.site, 't', set(), {'m': (self.site, [], [], False, False, False, False, True, - body, None)}, + False, body, None)}, {}, {}, {}, {}, {}, {}) ot = ObjTraits(self.dev, {t}, {'m': t}, {}, {}) self.dev.set_traits(ot) diff --git a/test/1.4/errors/T_EMETH.dml b/test/1.4/errors/T_EMETH.dml index 8a1b740d..99f449a9 100644 --- a/test/1.4/errors/T_EMETH.dml +++ b/test/1.4/errors/T_EMETH.dml @@ -23,10 +23,28 @@ template t { } /// ERROR EMETH independent method indep_ret() -> (int) default { return 0; } + + /// ERROR EMETH + method with_abstract_decls(); + + method with_abstract_decls(int i) default {} + + shared method sm() {} } is t; +/// ERROR EMETH +method sm(int i); + +// no error +method sm(); + +/// ERROR EMETH +method with_abstract_decls(bool b); + +method with_abstract_decls(int i) {} + template u is t {} template v1 is u { @@ -62,6 +80,11 @@ group g4 is t { } group g5 is t { + /// ERROR EMETH + method with_abstract_decls(bool b); + + method with_abstract_decls(int i) {} + /// ERROR EMETH independent startup method indep() { } diff --git a/test/1.4/provisional/T_explicit_method_decls.dml b/test/1.4/provisional/T_explicit_method_decls.dml new file mode 100644 index 00000000..bb5e6dac --- /dev/null +++ b/test/1.4/provisional/T_explicit_method_decls.dml @@ -0,0 +1,44 @@ +/* + © 2025 Intel Corporation + SPDX-License-Identifier: MPL-2.0 +*/ + +dml 1.4; + +provisional explicit_method_decls; + +device test; + +/// COMPILE-ONLY + +template t { + method m1() :{} + method m2() :default {} + method m3(); + + shared method sm1() :{} + shared method sm2() :default {} + shared method sm3(); +} + +template u { + method m1(); + method m2() :default {} + method m3(); +} + +template v is (t, u) { + method m2() {} + method m3() default {} + shared method sm2() {} + shared method sm3() default {} +} + +group g is v { + method m1(); + method m2(); + method m3(); + + method m3() {} + method sm3() {} +} diff --git a/test/1.4/provisional/T_explicit_method_decls_EOVERRIDEMETH.dml b/test/1.4/provisional/T_explicit_method_decls_EOVERRIDEMETH.dml new file mode 100644 index 00000000..ddb40e21 --- /dev/null +++ b/test/1.4/provisional/T_explicit_method_decls_EOVERRIDEMETH.dml @@ -0,0 +1,72 @@ +/* + © 2024 Intel Corporation + SPDX-License-Identifier: MPL-2.0 +*/ + +dml 1.4; + +/// ERROR ENOVERRIDEMETH +provisional explicit_method_decls; + +device test; + +/// ERROR ENOVERRIDEMETH +method m0() {} + +method m1a(); +// no error +method m1a() {} + +/// ERROR EOVERRIDEMETH +method m1b(); +/// ERROR EOVERRIDEMETH +method m1b() :{} + +method m2a(); +// no error +method m2a() default {} +/// ERROR EOVERRIDEMETH +method m2b() :default {} +/// ERROR EOVERRIDEMETH +method m2b(); + +template t { + method m3a() :default {} + method m3b() :default {} + method m4a() :default {} + /// ERROR EOVERRIDEMETH + method m4b() :default {} + /// ERROR ENOVERRIDEMETH + method m5() default {} + shared method m6a(); + /// ERROR EOVERRIDEMETH + shared method m6b(); + method m7a(); + /// ERROR EOVERRIDEMETH + method m7b(); + shared method m8a() :default {} + /// ERROR EOVERRIDEMETH + shared method m8b() :default {} + shared method m9() :{} + method m10() :{} +} + +group g is t { + method m3a() {} + /// ERROR EOVERRIDEMETH + method m3b() :{} + method m4a() default {} + /// ERROR EOVERRIDEMETH + method m4b() :default {} + method m6a() {} + /// ERROR EOVERRIDEMETH + method m6b() :{} + method m7a() {} + /// ERROR EOVERRIDEMETH + method m7b() :{} + method m8a() default {} + /// ERROR EOVERRIDEMETH + method m8b() :default {} + // no error + method m10(); +} diff --git a/test/1.4/provisional/T_explicit_param_decls_EOVERRIDE.dml b/test/1.4/provisional/T_explicit_param_decls_EOVERRIDEPARAM.dml similarity index 66% rename from test/1.4/provisional/T_explicit_param_decls_EOVERRIDE.dml rename to test/1.4/provisional/T_explicit_param_decls_EOVERRIDEPARAM.dml index 423c4f8a..77cf13ec 100644 --- a/test/1.4/provisional/T_explicit_param_decls_EOVERRIDE.dml +++ b/test/1.4/provisional/T_explicit_param_decls_EOVERRIDEPARAM.dml @@ -5,47 +5,47 @@ dml 1.4; -/// ERROR ENOVERRIDE +/// ERROR ENOVERRIDEPARAM provisional explicit_param_decls; device test; -/// ERROR ENOVERRIDE +/// ERROR ENOVERRIDEPARAM param p0 = 3; param p1a; // no error param p1a = 3; -/// ERROR EOVERRIDE +/// ERROR EOVERRIDEPARAM param p1b; -/// ERROR EOVERRIDE +/// ERROR EOVERRIDEPARAM param p1b := 3; param p2a; // no error param p2a default 3; -/// ERROR EOVERRIDE +/// ERROR EOVERRIDEPARAM param p2b :default 3; -/// ERROR EOVERRIDE +/// ERROR EOVERRIDEPARAM param p2b; template t { param p3a :default 3; param p3b :default 3; param p4a :default 4; - /// ERROR EOVERRIDE + /// ERROR EOVERRIDEPARAM param p4b :default 4; - /// ERROR ENOVERRIDE + /// ERROR ENOVERRIDEPARAM param p5 default 5; param p6a: int; - /// ERROR EOVERRIDE + /// ERROR EOVERRIDEPARAM param p6b: int; param p7a; - /// ERROR EOVERRIDE + /// ERROR EOVERRIDEPARAM param p7b; param p8a: int default 6; - /// ERROR EOVERRIDE + /// ERROR EOVERRIDEPARAM param p8b: int default 6; param p9: int = 7; param p10 := 10; @@ -53,19 +53,19 @@ template t { group g is t { param p3a = 33; - /// ERROR EOVERRIDE + /// ERROR EOVERRIDEPARAM param p3b := 33; param p4a default 44; - /// ERROR EOVERRIDE + /// ERROR EOVERRIDEPARAM param p4b :default 44; param p6a = 66; - /// ERROR EOVERRIDE + /// ERROR EOVERRIDEPARAM param p6b := 66; param p7a = 77; - /// ERROR EOVERRIDE + /// ERROR EOVERRIDEPARAM param p7b := 77; param p8a default 88; - /// ERROR EOVERRIDE + /// ERROR EOVERRIDEPARAM param p8b :default 88; // no error param p10;