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;