Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extension types page #5508

Merged
merged 13 commits into from
Feb 14, 2024
2 changes: 2 additions & 0 deletions src/_data/side-nav.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@
permalink: /language/enums
- title: Extension methods
permalink: /language/extension-methods
- title: Extension types
permalink: /language/extension-types
- title: Callable objects
permalink: /language/callable-objects
- title: Class modifiers
Expand Down
315 changes: 315 additions & 0 deletions src/language/extension-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
---
title: Extension types
description: Learn how to write a static-only interface for an existing type.
prevpage:
url: /language/extension-methods
title: Extension methods
nextpage:
url: /language/callable-objects
title: Callable objects
---

An extension type is a compile time abstraction that "wraps"
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
an existing type with a different, static-only interface.
They allow you to enforce discipline on the set of operations available
to objects of the underlying type, called the *representation type*.
You can choose to use some members, omit others,
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
replace others, and add new functionality,
all while getting compile time errors before runtime errors.
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved

The following example wraps the `int` type to create an extension type
that only allows operations that make sense for ID numbers:

```dart
extension type IdNumber(int i) {
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
// Wraps the 'int' type's '<' operator:
operator <(IdNumber other) => i < other.i;
// Doesn't declare the '+' operator, for example,
// because addition does not make sense for ID numbers.
}

void main() {
// Without the discipline of an extension type,
// 'int' exposes ID numbers to unsafe operations:
int myUnsafeId = 42424242;
myUnsafeId = myUnsafeId + 10; // OK, but shouldn't be allowed for IDs.

var safeId = IdNumber(42424242);
safeId + 10; // Compile-time error, no '+' operator.
myUnsafeId = safeId; // Compile-time error, wrong type.
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
myUnsafeId = safeId as int; // OK, runtime cast to representation type.
}
```

{{site.alert.note}}
Extension types serve the same purpose as **wrapper classes**,
but don't require the creation of expensive runtime objects.
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
Because extension types are static-only and compiled away at runtime,
they are essentially zero cost.

[**Extension methods**]() are a static abstraction similar to extension types.
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
However, an extension method adds functionality to *all* instances
of its underlying type. Extension types are the opposite;
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
an extension type's interface only applies to objects
declared with the extension type,
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
and is distinct from the interface of its underlying type by default.
Read the [Transparency](#transparency) section for more details.
{{site.alert.end}}

## Syntax

### Declaration

Define a new extension type with the `extension type` declaration and a name,
followed by the *representation type declaration* in parenthesis:

```dart
extension type E(int i) {
// Define set of operations
}
```

The representation type declaration specifies that
the underlying type of extension type `E` is `int`,
and the reference to the *representation object* is named `i`.

The representation object gives access to the underlying type,
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
the same way declaring a final instance variable of the underlying type
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "of the underlying type" here makes it sound (at least also) like the instance variable is on the underlying type, not that its declared type of the variable.
Needs at least "instance variable of the underlying type's type" which gets weird.

Copy link
Contributor Author

@MaryaBelanger MaryaBelanger Feb 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the second clause because it seems too confusing no matter what. I initially included it to clarify the purpose of the representation object for people who are familiar with wrapper classes, but maybe that's not valuable enough to warrant any possible confusion?

would in a wrapper class.
It's in scope in the extension type body, and
you can access it using its name as a getter:

- Within the extension type body: `this.i`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or just i, since it's in the lexical scope.
(In fact, I think our lints will likely suggest removing the this.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, I think our lints will likely suggest removing the this.

Just checked, that is true. Thanks

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lrhn would it make sense to say:

  • Within the extension type body: i or this.i in a constructor.

- Outside with a property extraction: `e.i` (where `e` is the object of an extension type).
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved

Extension types declarations can also include [type parameters](generics)
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
just like classes:
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved

```dart
extension type E<T>(int i) {
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
// ...
}
```

### Constructors

You can optionally declare constructors in an extension type's body.
A constructor must be named, because the the representation declaration
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
already acts as an unnamed constructor for the extension type
that initializes the represenation object.
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
Any additional constructors must declare the representation object's
instance variable in its initializer list:
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved

```dart
// 'E' has a named constructor 'n',
// as well as its built-in unnamed constructor
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
extension type E(int i) {
E.n(this.i, String foo);
}

void main() {
var named = E.n(3, "Hello");
var unnamed = E(4);
}
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
```

### Members

Declare members in the body of an extension type to define its interface.
Extension type member declarations are identical to class members.
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
Members can be methods, getters, setters, or operators:

```dart
extension type NumberE(int value) {
// Operator:
NumberE operator +(NumberE other) =>
NumberE(value + other.value);
// Getter:
NumberE get i => value;
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
// Method:
bool isValid() => !value.isNegative;
}
```

Members of the representation type are not available to the extension type
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
by default. If you want a member from the representation type to be available
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
to the extension type, you must write a declaration for it
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
in the extension type definition, like the `operator +` in `NumberE`.
You also can define new members unrelated to the representation type,
like the `i` getter and `isValid` method.

Extension types can't declare instance fields (unless [`external`][]),
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
or [abstract members][].

### Implements

You can optionally use the `implements` clause to introduce
a subtype relationship on an extension type, making *all* members
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
of the representation type available to the extension type:
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved

```dart
extension type NumberI(int i)
implements int{
// ...
}
```

An extension type can only implement its representation type
or a supertype of its representation type.
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved

This is technically NOT an inheritance relationship
like the one `implements` introduces for classes,
but rather an [applicability][] relationship like that between an
extension method and its `on` type.
Members that apply to the supertype are applicable to the subtype,
unless the supertype has a more specific definition for the member.
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved

#### `@redeclare`

Replacing a method that shares a name with a member of the supertype
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
is not an override relationship for extension types, but a *redeclaration*.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then say what that means, because it's not totally clear from the words.

An extension type member declaration completely replaces
any supertype member with the same name. The new member
need not have a compatible function type, or even be of the
same kind, a method can replace a getter or vice-versa,
and it's not possible to provide an alternative implementation
for the same function, like it is for class instance members.
Extension members are resolved statically, so at any point
where an extension member is called, the compiler knows
precisely which extension member declaration is the target.

Or maybe that's too much to say here. And I didn't even use the word "virtual".

Copy link
Contributor Author

@MaryaBelanger MaryaBelanger Feb 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the explanation but yes probably too much to say here :/ (I still added some of it though)

Where are annotations typically defined? It would be great to have this explanation wherever that is, and point to it from here.

Use the `@redeclare` annotation to tell the compiler you are *knowingly* choosing
to use the same name as a supertype's member definition, not just accidentally
using the same name:

```dart
class C {
void m() {}
}

extension type E(C c) implements C {
@redeclare
void m() {}
}
```

If you don't use the annotation, the compiler will choose the [more specific][]
definition wherever the name is invoked, which might not be the behavior you expect.
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we do need an explicit definition of 'more specific extension type member declaration'. ;-)

If we have that then we would just use the concept in section 'Implements' above.

However, the metadata annotation @redeclare makes no difference for the static analysis of the declaration or its invocations: The compiler will always choose the most specific instance member declaration for any given member name, metadata or not, based on the static type of the receiver.

@redeclare does only one thing: It turns off lint messages from annotate_redeclares if the declaration does redeclare some other declaration (which would then necessarily be in a superinterface, which could be an extension type or a non-extension type). If there is a redeclaration, but no @redeclare, then we get a lint message because this situation is considered to be somewhat error prone.

It is possible that a spurious @redeclare is also reported as a lint (that is, a @redeclare on a declaration that doesn't redeclare anything). This is not error-prone, just irritating, because we might waste some time looking for the declaration which is being "turned off" for this type of receiver, because we now have a more specific declaration with the same name—but there is no such declaration, so there's nothing to worry about.

The point is in any case that statically resolved member implementations are very different from OO dispatched member implementations: The latter will always fit the receiver perfectly (we're always calling the most specific implementation of the instance member, relative to the run-time type of the receiver, except when we use super). In contrast, the former is resolved based on the static type of the receiver, and the same receiver can have many different static types.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had linked to this section in a previous commit, should I put that back?

I'm starting to think it'd be best to just point to @redeclare / annotate_redeclares documentation from the "Implements" section, and skip the dedicated @redelcare section altogether. The lint documentation isn't very descriptive though. Fixing that could be one option. But where are annotations documented (if at all)? I can't find anything for @redeclare


## Usage

To use an extension type, create an instance the same as you would with a class:
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved

```dart
extension type NumberE(int value) {
NumberE operator +(NumberE other) =>
NumberE(value + other.value);

NumberE get i => value;
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
bool isValid() => !value.isNegative;
}

void testE() {
var num = NumberE(1);
}
```

Then you can invoke members on the object as you would with a class object.
Any attempt to treat the extension type object as an object
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
of the representation type, or vice versa, will result in compile time errors
(unless that behavior is explicitly defined for the extension type,
or it is [transparent](#transparency)).
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
For example:

```dart
void testE() {
var num = NumberE(1);
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
int num2 = NumberE(2); // Error: Can't assign 'NumberE' to 'int'.

num.isValid(); // Ok: Extension member invocation.
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
num.isNegative(); // Error: 'NumberE' does not define 'int' member 'isNegative'.

var sum = num + num; // Ok: 'NumberE' defines '+'.
var diff = num - num; // Error: 'NumberE' does not define 'int' member '-'.
var diff2 = num.value - 2; // Ok: Can access representation object with reference.
var sum2 = num + 2; // Error: Can't assign 'int' to parameter type 'NumberE'.

List<NumberE> numbers = [
NumberE(1),
num.i, // Ok: 'i' getter returns type 'NumberE'.
1, // Error: Can't assign 'int' element to list type 'NumberE'.
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
1 as NumberE, // Ok: Dynamic type cast recognizes representation type.
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
];
}
```

#### Transparency
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I want this to be the term we use for an extension type implementing its representation type.

It's also possible to have extension types that implement a supertype of their representation type. (Say ExtMyFuture<T>._(_MyFuture<T> _) implements Future<T> where _MyFuture is my own special implementation of Future that I don't want to expose, and ExtMyFuture is an extension type wrapper around it (for some reason).

Is that class "Transparent"? (Not with this definition.)

An extension type which implements precisely its representation type is a common and reasonable use-case, but it's not special to the extension type feature. It's just another "implements a supertype of the extension type" case that happens to not be a proper supertype.

Copy link
Contributor Author

@MaryaBelanger MaryaBelanger Feb 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm understanding correctly (from this and your other comments on this section) would it be better to say:

"An extension type that implements its representation type, or a supertype of its representation type, can be thought of as transparent, because it provides a kind of overarching access to underlying type members (at least somewhere in the hierarchy / either those of the direct representation type or somewhere higher in the type hierarchy) that is otherwise not the default for the intrinsic isolated nature of extension type interfaces"?

Not that that's exactly what I'll write, but is my understanding correct?

Edit: Or, are you saying that naming this scenario at all is not worthwhile? Maybe it should just be a sub-point of the implements section?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's also possible to have extension types that implement a supertype of their representation type.

True. I did not push on emphasizing this point because I think it's a natural refinement. The crucial message to communicate is that there are two equally valid, but very different, ways to use an extension type: It can be used (1) to extend the interface of an existing type (adding extra members), and (2) to create an unrelated type with an unrelated interface.

An example of the former is FancyString that adds extra methods to String; we surely want to be able to use an expression of this type in all the ways that String supports. An example of the latter is IdNumber that provides an interface which is suitable for ID numbers, but allows us to use plain (and cheap) int objects for that purpose; in this case we definitely do not want to silently include the interface of int as part of the interface of IdNumber.

I think those two modes of operation are so different that they should be established conceptually, thus clearly communicating that each of them is OK, but each of them comes with its own conceptualization and software engineering rules.

It would be helpful to support this conceptualization by means of a well-chosen terminology, and 'transparent' seems fine to me. So FancyString is transparent because we "can see" the underlying type String, and IdNumber is 'not transparent' because it is an important part of being an ID number that it does not do all the int things.

On top of that, we can refine: It's possible for an extension type E to have a representation type R1 and a clause implements ... R2 ... where R1 is a proper subtype of R2, e.g., because R1 is private and R2 defines the part of the interface of R1 that we wish to provide to clients. We can also have a mostly-transparent extension type that adds some new members and adapts others by redeclaring the given name (e.g., in order to use stricter types on some parameters of a method, or different default values, etc). Another way to be mostly transparent is to implement a type which is a proper supertype of the representation type.

My take on the refinements was that there may or may not be room for that level of detail here. If we can help developers to firmly establish the notion of an extension type as transparent or not transparent then it won't be a problem to deal with everything in between.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I think I figured out how to handle this (commit coming up!).

  1. I'll include Lasse's points about the different potential implements scenarios with examples in the "Implements" section. Since that section comes first, this will hopefully set the precedent that we are not implying implementing the representation type or nothing at all are your only options.

  2. Then later on in the "Usage" section, I'll describe Erik's crucial use cases, transparent and non-transparent. I'll include information about the other implements situations in the "transparent" case section to not gatekeep the scenario too much. I'll generally try not to be too strict about "transparency" as a title, but more just "you can think of these as transparent because..." (also based on Erik's comment here).

Thanks for the attention to detail!


An extension type that [implements](#implements) its representation type
is considered *transparent* because it gives the extension type access
to *all* members of the representation type. Otherwise,
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
only the members declared in the extension type body are available to it
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically not true. It can implement a superclass of its representation type, and then the members of that supertype are available, while not necessarily all the members of representation type.

extension type Sequence<T>(List<T> _) implements Iterable<T> {
  // Better operations than List!!!
}

extension type Id(int _id) implements Object {
  static Id? tryParse(String source) => int.tryParse(source) as Id?;
}

Generally, this section assume either implementing the representation type, or implementing nothing.

The other options are implementing a supertype of the representation type (even if just Object, so that the extension type is non-nullable and ExtensionType? becomes useful), and implementing another extension type which has a super-representation type.

My canonical example of the latter is:

extension type const Opt<T>._(({T value})? _) { 
  const factory Opt(T value) = Val<T>;
  const factory Opt.none() = Non<T>;
}
extension type const Val<T>._(({T value}) _) implements Opt<T> { 
  const Val(T value) : this._((value: value));
  T get value => _.value;
}
extension type const Non<T>._(Null _) implements Opt<Never> {
  const Non() : this._(null);
}

(considered a [*somewhat protected*](#type-considerations) extension type).
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved

```dart
extension type NumberT(int value)
implements int {
// Doesn't explicitly declare any members of 'int'.
NumberT get i => NumberT(value);
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
}

void main () {
var num = NumberT(1);
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved

// All OK: Representation type interface is available to extension type:
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
int numT = NumberT(2);
num.i - num; numT + num; num - 1; 2 + num;
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved

// Error: Extension type interface is not available to representation type:
numT.i;
}
```

Transparency is a conceptual decision for the extension type creator to consider.
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
A transparent extension type provides an *extended* interface to an existing type.
You can invoke all the members of the representation type,
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
plus any auxillary members you declare. The new interface is available
to instances of the extension type.
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved

The inverse to transparency is restricting all members of the representation,
providing a totally *different* interface to an existing type.
This means simply not including it in the extension type's implements clause,
and not declaring any of its members in its definition.
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
This is as close as you can get to the complete protection of a wrapper class.
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved

## Type considerations

Extension types are a compile-time wrapping construct.
So, at runtime, there is absolutely no trace of the extension type.
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
Any type query or similar runtime expression works on the representation type.
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved

This makes extension types an *unsafe* abstraction,
because you can always find out the representation type at runtime
and access the underlying object.

Dynamic type tests (`e is T`), casts (`e as T`),
and other runtime type queries (like `switch (e) ...` or `if (e case ...)`)
all evaluate to the underlying representation type.
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
That's true when the static type of `e` is an extension type,
and when testing against an extension type (`case MyExtensionType(): ... `).

```dart
void main() {
var num = NumberE(1);
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved

if (num case int x) { // True because runtime type is representation type.
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
print(x);
}

switch (num) {
case (int x): print(x); // Matches because runtime type is representation type.
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe also show:

  int i = 2;
  if (i is NumberE) print("It is");
  if (i case NumberE v) print("value: ${v.value}");
  switch (i) {
    case NumberE(:var value): print("value: $value");
  }

which shows that the static type of the matched value is NumberE here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a lead-in to this example saying:

Similarly, the static type of the matched value is that of the extension type
in this example:

But I don't think that's makes sense and I don't really understand the example. What are we showing here? Is it just another way to show that NumberE is int at run time? It doesn't seem like "the static type of the matched value (is that i?) is NumberE" to me, but rather that the runtime type of NumberE is int

```

It's important to be aware of this quality when using extension types,
and never rely on them in scenarios where the representation type must be concealed.
The trade off for using an extension type over a more-secure real object (wrapper class)
is their lightweight implementation, which can greatly improve performance in some scenarios.

[extension methods]: /language/extension-methods
[applicability]: https://github.com/dart-lang/language/blob/main/accepted/2.7/static-extension-methods/feature-specification.md#examples
[more specific]: https://github.com/dart-lang/language/blob/main/accepted/2.7/static-extension-methods/feature-specification.md#specificity
[`external`]: /language/functions#external
[abstract members]: /language/methods#abstract-methods
[`is` or `as` check]: /language/operators#type-test-operators
35 changes: 18 additions & 17 deletions src/language/keywords.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,23 @@ The following table lists the words that the Dart language treats specially.
{% assign bii = '&nbsp;<sup title="built-in-identifier" alt="built-in-identifier">2</sup>' %}
{% assign lrw = '&nbsp;<sup title="limited reserved word" alt="limited reserved word">3</sup>' %}
<div class="table-wrapper" markdown="1">
| [abstract][]{{bii}} | [else][] | [import][]{{bii}} | [show][]{{ckw}} |
| [as][]{{bii}} | [enum][] | [in][] | [static][]{{bii}} |
| [assert][] | [export][]{{bii}} | [interface][]{{bii}} | [super][] |
| [async][]{{ckw}} | [extends][] | [is][] | [switch][] |
| [await][]{{lrw}} | [extension][]{{bii}} | [late][]{{bii}} | [sync][]{{ckw}} |
| [base][]{{bii}} | [external][]{{bii}} | [library][]{{bii}} | [this][] |
| [break][] | [factory][]{{bii}} | [mixin][]{{bii}} | [throw][] |
| [case][] | [false][] | [new][] | [true][] |
| [catch][] | [final (variable)][] | [null][] | [try][] |
| [class][] | [final (class)][]{{bii}} | [on][]{{ckw}} | [typedef][]{{bii}} |
| [const][] | [finally][] | [operator][]{{bii}} | [var][] |
| [continue][] | [for][] | [part][]{{bii}} | [void][] |
| [covariant][]{{bii}} | [Function][]{{bii}} | [required][]{{bii}} | [when][] |
| [default][] | [get][]{{bii}} | [rethrow][] | [while][] |
| [deferred][]{{bii}} | [hide][]{{ckw}} | [return][] | [with][] |
| [do][] | [if][] | [sealed][]{{bii}} | [yield][]{{lrw}} |
| [dynamic][]{{bii}} | [implements][]{{bii}} | [set][]{{bii}} | |
| [abstract][]{{bii}} | [else][] | [implements][]{{bii}} | [set][]{{bii}} |
| [as][]{{bii}} | [enum][] | [import][]{{bii}} | [show][]{{ckw}} |
| [assert][] | [export][]{{bii}} | [in][] | [static][]{{bii}} |
| [async][]{{ckw}} | [extends][] | [interface][]{{bii}} | [super][] |
| [await][]{{lrw}} | [extension][]{{bii}} | [is][] | [switch][] |
| [base][]{{bii}} | [extension type][]{{bii}} | [late][]{{bii}} | [sync][]{{ckw}} |
MaryaBelanger marked this conversation as resolved.
Show resolved Hide resolved
| [break][] | [external][]{{bii}} | [library][]{{bii}} | [this][] |
| [case][] | [factory][]{{bii}} | [mixin][]{{bii}} | [throw][] |
| [catch][] | [false][] | [new][] | [true][] |
| [class][] | [final (variable)][] | [null][] | [try][] |
| [const][] | [final (class)][]{{bii}} | [on][]{{ckw}} | [typedef][]{{bii}} |
| [continue][] | [finally][] | [operator][]{{bii}} | [var][] |
| [covariant][]{{bii}} | [for][] | [part][]{{bii}} | [void][] |
| [default][] | [Function][]{{bii}} | [required][]{{bii}} | [when][] |
| [deferred][]{{bii}} | [get][]{{bii}} | [rethrow][] | [while][] |
| [do][] | [hide][]{{ckw}} | [return][] | [with][] |
| [dynamic][]{{bii}} | [if][] | [sealed][]{{bii}} | [yield][]{{lrw}} |
{:.table .table-striped .nowrap}
</div>

Expand All @@ -58,6 +58,7 @@ The following table lists the words that the Dart language treats specially.
[export]: /guides/libraries/create-packages
[extends]: /language/extend
[extension]: /language/extension-methods
[extension type]: /language/extension-types
[external]: /language/functions#external
[factory]: /language/constructors#factory-constructors
[false]: /language/built-in-types#booleans
Expand Down