Augmentations
July 20, 2026 ยท View on GitHub
Authors: rnystrom@google.com, jakemac@google.com, lrn@google.com, eernst@google.com
Version: 1.44 (see Changelog at end)
Experiment flag: augmentations
Augmentations allow splitting a declaration across multiple locations, both within a single file and across multiple files. They can add new top-level declarations, inject new members into classes, and provide bodies for functions.
Motivation
Dart libraries are the unit of code reuse. When an API is too large to fit into a single file, you can usually split it into multiple libraries and then have one main library export the others. That works well when the functionality in each file is made of separate top-level declarations.
However, sometimes a single declaration is too large to fit comfortably in a file. Dart libraries and even part files are no help there. Because of this, users have asked for something like partial classes in C# (#252 83 ๐, #678 20 ๐). C# also supports splitting the declaration and implementation of methods into separate files.
Generated code
Size isn't the only reason to split a library into multiple files. Code generation is common in Dart. AngularDart compiles HTML templates to Dart files. The freezed and built_value packages generate Dart code to implement immutable data structures.
In cases like this, it's important to have the hand-authored and machine-generated code in separate files so that the code generator doesn't inadvertently erase a user's code. AngularDart generates a separate library for the component. The freezed and built_value packages generate part files.
Note that the relationship between the hand-authored and generated code can go in both directions:
-
Often, a user hand-authors a skeleton declaration and then a code generator fills in implementation and adds capabilities to it. That's how freezed and built_value work.
-
Other times, a code generator produces code with default basic behavior that a human author then wants to tweak or refine. You see this sometimes with FFI where a code generator provides a default API to some external system but where you want to layer on hand-authored code to provide a more natural Dart-like experience.
Having a mixture of hand-authored and generated code works well when the generated code consists of completely separate declarations from the hand-authored code. But if a code generator wants to, say, add a method to a hand-authored class, or an author wants to add a method to a generated class, then the language is of little help. This proposal addresses that limitation by adding augmentations.
Augmentation declarations
A declaration marked with the modifier augment creates an augmentation
declaration (or just "augmentation"). In contrast, an unmarked declaration is
an introductory declaration.
An augmentation declaration doesn't create a new entity in the program. Instead,
it adds to some existing introductory declaration with the same name.
Augmentation declarations can affect types, functions, members, and almost any
other kind of declaration in Dart. They can add members to classes, append
values to enums, append types to with or implements clauses, fill in missing
function bodies, etc.
An augmentation can be in the same file as the introductory declaration it applies to, or in separate part files, but they must both be in the same library. Augmentations allow authoring a library in multiple separate pieces or files (some of which are likely generated) in a flexible manner, but they can't inject new behavior into libraries outside of the library author's control.
Design principle
When designing this feature, a fundamental question is how much power to give augmentations. Giving them more ability to change the introductory declaration makes them more powerful and expressive. But the more an augmentation can change, the less a reader can correctly assume from reading only the introductory declaration. That can make code using augmentations harder to understand and work with.
To balance those, the general principle of this feature is that augmentations can add new capabilities to the declaration and fill in implementation, but generally can't change any property a reader knows to be true from the introductory declaration or any prior augmentation. In other words, if a program would work without the augmentation being applied, it should generally still work after the augmentation is applied. Note that this a design principle and not a strict guarantee.
For example, if the introductory declaration of a function takes an int
parameter and returns a String, then any augmentation must also take an int
and return a String. That way a reader knows how to call the function and what
they'll get back without having to read the augmentations.
Likewise, if an introductory class declaration has a generative constructor, then the reader assumes they can inherit from that class and call that as a superclass constructor. Therefore, an augmentation of the class is prohibited from changing the constructor to a factory.
Syntax
The syntax changes are simple but fairly extensive and touch several parts of the grammar so are broken out into separate sections.
Top-level augmentations and incomplete top-level members
We allow an augment modifier before most top-level declarations.
Also, we allow incomplete declarations at the top level. This reuses the same
syntax used inside a class to declare abstract variables, methods, getters,
setters, and operators. For callable members, that means the body is ;. For
variable declarations, that means using abstract. Example:
abstract int x; // Incomplete top-level variable.
int get y; // Incomplete top-level getter.
set z(int value); // Incomplete top-level setter.
The new top-level grammar is:
topLevelDeclaration ::= classDeclaration
| mixinDeclaration
| extensionTypeDeclaration
| extensionDeclaration
| enumType
| typeAlias
| 'augment'? 'external' functionSignature ';'
| 'augment'? 'external' getterSignature ';'
| 'augment'? 'external' setterSignature ';'
| 'augment'? 'external' finalVarOrType identifierList ';'
| 'augment'? 'abstract' finalVarOrType identifierList ';'
| 'augment'? getterSignature (functionBody | ';')
| 'augment'? setterSignature (functionBody | ';')
| 'augment'? functionSignature (functionBody | ';')
| 'augment'? ('final' | 'const') type? staticFinalDeclarationList ';'
| 'augment'? 'late' 'final' type? initializedIdentifierList ';'
| 'augment'? 'late'? varOrType initializedIdentifierList ';'
Class-like declarations
We allow augment before class, extension type, and mixin declarations. (Enums
and extensions are discussed in subsequent sections.)
classDeclaration ::=
'augment'? (classModifiers | mixinClassModifiers)
'class' classNameMaybePrimary superclass? interfaces?
memberedDeclarationBody
| classModifiers 'mixin'? 'class' mixinApplicationClass
primaryConstructor ::= // From primary constructors specification.
'const'? typeWithParameters ('.' identifierOrNew)?
declaringParameterList;
classNameMaybePrimary ::= // From primary constructors specification.
primaryConstructor
| typeWithParameters;
mixinDeclaration ::=
'base'? 'mixin' typeIdentifier typeParameters?
('on' typeNotVoidNotFunctionList)? interfaces? memberedDeclarationBody
| 'augment' 'base'? 'mixin' typeIdentifier typeParameters?
interfaces? memberedDeclarationBody
extensionTypeDeclaration ::= // From primary constructors specification
'extension' 'type' primaryConstructor interfaces?
memberedDeclarationBody
| 'augment' 'extension' 'type' typeIdentifier
typeParameters? interfaces?
memberedDeclarationBody
memberedDeclarationBody ::=
'{' memberDeclarations '}'
| ';'
memberDeclarations ::= (metadata 'augment'? memberDeclaration)*
primaryConstructorBodySignature ::= // From primary constructors specification
'augment'? 'this' initializers?
memberDeclaration ::= declaration
| methodSignature functionBody
declaration ::=
'external'? factoryConstructorSignature ';'
| 'external' constantConstructorSignature ';'
| 'external' constructorSignature ';'
| 'external'? 'static'? getterSignature ';'
| 'external'? 'static'? setterSignature ';'
| 'external'? 'static'? functionSignature ';'
| 'external'? operatorSignature ';'
| 'external' ('static'? finalVarOrType | 'covariant' varOrType)
identifierList ';'
| 'abstract' (finalVarOrType | 'covariant' varOrType) identifierList ';'
| 'static' 'abstract' finalVarOrType identifierList ';'
| 'static' 'const' type? staticFinalDeclarationList ';'
| 'static' 'final' type? staticFinalDeclarationList ';'
| 'static' 'late' 'final' type? initializedIdentifierList ';'
| 'static' 'late'? varOrType initializedIdentifierList ';'
| 'covariant' 'late' 'final' type? identifierList ';'
| 'covariant' 'late'? varOrType initializedIdentifierList ';'
| 'late'? 'final' type? initializedIdentifierList ';'
| 'late'? varOrType initializedIdentifierList ';'
| redirectingFactoryConstructorSignature ';'
| constantConstructorSignature (redirection | initializers)? ';'
| constructorSignature (redirection | initializers)? ';'
| primaryConstructorBodySignature ';'
As introduced by the primary constructor feature, introductory extension type
declarations use the primary constructor syntax, but must have precisely
one parameter. That parameter must be declaring and final, but it can omit
the final keyword. Augmenting extension type declarations cannot write
primary constructors.
As with top-level declarations, we also reuse the abstract member syntax with a
static modifier to allow declaring incomplete static fields, methods, getters,
setters, and operators. Example:
class C {
static abstract int x; // Incomplete static variable (getter and setter).
static int get y; // Incomplete static getter.
static set z(int value); // Incomplete static setter.
}
Note that the grammar for putting augment before an extension type declaration
doesn't allow also specifying a representation field. This is by design. An
extension type augmentation always inherits the representation field of the
introductory declaration and can't specify it.
Likewise, the grammar for an augmenting mixin declaration does not allow
specifying an on clause. Only the introductory declaration permits that. We
could relax this restriction if compelling use cases arise.
Enums
For enum declarations, in addition to the augment modifier, we allow declaring
an enum (or augmentation of one) with no values. This is useful if the
introductory declaration wants to let the augmentation fill in all values, or if
the augmentation wants to add members but no values.
When there are no values, the enum still requires a leading ; before the first
member to avoid ambiguity.
enumType ::=
'augment'? 'enum' classNameMaybePrimary mixins? interfaces? enumBody
enumBody ::=
'{' (enumEntry (',' enumEntry)* (',')?)? (';' memberDeclarations)? '}'
| ';'
Note that an enum can also have neither values nor members and both {} and
{;} are valid.
Extensions
Extension declarations can be augmented:
extensionDeclaration ::=
'extension' typeIdentifierNotType? typeParameters? 'on' type
memberedDeclarationBody
| 'augment' 'extension' typeIdentifierNotType typeParameters?
memberedDeclarationBody
Note that only extensions with names allow a leading augment. Since
augmentations are matched with their introductory declaration by name, unnamed
extensions can't be augmented. Doing so wouldn't accomplish anything anyway.
Just make two separate unnamed extensions.
Also note that an augmentation of an extension can't specify an on clause. It
always uses the same on clause as the introductory declaration.
Primary constructors
A class, enum or extension type declarationscan use the primary
constructor syntax for declaring an initializing (non-redirecting
generative) constructor.
Instance variable initializer expressions
If a class or enum has a primary constructor, then the current scope of
the initializer expression of a non-late instance variable is the
primary initializer scope, rather than the body scope of the
surrounding class or enum declaration.
It's a compile-time error if a non-late instance variable initializer
expressions refers to a variable introduced by the constructor's
initializer list scope, and the surrounding class or enum declaration does
not have a primary constructor declaration which declares the
corresponding parameter's name.
A primary constructor must declare all parameters, but it can omit declaring
a positional parameter's name by using _ instead of the name.
If it does so, that parameter's name cannot be used by instance variable
initializers of that class or enum declaration.
The scope exists and is used whether this particular declaration writes a primary constructor or not, but for readability reasons, code may only refer to variables of that scope, if there appears to be a declaration of the variable's name in the surrounding code.
Static semantics
Augmentation context
Prior to this proposal, an entity like a class or function is introduced by a single syntactic declaration. With augmentations, an entity may be composed out of multiple declarations, the introductory one and any number of augmentations. We define a notion of a augmentation context to help us talk about the location where we need to look to collect all of the declarations that define some entity.
-
The augmentation context of a top-level declaration is the library and its associated tree of part files.
-
The augmentation context of a member declaration in a type or extension declaration named N is the set of type declarations (introductory and augmenting) named N in the enclosing set of Dart files.
Note that augmentation context is only defined for the kinds of declarations that can be augmented. We don't define an augmentation context for, say, local variable declarations, because those aren't subject to augmentation.
Scoping
The static and instance member namespaces for an augmented type or extension declaration include the declarations of all members in the introductory and augmenting declarations. Identifiers in the bodies of members are resolved against that complete merged namespace. In other words, augmentations are applied before identifiers inside members are resolved.
It is already a compile-time error for multiple declarations to have the same name in the same scope. This error is checked after part files and augmentations have been applied. In other words, it's an error to declare the same top-level name in a library and a part, the same top-level name in two parts, the same static or instance name inside an introductory declaration and an augmentation on that declaration, or the same static or instance name inside two augmentations of the same declaration.
For example:
// Library "main.dart":
part 'other.dart';
const name = 'top level';
class C {
test() {
print(name);
}
}
main() {
C().test();
}
// Part file "other.dart":
part of 'main.dart';
augment class C {
String get name => 'member';
}
This program prints "member", not "top level". When name is resolved inside
test() it walks up to the instance member scope for C. Since that scope
contains the merged members of all applied augmentations, it finds the name
getter added by the augmentation and uses that instead of continuing and
finding the top level name.
You can visualize the namespace nesting sort of like this:
main.dart : other.dart
:
.-----------------------------------------------.
| main.dart imports: |
'-----------------------------------------------'
^ : ^
| : |
| : .---------------------.
| : | other.dart imports: |
| : '---------------------'
| : ^
| : |
.-----------------------------------------------.
| top-level declarations: |
| const name |
| class C |
'-----------------------------------------------'
^ : ^
| : |
.-----------------------------------------------.
| class C instance members: |
| test() |
| name |
'-----------------------------------------------'
^ : ^
| : |
.---------------------. : .---------------------.
| test() body | : | name body |
'---------------------' : '---------------------'
The main library file has an import scope which is inherited by all of the part files. Each part file then has its own import scope (which are inherited by that part file's own further part files).
The main library and all part files share and contribute to a single top-level declaration scope. Each type or extension declaration in there has a scope shared across introductory and augmenting declarations of that type or extension.
Then inside those types and extensions are scopes for the member bodies. Each member has its own scope. When resolving an identifier inside a member, we look in the member body, then up through the scopes whose declarations are merged from all of the augmentations and parts, then through the import scopes which may be different for each part file, and finally to the import scope of the main library.
Type annotation inheritance
An augmenting declaration may omit type annotations for a return type, variable
type, parameter type, or type parameter bound. In the last case, that includes
omitting the extends keyword. For a variable, a var keyword replaces the
type if the variable isn't final.
If a type annotation or type parameter bound is omitted in the augmenting declaration, it is inferred to be the same as the corresponding type annotation or type parameter bound in the declaration being augmented.
If the type annotation or type parameter bound is not omitted, then it's a compile-time error if the type denoted by the augmenting declaration is not the same type as the type in the corresponding declaration being augmented.
In short, an augmenting declaration can omit type annotations, but if it doesn't, it must repeat the type from the augmented definition.
Inheriting combined getter setter signatures
An instance getter and instance setter can be augmented with an abstract variable declaration because the latter is syntactic sugar for an abstract getter and setter declaration. This leads to a tricky edge case where the augmenting abstract variable may want to inherit a type but the getter and setter it inherits from have different types:
class C {
int get x => 1;
set x(String value) {}
@metadataToAdd
augment abstract var x; // What type is inherited here?
}
It's a compile-time error if an abstract variable augments a getter and setter that don't have a combined signature.
Applying augmentations
An augmentation declaration D is a declaration marked with the built-in
identifier augment. We add augment as a built-in identifier as a language
versioned change, to avoid breaking pre-feature code.
D augments a declaration I with the same name and in the same augmentation context as D. There may be multiple augmentations in the augmentation context of D. More precisely, I is the declaration before D and after every other declaration before D. (See "Application order" for the definition of before and after.)
It's a compile-time error if there is no matching declaration I. In other
words, it's an error to have a declaration marked augment with no declaration
to apply it to.
We say that I is the declaration which is augmented by D.
In other words, take all of the declarations with the same name in some
augmentation context, order them according to the "after" relation, and each
augments the result of all the prior augmentations applied to the original
declaration. The first one must not be marked augment and all the subsequent
ones must be.
An augmentation declaration does not introduce a new name into the surrounding scope. We could say that it attaches itself to the existing name.
It's a compile-time error if an augmentation doesn't have the same kind as
the introductory declaration. For example, augmenting a class with a mixin,
an enum with a function, a method with a getter, etc.
The exception is that a variable declaration (introductory or augmenting) is treated as a getter declaration (and a setter declaration if non-final) for purposes of augmentation. These implicit declarations can augment and be augmented by other explicit getter and setter declarations. In other words, variables are never augmented or augmenting, only the getters and possibly setters that they induce are.
Complete and incomplete declarations
Augmentations aren't allowed to replace code, so they mostly add entirely new declarations to the surrounding type. However, function and constructor augmentations can fill in a body for an augmented declaration that lacks one.
More precisely, a function or constructor declaration (introductory or augmenting) is incomplete if all of:
-
It has no body. That means no
{ ... }or=> ...;but only;. -
The function is not marked
external. Anexternalfunction is considered to have a body, just not one that is visible as Dart code. -
There is no redirection, initializer list, initializing formals, field parameters, or super parameters. Obviously, this only applies to constructor declarations.
If a declaration is not incomplete then it is complete.
It's a compile-time error if an augmentation is complete and any declaration before it in the augmentation chain is also complete. In other words, once a declaration has acquired a body, no augmentation can replace it with another.
It is allowed to augment a complete declaration as long as the augmentation itself is incomplete. This can be useful for an augmentation to add metadata.
Examples:
a() {}
augment a() {} // Error.
b();
augment b() {} // OK.
c() {}
@meta
augment c(); // OK.
d() {}
augment d(); // OK.
augment d() {} // Error.
Note that the initializer list and body are not treated separately. If a
constructor declaration has an initializer list and ; body, it is still
considered complete. Likewise, a constructor with no initializer list but a
non-; body is complete. Thus a constructor can't acquire an initializer
list in one declaration and a constructor body in another. For example:
class C {
C() : assert(true);
}
augment class C {
augment C() { body; } // Error. C() is already complete.
}
Application order
The same declaration can be augmented multiple times by separate augmentation declarations. This occurs in the situation where an augmentation declaration has an augmented declaration which is itself an augmentation declaration, and so on, until an introductory declaration is reached.
In some cases (enum values, with clauses, etc.), the order that augmentations
are applied is user-visible, so must be specified. Within a single file, the
obvious order for applying augmentations is based on which appears first.
Expanded part files make that more complex: there may be
augmentations of the same declaration scattered across an entire tree of part
files.
Some terminology:
-
The source code of a declaration is the span of characters from the first non-whitespace character of the declaration to the last non-whitespace character of the declaration, as matched by the relevant grammar production. Metadata is not considered part of the declaration, it precedes the declaration in the source.
-
A syntactic declaration, D, occurs in a Dart file F if D's source code occurs in that file.
-
A Dart file, F, includes a part file P if the Dart file F has a
partdirective whose URI denotes P. -
A Dart file, F, contains (aka. transitively includes) a part file P if F (directly) includes P, or if F includes a part file Q and Q contains P.
-
A Dart file contains a declaration D if the declaration D occurs in the file F itself, or if D occurs in a file P and F contains P.
For any two distinct syntactic declarations A and B:
-
If A and B occur in the same file:
-
If A's position is before B's position in the source code of the file, then A is before B.
The position of a declaration is defined by the first of these rules that apply:
- If the declaration is a constructor declaration with no
class name, its position is the start position of the
neworfactorykeyword in the source. - If the declaration contains an identifier for the declared name,
or a qualified identifier for a constructor name,
the position is the start position of that identifier
in the source.
(Includes an unnamed local variable declared with a
_as identifier. A primary constructor declaration's position is that of the class name.) - If the declaration is an unnamed
extensiondeclaration, its position is the position of theonkeyword.
You cannot augment an unnamed
extensiondeclaration, because an augmentation needs to repeat the same name. - If the declaration is a constructor declaration with no
class name, its position is the start position of the
-
If B's position is before A's position, then B is before A.
-
If A's and B's position are the same, but they are not the same declaration, then A and B must be a primary constructor declaration and its surrounding type-introducing declaration. In that case, the type introducing declaration is before the primary constructor declaration. (The same effect can be achieved by making the position of a primary constructor be at the end of the class name identifier instead of at the start.)
-
-
Else if the file where A occurs contains the file where B occurs then A is before B.
-
Else if the file where B occurs contains the file where A occurs then B is before A.
In other words, if there is a
partchain from the file where one declaration occurs to the file where the other occurs, then the outer one comes first. -
Otherwise, A and B are in sibling branches of the part tree:
-
Let F be the unique Dart file which contains both A and B, but which does not include a part file which also contains both A and B. Such a file must exist for any two declarations because a declaration only exists in one file, and the part files form a tree. Neither A nor B will occur directly in F because if they did, then the previous clauses would have handled it.*
-
If the
partdirective in F including the file that contains A is syntactically before thepartdirective in F including the file that contains B in source order, then A is before B. -
Otherwise B is before A.
In other words, augmentations in sibling branches are ordered by the
partdirective order in the file where the branches split off. -
We say that B is after A if and only if A is before B.
In short, declarations are ordered by a pre-order depth-first traversal of the
file tree, visiting declarations of a file in source order, and then recursing
into part directives in source order.
Augmentations are applied in least to greatest order using the after relation.
For example:
// main.dart:
part 'a.dart';
part 'b.dart';
enum E { v1 }
augment enum E { v2 }
augment enum E { v3 }
// a.dart:
augment enum E { v4 }
// b.dart:
augment enum E { v5 }
The resulting enum has values v1, v2, v3, v4, and v5, in that order.
A consequence of application order is that an augmentation in one part file can
augment an introductory declaration in a sibling part file. This may be
confusing since the augmentation affects a declaration that can't be found
anywhere in its part of chain.
However, restricting augmentations to only apply to declarations above (not before) the augmentation would complicate code generators and potentially require them to generate one part file per input part file instead of one part file for the entire library. To avoid that, the rule for where the introductory declaration can be found is intentionally loose so that a code generator can produce one part file which is appended to the main library and which is able to reliably augment any declaration in the entire library regardless of what part file they are introduced in.
Augmenting class-like declarations
A class, enum, extension, extension type, mixin, or mixin class declaration
can be marked with an augment modifier:
augment class SomeClass {
// ...
}
Mixin application classes can't be augmented:
class C = S with M;
augment class C = S with N; // Error.
A class, enum, extension type, mixin, or mixin class augmentation may specify
extends, implements and with clauses (when otherwise supported). The types
in these clauses are appended to the introductory declarations' clauses of the
same kind, and if that clause did not exist previously, then it is added with
the new types.
Example:
class C with M1 implements I1 {}
augment class C with M2 implements I2 {}
// Is equivalent to:
class C with M1, M2 implements I1, I2 {}
Instance or static members defined in the body of the augmenting type, including enum values, are added to the instance or static namespace of the corresponding type in the introductory declaration. In other words, the augmentation can add new members to an existing type.
An instance or static member inside a class-like declaration may itself be an augmentation. In that case, it augments the corresponding member (the introducing member with the same name) in the same augmentation context, according to the rules in the following subsections.
It's a compile-time error if:
-
An augmenting class declaration has an
extendsclause and any prior declaration for the same class also has anextendsclause. -
The augmenting declaration and augmented declaration do not have all the same modifiers:
abstract,base,final,interface,sealedandmixinforclassdeclarations, andbaseformixindeclarations.This is not a technical requirement, but follows our design principle that what is known from reading the introductory declaration will still be true after augmentation.
-
The type parameters of the augmenting declaration do not match the introductory declarations's type parameters. This means there must be the same number of type parameters with the exact same type parameter names (same identifiers) and bounds if any (same types, even if they may not be written exactly the same in case one of the declarations needs to refer to a type using an import prefix).
Since repeating the type parameters is, by definition, redundant, this restriction doesn't accomplish anything semantically. It ensures that anyone reading the augmenting type can see the declarations of any type parameters that it uses in its body and avoids potential confusion with other top-level variables that might be in scope in the library augmentation.
The augmenting declaration may choose to omit the bound on any type parameter, in which case it will be inherited from the introductory declaration.
foo<X extends num, Y extends X>(); augment foo<X extends num, Y>() { ... }Here,
Yin the augmentation inheritsextends Xfrom the introductory declaration.
Augmenting function and constructor signatures
When augmenting a function (top level, static method, instance method, etc.) or constructor (generative, factory, etc.) the parameter lists must be the same in all meaningful ways. We say that an augmenting function or constructor's signature matches an introductory signature if:
-
It has the same number of type parameters with the same type parameter names (same identifiers) and bounds (after type annotation inheritance), if any (same types, even if they may not be written exactly the same in case one of the declarations needs to refer to a type using an import prefix).
-
The return type (if not omitted) is the same as the introductory declaration's return type.
-
It has the same number of positional parameters as the introductory declaration, and the same number of those are optional.
-
It has the same set of named parameter names as the introductory declaration.
-
For each corresponding pair of parameters:
-
They have the same type (or the augmenting declaration omits the type).
-
They both have the modifier
covariant, or none of them have it. -
They both have the modifier
required, or none of them have it.
For constructors, we do not require parameters to match in uses of initializing formals or super parameters. In fact, they are implicitly prohibited from doing so: if a constructor and its augmentation both have initializing formals or super parameters, they are both complete and it's an error to augment a complete constructor with another complete constructor. Instead, at most only one of the constructors can use initializing formals or super parameters and all other declarations for the same constructor must declare the corresponding parameters as regular parameters.
-
-
For all positional parameters:
-
The name of the augmenting parameter declaration is
_, or -
The name of the augmenting parameter declaration is the same as the name of the corresponding parameter declaration in every preceding declaration that doesn't have
_as its name.
In other words, a declaration can ignore a positional parameter's name by using
_, but all declarations in the chain that specify a name which is not_must agree on it.f1(int _) {} // OK, this declaration doesn't care about the name. augment f1(int x); // OK, first declaration to introduce a name. augment f1(int _); // OK. augment f1(int y); // Error, can't change the name. augment f1(int _); // OK.*If an augmentation specifies
_as the name of a parameter, a non-_name is not "inherited" from a preceding declaration for use in the augmentation's body. The name of the parameter for that augmentation is_, which can't be accessed because it's a wildcard:f(int x); augment f(int _) { print(x); } // Error. -
In this definition, the properties of the introductory declaration may correspond to an explicitly declared property (such as an explicitly stated return type) or an inferred property (such as a parameter type which has been obtained by override inference).
In a declaration where a parameter named n is declared using _, the
name n is not in scope and may be resolved elsewhere. For example:
int y = 42;
void g(int y);
augment g(_) {
print(y); // OK, prints '42'.
}
Augmenting functions
A top-level function, static method, instance method, operator, getter, or setter may be augmented to provide a body or add metadata:
class Person {
final String name;
final int age;
Map<String, Object?> toJson();
}
// Provide a body for `toJson()`:
augment class Person {
augment toJson() => {'name': name, 'age': age};
}
A top-level function, static method, or instance method may be augmented to provide default values for optional parameters:
class C {
void m1([int i]);
void m2({String name});
void m3({String otherName = "Smith"}); // OK, too.
}
augment class C {
augment m1([i = 1]) {}
augment m2({name = "John"}) {}
augment m3({otherName}) {}
}
An optional formal parameter has the default value d if exactly one
declaration of that formal parameter in the augmentation chain specifies a
default value, and it is d. An optional formal parameter does not have an
explicitly specified default value if none of its declarations in the
augmentation chain specifies a default value. The default value is
introduced implicitly with the value null in the case where the parameter
has a nullable declared type, and no default values for that parameter are
specified in the augmentation chain.
It's a compile-time error if:
-
The signature of the augmenting function does not match the signature of the corresponding introductory declaration.
-
More than one declaration in the augmentation chain specifies a default value for the same optional parameter. This is an error even in the case where all of them are identical. Default values are defined by the introductory function or an augmentation, but at most once.
-
No declaration in the augmentation chain specifies a default value for an optional parameter whose declared type is potentially non-nullable, and the declared function is not abstract.
-
A function is not complete after all augmentations are applied, unless it's an instance member and the surrounding class is abstract. Every function declaration eventually needs to have a body filled in unless it's an instance method that can be abstract. In that case, if no declaration provides a body, it is considered abstract.
Augmenting variables, getters, and setters
For purposes of augmentation, a variable declaration is treated as implicitly
defining a getter whose return type is the type of the variable. If the variable
is not final, or is late without an initializer, then the variable
declaration also implicitly defines a setter with a parameter named _ whose
type is the type of the variable.
If the variable is abstract, then the getter and setter are incomplete,
otherwise they are complete. For non-abstract variables, the compiler
synthesizes a getter that accesses the backing storage and a setter that updates
it, so these members have bodies.
A getter can be augmented by another getter, and likewise a setter can be augmented by a setter. This is true whether the getter or setter is explicitly declared or implicitly declared using a variable declaration.
Since non-abstract variables are complete, that implies that it is an error to augment a non-abstract variable declaration with a complete getter, setter, or variable declaration. Likewise, it is an error to augment a complete getter or setter with a non-abstract variable declaration.
It's a compile-time error if:
-
The signature of the augmenting getter or setter does not match the signature of the corresponding introductory getter or setter.
-
A
constvariable declaration is augmented or augmenting. -
A getter or setter (including one implicitly induced by a variable declaration) is not complete after all augmentations are applied, unless it's an instance member and the surrounding class is abstract. Every getter or setter declaration eventually needs to have a body filled in unless it's an instance member that can be abstract. In that case, if no declaration provides a body, it is considered abstract.
Using the general compile-time error principle, some additional situations
are errors. In particular, a compile-time error occurs if, after application of
all augmentations, a static or library variable has no initializing expression,
and its type is not nullable, and the declaration does not have the modifier
late nor the modifier external.
Augmenting enums
An augmentation of an enum type can add new members to the enum, including new enum values. Enum values are appended in augmentation application order.
Enum values themselves can't be augmented since they are essentially constant variables and constant variables can't be augmented.
An introductory enum declaration introduces implicit introductory and
complete declarations of:
int get indexint get hashCodebool operator ==(Object)static const List<E> values;whereEis the enum type.
For ordering purposes, these implicit declarations are before any members declared in the declaration.
Any declaration of the same members must be augmenting (they are not
introductory) and must not be complete.
Declaring an instance member named values will conflict with the
static values declaration as a normal scope name conflict.
It's a compile-time error if:
- An enum doesn't have any values after all augmentations are applied. The grammar allows an enum declaration to not have any values so that other declarations of the same enum can add them, but ultimately the enum must end up with at least one value.
Augmenting constructors
Augmenting a constructor works similarly to augmenting a function, with some extra rules to handle features unique to constructors, like redirections and initializer lists, and the primary constructor syntax.
A constructor declaration is a factory constructor declaration if it
has a factory keyword, and it is a generative constructor declaration if
it does not, including when it is a primary constructor declaration.
A constructor declaration is a const constructor declaration if
it has a const keyword, or if it is a generative constructor declaration
of an enum declaration, and otherwise it is not.
For a primary constructor, the const keyword goes before the class name
in the header.
Factory constructor declarations
A factory constructor declaration is a non-redirecting factory constructor
declaration, and is a complete declaration, if it has a body ({...}
or => ...;) or an external keyword.
A factory constructor declaration is a redirecting factory constructor
declaration and is complete if it has a redirection clause (= TargetType;).
A factory constructor declaration with no body and no redirection clause,
ended by a single ;, is not a complete declaration, and does not decide
whether the factory constructor being defined is redirecting or not.
Only the (single) complete declaration forces that decision.
Generative constructor declarations.
A primary constructor declaration causes the constructor it defines to be a primary constructor, and the class to have a primary constructor. This means the class cannot have any other initializing constructors, which is reflected by the consistency rules below, and it changes the scope used for instance variable initializer expressions.
A primary constructor declaration always has an in-header part which
contains a parameter list, and optionally a const and a constructor-
name identifier, (class const C.id(int x)). It may also have an
in-body this-part which can contain an initializer list or body,
and can have metadata attached (this: super(x) { print('done'); }).
A primary constructor declaration is an initializing constructor declaration. A primary constructor declaration is complete if (any of):
- It has an initializing formal parameter (
this.name). - It has a super parameter (
super.name). - It has a declaring parameter (
final int name). - It has an in-body
this-part that:- has a body (
{...}) and/or - has an initializer list (
: ...).
- has a body (
- It's in an extension type declaration. A primary constructor declaration is an augmenting declaration if (either of):
- It has an in-body
this-part with anaugmentkeyword (augment this ...). - It has no in-body
this-part, and there is another constructor declaration with the same name which occurs before this constructor declaration. Otherwise it is an introductory declaration. An augmenting primary constructor with no in-bodythis-part does not have a separateaugmentkeyword. Such a constructor is necessarily part of an augmenting class or enum declaration, and its header-part is likely on the same line as theaugmentkeyword of that declaration. This is considered sufficient, rather than forcing the author of a class declaration likeaugment class C(final int x);to add a body and in-bodythis-part just to write anaugmenton it, or alternatively to require a secondaugmentkeyword in the header asaugment class augment C(final int x);.
If a complete primary constructor has a declaring parameter, that parameter
declaration also counts as a complete introductory or augmenting variable
declaration, which is final if the declaring parameter has the final
modifier, and is augmenting if there is a prior declaration of
the same getter and/or setter. (The augment modifier does not apply
to parameters, so the declaring parameter cannot be marked as augmenting
a variable.)
A non-primary generative constructor declaration is an initializing constructor declaration and is complete if (any of):
- It has an
externalkeyword. - It has an initializing formal parameter (
this.name). - It has a super parameter (
super.name). - It has a body (
{...}instead of;). - It has an initializer list (
: ...).
(A non-primary initializing constructor cannot have declaring parameters, those are only available to primary constructor declarations. If declaring parameters ever become valid for a non-primary generative constructor, they'll also make the constructor complete.)
A non-primary generative constructor declaration is a redirecting generative
constructor declaration and is complete if it has a redirection clause
(: this(args); or : this.name(args);).
The declarations of a class or enum can contain both primary and non-primary declarations of the same constructor. The class has a primary constructor if at least one declaration uses the primary constructor syntax.
An in-body generative constructor declaration with no special parameters,
initializer list, body or redirection, like ClassName(), does not decide
whether the constructor is redirecting or initializing. That is decided by
any declaration which is complete, or which is a primary constructor
since those must be initializing.
If all declarations of a generative constructor are incomplete,
(and therefore contains no redirecting generative constructor declarations,
since those are all complete),
then the constructor being defined is an initializing constructor,
which is const if the declarations are, and which has normal parameters
corresponding to the signature and default values defined by all
the declarations, no initializer list, invoking the "unnamed" superclass
constructor with no arguments, and with no body.
(A declaration of C(); is defined as incomplete, but is also
historically a valid concrete implementation of a trivial constructor,
and this ensures that it keeps working that way. Effectively if a
generative constructor has no complete declaration, it gets a "default
constructor" implementation with normal parameters for the combined
parameters declared by all of the incomplete constructors. Those
parameters may be visible in instance variable initializers.)
Example:
class C {
C.generative();
factory C.fact();
C.other();
}
augment class C {
augment C.generative() : this.other();
augment factory C.fact() = C.other;
}
Here C.other has only incomplete declarations.
The class gets an implementation of that constructor equivalent to
C.other(): super();.
Example with a primary constructor:
class const D(int x) {
final int squared = x * x;
/// Creates a `D`.
this;
augment const new(@Since("3.15") int x);
}
augment class const D(final int x) {
augment this { print("Initialized D"); }
@Deprecated("Was a bad idea anyway")
augment const D(int _);
}
Example with incomplete primary constructor.
class const D(int _) {
/// Don't know yet.
init;
}
augment class const D(int x) {
final int squared = x * x;
}
Here the D.new constructor has only incomplete declarations.
It gest a default implementation equivalent to D(int x): super();,
based on the names and types of all declarations, and it can use x
in its instance variable declarations (which is exactly how the second
class declaration would work by itself without augmentations).
Consistency rules for constructor declarations
It's a compile-time error if the declarations of a class or enum contain constructor declarations where:
-
There is an augmenting constructor declaration with no corresponding (earlier) introductory declaration.
-
There is an introductory constructor declaration with any other declaration for that constructor which is before it. Same two rules as for other augmentations. The first declaration must be introductory, any non-first declaration must be augmenting.
-
There exist two constructor declarations for the same constructor, and (any of):
- Both are complete declarations. (As everywhere else, there can be at most one complete declaration.)
- One is a const constructor declaration and the other is not.
- One is a factory constructor declaration and the other is a generative constructor declaration.
- One is a redirecting factory constructor declaration and the other is a non-redirecting factory constructor declaration. Redundant since both would be complete declarations too, but included for completeness, in case later language features would make it not be redundant.
- One is an initializing constructor declaration and the other is a redirecting generative constructor declaration.
If there are more than two declarations of a constructor, and one of them does not match the rest for one of these rules, there is more than one way that that program has a compile-time error. How to report that usefully is up to the tool that checks for errors. It may be sufficient to point to the one declaration that stands out, or it can choose to report every declaration that has an error compared to the introductory declaration.
-
The signature of an augmenting constructor does not match the signature of the corresponding introductory constructor. The signature of a constructor using the privately-named-parameters feature uses the public name for that parameter.
-
There is a primary constructor declaration, and there is an initializing constructor declaration with a different name, and the enclosing declaration is a class or enum declaration. If a class or enum has a primary constructor declaration, that constructor must still be the only initializing constructor, just as specified by the primary-constructors feature. That restriction applies to the entire class or enum being defined, not just a single declaration, and applies in both directions if two separate class declarations have primary constructor declarations for different constructors.
-
Two different declarations of the same constructor both specify a default value of the same parameter. This is an error even if it is the same default value. Parameter default values can be defined by the introductory declaration or by an augmenting declaration, but at most once.
-
A constructor declaration has a default value for a parameter, and there is a redirecting factory constructor declaration for the same constructor. It can be the same declaration. Redirecting factory constructors cannot declare default values.
-
A constructor declaration declares an optional parameter with a non-null type, and no declaration for the same constructor:
- declares a default value for that parameter, or
- is a redirecting factory constructor declaration. Non-redirecting factory constructors must have default values for non-nullable optional parameters.
-
There is a factory constructor declaration and no complete constructor declaration for the same constructor. A factory constructor which has
;instead of a body or redirection is incomplete, and it's not deciding whether the constructor is redirecting or not. This allows, and requires due to this rule, another declaration to fill in an implementation. Generative constructors may have no complete declarations, they'll then get a trivial default implementation as described above.
Instance variable initialization during constructor invocation
When invoking an initializing generative constructor to initialize a new object, instance variable initialization happens before executing the initializer list.
This is true whether or not the class or enum has a primary constructor.
When invoking the initializing constructor to initialize a new object, the first thing that happens is that actual arguments are bound to formal parameters, which provides the bindings for the initializer list scope.
Then all non-late instance variable declarations of the class which have
an initializer expression are processed in their source order.
Each instance variable is initialized in turn, by evaluating its initializer expression. If the class or enum has a primary constructor, the initializer expression is evaluated in the initializer list scope, otherwise it's evaluated in the body scope of the surrounding class or enum (extension and extension type declarations cannot contain instance variable declarations, mixins and mixin-application classes cannot have primary constructors).
If the class has a constant generative constructor, then it's still a compile-
time error if the class has any non-final instance variables, and it's still
a compile-time error if an instance variable has an initializer expression
that is not a potentially constant expression.
Whether the evaluation uses the body scope or the initializer list scope, which has the body scope as parent scope, it's still an error if the expression refers to any instance member in the body scope.
After all instance variable initializers have been executed, constructor execution continues with executing as in Dart before this feature, starting with the variable initialization of the parameter list, by initializing formals and declaring parameters.
This is how primary constructors already work. The only difference is that because a single constructor can be introduced by more than one declaration, the complete declaration, the actual implementation, of a primary constructor might not be a primary constructor declaration.
Augmenting extension types
An introductory extension type declaration must have a primary constructor clause, which must have precisely one parameter. Just like for a class or enum, that primary constructor clause is a constructor declaration. For an extension type, it is an introductory and complete initializing constructor declaration.
That primary constructor's parameter declaration is also an introductory and
complete getter declaration for a getter with the same name and type as the
parameter declaration. This is just like a declaring final parameter of a
primary constructor of a class declaration. The parameter of an extension type
primary constructor is always declaring and final, whether it has an explicit
final or not.
This orders these declarations before any member declarations inside the extension type declaration, and since they must be declared by the introductory extension type declaration, they are always introductory.
When augmenting an extension type declaration, the representation type declaration cannot be repeated, an augmenting extension type declaration cannot have a primary constructor.
Note
Since the representation type declaration on the introductory extension type introduces a complete constructor, it is a known limitation that an augmentation cannot later attach an implementation body to the primary constructor. All implementation of the primary constructor must be given in the introductory declaration. To work around this, developers can declare a private primary constructor and expose a public constructor to be augmented.
Augmenting with metadata annotations
An augmenting declaration can have metadata attached to it. The language doesn't specify how metadata is used. Tools may choose to append metadata from augmentations to the resulting combined declaration or allow inspecting the metadata on the individual augmentations.
In practice, most code generators use the analyzer package to introspect over code. Code generators introspecting on the syntax of some code likely want to see the metadata for each syntactic declaration separately. Code generators introspecting over the resolved semantic model of the code (which is more common) probably want to see the metadata of the introductory declaration and all augmentations appended into a single list of metadata accessible from the combined declaration.
Compile-time errors which are eliminated
With this feature, it is no longer an error to have the following situations:
-
An
implementsclause contains two or more type operands denoting the same type. For example,class A implements I, I;is no longer an error. -
An
implementsclause of a declaration D contains an operandT, and anextendsorwithclause of D contains an operandS, andTandSdenote the same type. For example,class A extends I implements I;is no longer an error. -
An
implementsclause of a mixin declaration D contains an operandT, and anonclause of D contains an operandS, andTandSdenote the same type. For examplemixin M on I implements I;is no longer an error.
An operand of an extends, with, on, or implements clause is a
type that occurs in the clause which is not a subterm of another type.
In particular, these changes have no relevance to nested occurrences like
I in class A extends B<I> implements C<I, I Function(String)>;
(which were not errors, anyway).
The motivation for these changes is mainly that, in general, compile-time errors are reported for code constructs whose semantics cannot reasonably be determined, and hence no program containing this code can be compiled and executed.
In contrast, redundant implemented interfaces do not give rise to any ambiguities or inconsistencies. The errors were only reported because the situation was considered useless and confusing, and it was assumed to arise only by mistake.
It is outside the scope of a language specification document like this one, but it may well be useful for tools like the analyzer or linter to report a warning if this kind of redundancy is detected. They might treat redundancies in the same syntactic construct more severely than redundancies that only exist in the semantic declaration, but are syntactically located in different elements of an augmentation chain.
Compile errors with augmentations
Prior to augmentations, the definition of a semantic entity is produced by a single syntactic declaration. That allows the language specification to refer to those entities interchangeably. With augmentations, that is no longer the case. A single semantic entity may be the product of multiple syntactic declarations (an introductory and any number of augmentations). This raises the question of whether existing compile errors apply to syntactic declarations or semantic definitions.
For example, it is an error according to the language specification if a concrete class has an abstract instance member declaration D, and there is no implementation inherited from a superclass whose signature is a correct override of the member signature of D.
Thus this is an error in Dart without augmentations:
class C {
int get g;
}
However, when augmentations are supported it is possible to provide the missing implementation in an augmenting declaration:
augment class C {
augment int get g => 0;
}
The general rule is that compile-time errors apply to semantic definitions whenever possible. For this example it is the class as a whole, as defined by all its introducing and augmenting declarations, which must have a concrete member whose signature is a valid override for its interface member signature. Having some individual member declarations which do not introduce an implementation does not mean that the class does not introduce an implementation of that member, as long as there is one concrete member declaration for the member.
The motivation for this principle is that reorganizing code into or out of augmentations shouldn't affect the errors that are reported. Augmentations are a syntactic tool for organizing code, but what the user cares about -- and what static analysis should thus focus on -- is the semantics of the resulting definitions. Also, in most cases the error relies on semantic information that isn't even well defined for syntactic entities and is only known from the resolved semantic definition which can't be produced without applying augmentations.
Dynamic semantics
The application of augmentation declarations to an augmented declaration produces something that looks and behaves like a single declaration: It has a single name, a single type or function signature, and it's what all references to the name refers to inside and outside of the library.
Unlike before, that single semantic declaration now consists of multiple syntactic declarations (one introductory declaration, the rest augmenting declarations, with a given augmentation application order), and the properties of the combined semantic declaration can be derived from the syntactic declarations.
We redefine a number of semantic functions to now work on a stack of declarations (the declarations for a name in bottom to top order), so that existing semantic definitions keep working.
Example: Class declarations
Super-declarations
The specification of class modifiers introduced a number of predicates on declarations, to check whether the type hierarchy is well formed and the class modifiers are as required, before the static semantics have even introduced types yet. We modify those predicates to apply to a stack of augmenting declarations and an introductory declaration as follows:
- A a non-empty stack of syntactic class declarations, C, has a
declaration D as declared super-class if:
- C starts with an (augmenting or not) class declaration C0 and either
- C0 has an
extendsclause whose type clause denotes the declaration D, or - C0 is an augmenting declaration, so C continues with a non-empty Crest, and Crest has D as declared super-class.
- C0 has an
- C starts with an (augmenting or not) class declaration C0 and either
- A a non-empty stack of syntactic class declarations, C, has a
declaration D as declared super-interface if:
- C starts with an (augmenting or not) class declaration C0 and either
- C0 has an
implementsclause with an entry whose type clause denotes the declaration D, or - C0 is an augmenting declaration, so C continues with a non-empty Crest, and Crest has D as declared super-interface.
- C0 has an
- C starts with an (augmenting or not) class declaration C0 and either
- A a non-empty stack of syntactic class declarations, C, has a
declaration D as declared super-mixin if:
- C starts with an (augmenting or not) class declaration C0 and either
- C0 has a
withclause with an entry whose type clause denotes the declaration D, or - C0 is an augmenting declaration, so C continues with a non-empty Crest, and Crest has D as declared super-mixin.
- C0 has a
- C starts with an (augmenting or not) class declaration C0 and either
Members
A class declaration stack, C, of an introductory declaration and zero or more augmenting declarations, defines an augmented interface (member signatures) and augmented implementation (instance members declarations) based on the individual syntactic declarations.
A non-empty class declaration stack, C, has the following set of instance member declarations:
- Let Ctop be the latest declaration of the stack, and Crest the rest of the stack.
- If Ctop is a non-augmenting declaration, the declarations of C is the set of syntactic instance member declarations of Ctop.
- Otherwise let P be the set of member declarations of the non-empty stack Crest.
- and the member declarations of C is the set R defined as containing
only the following elements:
- A singleton stack of each syntactic instance member declaration M of Ctop, where M is a non-augmenting declaration.
- The elements N of P where Ctop does not contain an augmenting instance member declaration with the same name (mutable variable declarations have both a setter and a getter name).
- The stacks of a declaration M on top of the stack N, where N is a member of P, M is an augmenting instance member declaration of Ctop, and M has the same name as N.
And we can whether such an instance member declaration stack, C, defines an abstract method as:
- Let Ctop be the latest element of the stack and Crest the rest of the stack.
- If Ctop is a non-variable declaration, and is not declared abstract, the C doe
- If Ctop declares a function body, then C does not define an abstract method.
- Otherwise C defines an abstract method if Crest defines an abstract method.
(This is just for methods, we will define it more generally for members, including variable declarations.)
Example: Instance methods
Properties
Similarly we can define the properties of stacks of member declarations.
For example, we define the augmented parameter list of a non-empty stack, C, of augmentations on an introductory function declaration as:
- Let Ctop be the latest element of the stack and Crest the rest of the stack.
- If Ctop is not an augmenting declaration, its augmented parameter list is its actual parameter list. (And Crest is known to be empty.)
- Otherwise Ctop is an augmenting declaration with a parameter
list which must have the same parameters (names, positions, optionality and
types) as its augmented declaration, except that it is not allowed to
declare default values for optional parameters.
- Let P be the augmented parameter list of Crest.
- The augmented parameter list of Ctop is then the parameter list of Ctop, updated by adding to each optional parameter the default value of the corresponding parameter in P, if any.
This will usually be exactly the parameter list of the introductory declaration, but the ordering of named parameters may differ. This is mostly intended as an example, in practice the augmented parameter list can just be the parameter list of the introductory declaration, but it's more direct and clearly correct to use the actual parameter list of the declaration when creating the parameter scope that its body will run in.
Similarly we define the augmented function type of the declaration stack. Because of the restrictions we place on augmentations, they will all have the same function type as the introductory declaration, but again it's simpler to assign a function type to every declaration.
Invocation
When invoking an instance member on an object, the current specification looks
up the corresponding implementation on the class of the runtime-type of the
receiver, traversing super-classes, until it it finds a non-abstract
declaration or needs to search past Object. The specification then defines
how to invoke that method declaration, with suitable contexts and bindings.
We still define the same thing, only the result of lookup is not a single declaration, but a stack of augmenting declarations on top of an introductory declaration, and while searching, we skip past declaration stacks that define an abstract method. The resulting stack is the member definition, or semantic declaration, which is derived from the syntactic declarations in the source.
Invoking a stack, C, of instance method declarations on a receiver object o with an argument list A and type arguments T, is then defined as follows:
- Let Ctop be the latest declaration on the stack (the last applied augmentation in augmentation application order), and Crest the rest of the stack.
- If Ctop has a function body B then:
- Bind actuals to formals (using the usual definition of that), binding the argument list A and type arguments T to the augmented parameter list of Ctop and type parameters of Ctop. This creates a runtime parameter scope which has the runtime body scope as parent scope (the lexical scope of the class, except that type parameters of the class are bound to the runtime type arguments of those parameters for the instance o).
- Execute the body B in this parameter scope, with
thisbound to o. - There would have been a compile-time error if there is no earlier declaration with a body.
- The result of invoking C is the returned or thrown result of executing B.
- Otherwise, the result of the invocation of C is the result of invoke
Crest on o with argument list A and type arguments T.
- This will eventually find a body to execute, otherwise C would have defined an abstract method, and would not have been invoked to begin with.
Tooling
Documentation comments
Documentation comments are allowed in all the standard places in library augmentations. It is up to the tooling to decide how to present such documentation comments to the user, but they should generally be considered to be additive, and should not completely override the original comment. In other words, it is not the expectation that augmentations should duplicate the original documentation comments, but instead provide comments that are specific to the augmentation.
Part directive order
The order that augmentations are applied is sometimes user visible. That order
is in turn affected by the order of part file directives in the file.
That means that the order of part declarations in a file is now semantically
meaningful. Tools like IDEs should not assume it is always safe to, say,
automatically alphabetize them. However, in most cases, augmentation order
doesn't matter and it's usually safe to sort them if a user requests it.
For the part directive order to matter:
-
There must be augmentations of the same declaration in multiple separate part files.
-
The part files containing the augmentations must be siblings with neither a parent of the other.
-
Those augmentations must have their application order be user visible. This isn't defined precisely, but includes adding
enumvalues or mixins (withclause). Even then, the order is often not visible. Both augmentations would have to add enum values. If multiple augmentations addwithclauses, the order is only visible if the applied mixins have overlapping members.
The first two are fairly simple to detect. The third is subtle (and may not be fully captured by that paragraph). It's probably safest to be pessimistic and assume the third point is always true.
Changelog
1.44
-
Add primary constructors.
-
Fix the Before/After relation definitions (prior definition used "includes" transitively even if it was only declared for direct part includes).
-
Remove restriction against declaring
toStringin anenum. There is no existing rule against anenumoverridingtoString, and augmentations shouldn't introduce a restriction. -
Change restriction against declaring
operator ==,hashCode,indexandvaluesin anenumto having the introductoryenumintroduce those members as complete declarations. Then you can still@something augment get index;to add metadata. (Existing language allows abstract declarations of all mentioned members exceptvalues.)
1.43
- Restore the rule about the use of
_as a "don't care" name in the augmentation chain for a formal parameter, using the same text as in version 1.37.
1.42
- Adjust the grammar to allow empty membered bodies to be specified as a semicolon.
1.41
-
Adjust the grammar to enforce that some top-level constant and final variable declarations have an initializing expression.
-
Add support for static abstract variable declarations in the grammar.
-
Adjust the definition of signature matching (using
_as "don't care" parameter names is no longer supported). -
Add a section to say that certain redundant superinterfaces are no longer an error.
1.40
- Clarify how applying augmentations interacts with compile-time errors (#3690).
1.39
-
Non-semantic copy editing. Remove some redundant specification. Clarify that generative constructors can remain incomplete without error.
-
Add some rationale for why the introductory declaration only needs to be before the augmenting ones and not above (#4377).
1.38
- Generalize the treatment of default values of optional parameters (#4172).
1.37
- Rename to "augmentations" (from "augmentation libraries") and define the experiment flag to be "augmentations" (was part of "macros").
1.36
- Remove
augmentfrom typedef grammar since typedefs can no longer be augmented (#4388). - Allow augmenting variable declarations (#4387).
1.35
- Reorganize sections.
- Remove references to macros.
- Don't allow augmentations to wrap or replace code. Remove support for
augmentedexpressions. Disallow an augmentation from providing a body to a declaration that already has one. - Remove support for augmenting variables.
- Simplify constructor augmentations: no concatenating initializers or merging initializers from one augmentation and a body from another.
- Remove support for augmenting typedefs.
- Remove support for augmenting redirecting constructors.
- Allow a function augmentation to have an
externalbody. - Rewrite "Scoping" section to be clearer.
- Remove recommend path ordering lint. Commit to making
partdirective order meaningful and acceptable to rely on (#3849). - Allow enum declarations without values (#4356).
- Specify signature matching for implicit setters from abstract variables (#4022).
- Clarify that you can't augment an extension type constructor and add a body (#4047).
- Don't allow augmenting mixin application classes (#4060).
1.34
-
Revert some errors introduced in version 1.28.
- An abstract variable can now be augmented with non-abstract getters and setters.
- External variables can now be augmented with abstract getters and setters.
1.33
- Change the grammar to remove the primary constructor parts of an augmenting extension type declaration.
1.32
- Specify that variables which require an initializer can have it defined in any augmentation.
- Specify that the implicit null initialization is not applied until after augmentation.
1.31
- Specify that it is an error to have a static and instance member with the same name in the fully merged declaration.
1.30
- Simplify extension type augmentations, don't allow them to contain the representation type at all.
1.29
- Simplify enum value augmentations, no longer allow altering the constructor invocation.
1.28
- Explicitly disallow augmenting abstract variables with non-abstract variables, getters, or setters.
- Explicitly disallow augmenting external declarations with abstract declarations.
- Remove error when augmenting an abstract or external variable with a variable (allowed for adding comments/annotations).
1.27
- Specify that representation objects for extension types cannot be augmented.
1.26
- Recreate the change made in 1.23 (which was undone by accident).
1.25
- Clarify that augmentations can occur in the same type-introducing declaration body, even in a non-augmenting declaration.
- Update some occurrences of old terminology with new terms.
1.24
- Allow augmentations which only alter the metadata and/or doc comments on various types, and specify behavior.
1.23
- Change
augmentedoperator invocation syntax to be function call syntax.
1.22
- Unify augmentation libraries and parts. Parts with imports moved into separate document, as a stand-alone feature that is not linked to augmentations.
- Augmentation declarations can occur in any file, whether a library or part file. Must occur "below" the introductory declaration (later in same file or sub-part) and "after" any prior applied augmentation that it modifies (below, or in a later sub-part of a shared ancestor).
- Suggest a stronger ordering lint, where the augmentation must be "below"
the augmentation it is applied after. That imples that all declarations with
the same name are on the same path in the library file tree, so that
reordering
partdirectives does not change augmentation application order. - Change the lexical scope of augmenting class-like declarations to only contain the member declarations that are syntactically inside the same declaration, rather than collecting all member declarations from all augmenting or non-augmenting declarations with the same name, and making them all available in each declaration.
- Avoid defining a syntactic merging, since it requires very careful scope management, which isn't necessary if we can just extend properties that are currently defined for single declarations to the combination of a declaration plus zero or more augmentations.
1.21
- Add a compile-time errors for wrong usages of
augmented.
1.20
- Change the
extensionDeclarationgrammar rule such that an augmenting extension declaration cannot have anonclause. Adjust other rules accordingly.
1.19
- Change the phrase 'augmentation library' to 'library augmentation', to be consistent with the rename which was done in 1.15.
1.18
- Add a grammar rule for
enumEntry, thus allowing them to have the keywordaugment.
1.17
- Introduce compile-time errors about wrong structures in the graph of
libraries and augmentation libraries formed by directives like
importandimport augment(#3646).
1.16
-
Update grammar rules and add support for augmented type declarations of all kinds (class, mixin, extension, extension type, enum, typedef).
-
Specify augmenting extension types. Clarify that primary constructors (which currently only exist for extension types) can be augmented like other constructors (#3177).
1.15
- Change
library augmenttoaugment library.
1.14
- Change
augment supertoaugmented.
1.13
- Clarify which clauses are (not) allowed in augmentations of certain declarations.
- Allow adding an
extendsclause in augmentations.
1.12
- Update the behavior for variable augmentations.
1.11
- Alter and clarify the semantics around augmenting external declarations.
- Allow non-abstract classes to have implicitly abstract members which are implemented in an augmentation.
1.10
- Make
augmenta built-in identifier.
1.9
- Specify that documentation comments are allowed, and should be considered to be additive and not a complete override of the original comment. The rest of the behavior is left up to implementations and not specified.
1.8
-
Specify that augmented libraries and their augmentations must have the same language version.
-
Specifically call out that augmentations can add and augment enum values, and specify how that works.
1.7
- Specify that augmentations must contain all the same keywords as the original declaration (and no more).
1.6
-
Allow class augmentations to use different names for type parameters. This isn't particular valuable, but is consistent with functions augmentations which are allowed to change the names of positional parameters.
-
Specify that a non-augmenting declaration must occur before any augmentations of it, in merge order.
-
Specify that augmentations can't have parts (#2057).
1.5
-
Augmentation libraries share the same top-level declaration and private scope with the augmented library and its other augmentations.
-
Now that enums have members, allow them to be augmented.
-
Compile-time error if a non-
lateaugmenting instance variable calls the initializer for alateone.
1.4
- When inferring the type of a variable, only the original variable's initializer is used.
1.3
- Constructor and function augmentations can't define default values.
1.2
- Specify that augmenting constructor initializers are inserted before the original constructor's super or redirecting initializer if present (#2062).
- Specify that an augmenting type must replicate the original type's type parameters (#2058).
- Allow augmenting declarations to add metadata annotations and macro applications (#2061).
1.1
- Make it an error to apply the same augmentation multiple times (#1957).
- Clarify type parameters and parameter modifiers in function signature matching (#2059).
1.0
Initial version.