Change-Id: Ieb8e323a37f66713067f8a33a5d3a8596e840458 Reviewed-on: https://dart-review.googlesource.com/14401 Reviewed-by: Lasse R.H. Nielsen <lrn@google.com> Reviewed-by: Bob Nystrom <rnystrom@google.com> Commit-Queue: Erik Ernst <eernst@google.com>
11 KiB
Optional new
Author: eernst@.
Version: 0.4 (2017-10-17)
Status: Under implementation.
This document is an informal specification of the optional new feature.
The feature adds support for omitting the reserved word new in instance
creation expressions.
This feature extends and includes the optional const feature, and it is assumed that the reader knows about optional const. Beyond that, this informal specification is derived from a combined proposal which presents optional new together with several other features.
Motivation
In Dart without optional new, the reserved word new is present in every
expression whose evaluation invokes a constructor (except constant
expressions). These expressions are known as instance creation expressions. If
new is removed from such an instance creation expression, the remaining phrase
is still syntactically correct in almost all cases. The required grammar
update that makes them all syntactically correct is a superset of the one that
is specified for
optional const.
With that grammar update, all instance creation expressions can technically
omit the new because tools (compilers, analyzers) are able to parse these
expressions, and they are able to recognize that they denote instance creations
(rather than, say, static function invocations), because the part before the
left parenthesis is statically known to denote a constructor.
For instance, p.C.foo may resolve statically to a constructor named foo in
a class C imported with prefix p. Similarly, D may resolve to a class, in
which case D(42) is statically known to be a constructor invocation because
the other interpretation is statically known to be incorrect (that is, cf.
section '16.14.3 Unqualified Invocation' in the language specification,
evaluating (D)(42): (D) is an instance of Type which is not a function
type and does not have a method named call).
For human readers, it may be helpful to document that a particular expression
is guaranteed to yield a fresh instance, and this is the most common argument
why new should not be omitted. However, Dart already allows instance
creation expressions to invoke a factory constructor, so Dart developers never
had any firm local guarantees that any particular expression would yield a
fresh object.
Developers may thus prefer to omit new in order to obtain more concise code,
and possibly also in order to achieve greater uniformity among invocations of
constructors and other invocations, e.g., of static or global functions.
With that in mind, this proposal allows instance creation expressions to omit
the new in all cases, but also preserves the permission to include new in
all cases. It is a matter of style to use new in a manner that developers
find helpful.
Syntax
The syntax changes associated with this feature are the following:
postfixExpression ::=
assignableExpression postfixOperator |
constructorInvocation | // NEW
primary selector*
constructorInvocation ::= // NEW
typeName typeArguments '.' identifier arguments
assignableExpression ::=
SUPER unconditionalAssignableSelector |
constructorInvocation (arguments* assignableSelector)+ | // NEW
identifier |
primary (arguments* assignableSelector)+
As mentioned, this grammar update is a superset of the grammar update for optional const.
Static analysis
We specify a type directed source code transformation which eliminates the feature by expressing the same semantics with different syntax. The static analysis proceeds to work on the transformed program.
Similarly to optional const, this means that the feature is "static semantic sugar". We do not specify the dynamic semantics for this feature, because the feature is eliminated in this transformation step.
We need to treat expressions differently in different locations, hence the following definition, which is identical to the one in optional const:
An expression e is said to occur in a constant context,
- if e is an element of a constant list literal, or a key or value of an entry of a constant map literal.
- if e is an actual argument of a constant object expression or of a metadata annotation.
- if e is the initializing expression of a constant variable declaration.
- if e is a switch case expression.
- if e is an immediate subexpression of an expression e1 which occurs in
a constant context, unless e1 is a
throwexpression or a function literal.
This roughly means that everything which is inside a syntactically
constant expression is in a constant context. A throw expression is
currently not allowed in a constant expression, but extensions affecting
that status may be considered. A similar situation arises for function
literals.
We define new/const insertion as the following transformation:
- if the expression e occurs in a constant context, replace e by
conste, - otherwise replace e by
newe.
We define new insertion as the following transformation:
- replace e by
newe.
For the purposes of describing the main transformation we need the following syntactic entity:
assignableExpressionTail ::=
arguments assignableSelector (arguments* assignableSelector)*
We specify the transformation as based on a top-down traversal of an
abstract syntax tree (AST). This means that the program is assumed to be
free of syntax errors, and when the current AST is, e.g., a
postfixExpression, the program as a whole has such a structure that
the current location was parsed as a postfixExpression. This is
different from the situation where we just require a given subsequence of the
tokens of the program allows for such a parsing in isolation. For instance,
an identifier like x parses as an assignableExpression in isolation,
but if it occurs in the context var x = 42; or var y = x; then it
will not be parsed as an assignableExpression, it will be parsed as a
plain identifier which is part of a declaredIdentifier in the first
case, and as a primary which is a postfixExpression, which is a
unaryExpression, etc., in the second case. In short, we are
transforming the AST of the program as a whole, not isolated snippets of
code.
An expression of one of the following forms must be modified in top-down
order to be or contain a constantObjectExpression or newExpression
as described:
With a postfixExpression e,
- if e is of the form
constructorInvocation, i.e.,typeName typeArguments '.' identifier argumentsthen perform new/const insertion on e. - if e is of the form
typeIdentifier argumentswheretypeIdentifierdenotes a class then perform new/const insertion on e. - if e is of the form
identifier1 '.' identifier2 argumentswhereidentifier1denotes a class andidentifier2is the name of a named constructor in that class, oridentifier1denotes a prefix for a library L andidentifier2denotes a class exported by L, perform new/const insertion on e. - if e is of the form
identifier1 '.' typeIdentifier '.' identifier2 argumentswhereidentifier1denotes a library prefix for a library L,typeIdentifierdenotes a class C exported by L, andidentifier2is the name of a named constructor in C, perform new/const insertion on e.
With an assignableExpression e,
- if e is of the form
constructorInvocation assignableExpressionTailthen perform new insertion on the initialconstructorInvocation arguments. - if e is of the form
typeIdentifier assignableExpressionTailwheretypeIdentifierdenotes a class then perform new insertion on the initialtypeIdentifier arguments. - if e is of the form
identifier1 '.' identifier2 assignableExpressionTailwhereidentifier1denotes a class andidentifier2is the name of a named constructor in that class, oridentifier1denotes a prefix for a library L andidentifier2denotes a class exported by L then perform new insertion on the initialidentifier1 '.' identifier2 arguments. - if e is of the form
identifier1 '.' typeIdentifier '.' identifier2 assignableExpressionTailwhereidentifier1denotes a library prefix for a library L,typeIdentifierdenotes a class C exported by L, andidentifier2is the name of a named constructor in C then perform new insertion on the initialidentifier1 '.' typeIdentifier '.' identifier2 arguments.
For a list literal e occurring in a constant context, replace e by
const e. For a map literal e occurring in a constant context,
replace e by const e.
In short, add const in const contexts and otherwise add new. With
assignableExpression we always add new, because such an expression
can never be a subexpression of a correct constant expression. It is easy
to verify that each of the replacements can be derived from
postfixExpression via primary selector* and similarly for
assignableExpression. Hence, the transformation preserves syntactic
correctness.
Dynamic Semantics
There is no dynamic semantics to specify for this feature because it is eliminated by code transformation.
Interplay with optional const
This informal specification includes optional const as well as optional new, that is, if this specification is implemented then optional const may be considered as background material.
Revisions
-
0.4 (2017-10-17) Reverted to use 'immediate subexpression' again, for correctness. Adjusted terminology for consistency. Clarified the semantics of the transformation.
-
0.3 (2017-09-08) Included missing rule for transformation of composite literals (lists and maps). Eliminated the notion of an immediate subexpression, for improved precision.
-
0.2 (2017-07-30) Updated the document to specify the previously missing transformations for
assignableExpression, and to specify a no-magic approach (where noconstis introduced except when forced by the syntactic context). -
0.1 (2017-08-15) Stand-alone informal specification for optional new created, using version 0.8 of the combined proposal optional-new-const.md as the starting point.