bfa8be887a
Change-Id: I09d01a21f8b227d46ddafedea223b1c765a1d29c Reviewed-on: https://dart-review.googlesource.com/56981 Reviewed-by: Leaf Petersen <leafp@google.com>
252 lines
12 KiB
Markdown
252 lines
12 KiB
Markdown
# Feature: Generic Method Syntax
|
|
|
|
**Author**: eernst@
|
|
|
|
**Status**: Background material.
|
|
The normative text on this topic is part of the language specification as of
|
|
[`673d5f0`](https://github.com/dart-lang/sdk/commit/673d5f0a665085153d25f8c39495eacdb010ca64).
|
|
|
|
**This document** is an informal specification of the support in Dart 1.x
|
|
for generic methods and functions which includes syntax and name
|
|
resolution, but not reification of type arguments.
|
|
|
|
The **motivation for** having this **feature** is that it enables partial
|
|
support for generic methods and functions, thus providing a bridge between
|
|
not having generic methods and having full support for generic methods. In
|
|
particular, code declaring and using generic methods may be type checked and
|
|
compiled in strong mode, and the same code will now be acceptable in
|
|
standard (non-strong) mode as well. The semantics is different in certain
|
|
cases, but standard mode analysis will emit diagnostic messages (e.g.,
|
|
errors) for that.
|
|
|
|
In this document, the word **routine** will be used when referring to
|
|
an entity which can be a non-operator method declaration, a top level
|
|
function declaration, a local function declaration, or a function literal
|
|
expression. Depending on the context, the word routine may also denote the
|
|
semantic entity associated with such a declaration, e.g., a closure
|
|
corresponding to a function literal.
|
|
|
|
With **this feature** it is possible to compile code where generic methods
|
|
and functions are declared, implemented, and invoked. The runtime semantics
|
|
does not include reification of type arguments. Usages of the runtime
|
|
value of a routine type parameter is a runtime error or yields `dynamic`,
|
|
depending on the context. No type checking takes place at usages of a method
|
|
or function type parameter in the body, and no type checking regarding
|
|
explicitly specified or omitted type arguments takes place at call sites.
|
|
|
|
In short, generic methods and functions are supported syntactically, and the
|
|
runtime semantics prevents dynamic usages of the type argument values, but
|
|
it allows all usages where that dynamic value is not required. For instance,
|
|
a generic routine type parameter, `T`, cannot be used in an expression like
|
|
`x is T`, but it can be used as a type annotation. In a context where other
|
|
tools may perform type checking, this allows for a similar level of
|
|
expressive power as do language designs where type arguments are erased at
|
|
compile time.
|
|
|
|
The **motivation for** this **document** is that it serves as an informal
|
|
specification for the implementation of support for the generic method
|
|
syntax feature in all Dart tools.
|
|
|
|
## Syntax
|
|
|
|
The syntactic elements which are added or modified in order to support this
|
|
feature are as follows, based on grammar rules given in the Dart Language
|
|
Specification (Aug 19, 2015).
|
|
|
|
```
|
|
formalParameterPart:
|
|
typeParameters? formalParameterList
|
|
functionSignature:
|
|
metadata returnType? identifier formalParameterPart
|
|
typeParameter:
|
|
metadata identifier ('extends' type)?
|
|
functionExpression:
|
|
formalParameterPart functionBody
|
|
fieldFormalParameter:
|
|
metadata finalConstVarOrType? 'this' '.' identifier
|
|
formalParameterPart?
|
|
argumentPart:
|
|
typeArguments? arguments
|
|
selector:
|
|
assignableSelector | argumentPart
|
|
assignableExpression:
|
|
primary (argumentPart* assignableSelector)+ |
|
|
'super' unconditionalAssignableSelector |
|
|
identifier
|
|
cascadeSection:
|
|
'..' (cascadeSelector argumentPart*)
|
|
(assignableSelector argumentPart*)*
|
|
(assignmentOperator expressionWithoutCascade)?
|
|
```
|
|
|
|
In a [draft specification](https://codereview.chromium.org/1177073002) of
|
|
generic methods from June 2015, the number of grammar changes is
|
|
significantly higher, but that form can be obtained via renaming.
|
|
|
|
This extension to the grammar gives rise to an **ambiguity** where the
|
|
same tokens may be angle brackets of a type argument list as well as
|
|
relational operators. For instance, `foo(a<b,c>(d))`[^1] may be parsed as
|
|
a `postfixExpression` on the form `primary arguments` where the arguments
|
|
are two relational expressions (`a<b` and `c>(d)`), and it may also be
|
|
parsed such that there is a single argument which is an invocation of a
|
|
generic function (`a<b,c>(d)`). The ambiguity is resolved in **favor** of
|
|
the latter.
|
|
|
|
*This is a breaking change, because existing code could include
|
|
expressions like `foo(a < b, c > (d))` where `foo` receives two
|
|
arguments. That expression will now be parsed as an invocation of `foo`
|
|
with one argument. It is unlikely that this will introduce bugs silently,
|
|
because the new parsing is likely to incur diagnostic messages at
|
|
compile-time.*
|
|
|
|
We chose to favor the generic function invocation over the
|
|
relational expression because it is considered to be a rare exception that
|
|
this ambiguity arises: It requires a balanced set of angle brackets followed
|
|
by a left parenthesis, which is already an unusual form. On top of that, the
|
|
style guide recommendation to use named parameters for boolean arguments
|
|
helps making this situation even less common.
|
|
|
|
If it does occur then there is an easy **workaround**: an extra set of
|
|
parentheses (as in `foo(a<b,(2>(d)))`) will resolve the ambiguity in the
|
|
direction of relational expressions; or we might simply be able to remove
|
|
the parentheses around the last expression (as in `foo(a<b,2>d)`), which
|
|
will also eliminate the ambiguity.
|
|
|
|
_It should be noted that parsing techniques like recursive descent seem to
|
|
conflict with this approach to disambiguation: Determining whether the
|
|
remaining input starts with a balanced expression on the form `<` .. `>`
|
|
seems to imply a need for unbounded lookahead. However, if some type of
|
|
parsing is used where bracket tokens are matched up during lexical
|
|
analysis then it takes only a simple O(1) operation in the parser to
|
|
perform a check which will very frequently resolve the ambiguity._
|
|
|
|
## Scope of the Mechanism
|
|
|
|
With the syntax in place, it is obvious that certain potential extensions
|
|
have **not** been **included**.
|
|
|
|
For instance, constructors, setters, getters, and operators cannot be
|
|
declared as generic: The syntax for passing actual type arguments at
|
|
invocation sites for setters, getters, and operators is likely to be
|
|
unwieldy and confusing, and for constructors there is a need to find
|
|
a way to distinguish between type arguments for the new instance and
|
|
type arguments for the constructor itself. However, there are plans
|
|
to add support for generic constructors.
|
|
|
|
This informal specification specifies a dynamic semantics where the values
|
|
of **actual type arguments are not reified** at run time. A future
|
|
extension of this mechanism may add this reification, such that dynamic
|
|
type tests and type casts involving routine type variables will be
|
|
supported.
|
|
|
|
## Resolution and Type Checking
|
|
|
|
In order to be useful, the support for generic methods and functions must be
|
|
sufficiently complete and consistent to **avoid spurious** diagnostic
|
|
**messages**. In particular, even though no regular type checks take place
|
|
at usages of routine type parameters in the body where they are in scope,
|
|
those type parameters should be resolved. If they had been ignored then any
|
|
usage of a routine type parameter `X` would give rise to a `Cannot resolve
|
|
type X` error message, or the usage might resolve to other declarations of
|
|
`X` in enclosing scopes such as a class type parameter, both of which is
|
|
unacceptable.
|
|
|
|
In `dart2js` resolution, the desired behavior has been achieved by adding a
|
|
new type parameter **scope** and putting the type parameters into that
|
|
scope, giving each of them the bound `dynamic`. The type parameter scope is
|
|
the current scope during resolution of the routine signature and the type
|
|
parameter bounds, it encloses the formal parameter scope of the routine, and
|
|
the formal parameter scope in turn encloses the body scope.
|
|
|
|
This implies that every usage of a routine type parameter is treated during
|
|
**type checking** as if it had been an alias for the type dynamic.
|
|
|
|
Static checks for **invocations** of methods or functions where type
|
|
arguments are passed are omitted entirely: The type arguments are parsed,
|
|
but no checks are applied to certify that the given routine accepts type
|
|
arguments, and no checks are applied for bound violations. Similarly, no
|
|
checks are performed for invocations where no type arguments are passed,
|
|
whether or not the given routine is statically known to accept type
|
|
arguments.
|
|
|
|
Certain usages of a routine type parameter `X` give rise to **errors**: It
|
|
is a compile-time error if `X` is used as a type literal expression (e.g.,
|
|
`foo(X)`), or in an expression on the form `e is X` or `e is! X`, or in a
|
|
try/catch statement like `.. on T catch ..`.
|
|
|
|
It could be argued that it should be a warning or an error if a routine type
|
|
parameter `X` is used in an expression on the form `e as X`. The blind
|
|
success of this test at runtime may introduce bugs into correct programs in
|
|
situations where the type constraint is violated; in particular, this could
|
|
cause "wrong" objects to propagate through local variables and parameters
|
|
and even into data structures (say, when a `List<T>` is actually a
|
|
`List<dynamic>`, because `T` is not present at runtime when the list is
|
|
created). However, considering that these type constraint violations are
|
|
expected to be rare, and considering that it is common to require that
|
|
programs compile without warnings, we have chosen to omit this warning. A
|
|
tool is still free to emit a hint, or in some other way indicate that there
|
|
is an issue.
|
|
|
|
## Dynamic semantics
|
|
|
|
If a routine invocation specifies actual type arguments, e.g., `int` in the
|
|
**invocation** `f<int>(42)`, those type arguments will not be evaluated at
|
|
runtime, and they will not be passed to the routine in the
|
|
invocation. Similarly, no type arguments are ever passed to a generic
|
|
routine due to call-site inference. This corresponds to the fact that the
|
|
type arguments have no runtime representation.
|
|
|
|
When the body of a generic **routine** is **executed**, usages of the formal
|
|
type parameters will either result in a run-time error, or they will yield
|
|
the type dynamic, following the treatment of malformed types in
|
|
Dart. There are the following cases:
|
|
|
|
When `X` is a routine type parameter, the evaluation of `e is X`, `e is! X`,
|
|
and `X` used as an expression proceeds as if `X` had been a malformed type,
|
|
producing a dynamic error; the evaluation of `e as X` has the same outcome
|
|
as the evaluation of `e`.
|
|
|
|
Note that the forms containing `is` are compile-time errors, which means
|
|
that compilers may reject the program or offer ways to compile the program
|
|
with a different runtime semantics for these expressions. The rationale for
|
|
`dart2js` allowing the construct and compiling it to a run time error is
|
|
that (1) this allows more programs using generic methods to be compiled,
|
|
and (2) an `is` expression that blindly returns `true` every time (or
|
|
`false` every time) may silently introduce a bug into an otherwise correct
|
|
program, so the expression must fail if it is ever evaluated.
|
|
|
|
When `X` is a routine type parameter which is passed as a type argument to a
|
|
generic class instantiation `G`, it is again treated like a malformed type,
|
|
i.e., it is considered to denote the type dynamic.
|
|
|
|
This may be surprising, so let us consider a couple of examples: When `X` is
|
|
a routine type parameter, `42 is X` raises a dynamic error, `<int>[42] is
|
|
List<X>` yields the value `true`, and `42 as X` yields `42`, no matter
|
|
whether the syntax for the invocation of the routine included an actual type
|
|
argument, and, if so, no matter which value the actual type argument would
|
|
have had at the invocation.
|
|
|
|
Object construction is similar: When `X` is a routine type parameter which
|
|
is a passed as a type argument in a constructor invocation, the actual
|
|
value of the type type argument will be the type dynamic, as it would have
|
|
been with a malformed type.
|
|
|
|
In **checked mode**, when `X` is a routine type parameter, no checked mode
|
|
checks will ever fail for initialization or assignment to a local variable
|
|
or parameter whose type annotation is `X`, and if the type annotation is a
|
|
generic type `G` that contains `X`, checked mode checks will succeed or
|
|
fail as if `X` had been the type dynamic. Note that this differs from the
|
|
treatment of malformed types.
|
|
|
|
## Changes
|
|
|
|
2017-Jan-04: Changed 'static error' to 'compile-time error', which is the
|
|
phrase that the language specification uses.
|
|
|
|
## Notes
|
|
|
|
[^1]: These expressions violate the common style in Dart with respect to
|
|
spacing and capitalization. That is because the ambiguity implies
|
|
conflicting requirements, and we do not want to bias the appearance in
|
|
one of the two directions.
|