From b7b2068f16eeaf5ae507907a8ede9bf737302c5b Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Fri, 30 Jun 2023 12:52:07 +0200 Subject: [PATCH 01/29] Modifying inline class feature specification to extension type --- .../feature-specification.md | 0 .../feature-specification-inline-classes.md | 1082 +++++++++++++++++ 2 files changed, 1082 insertions(+) rename accepted/future-releases/{inline-classes => extension-types}/feature-specification.md (100%) create mode 100644 working/1426-extension-types/feature-specification-inline-classes.md diff --git a/accepted/future-releases/inline-classes/feature-specification.md b/accepted/future-releases/extension-types/feature-specification.md similarity index 100% rename from accepted/future-releases/inline-classes/feature-specification.md rename to accepted/future-releases/extension-types/feature-specification.md diff --git a/working/1426-extension-types/feature-specification-inline-classes.md b/working/1426-extension-types/feature-specification-inline-classes.md new file mode 100644 index 0000000000..dde3d646ba --- /dev/null +++ b/working/1426-extension-types/feature-specification-inline-classes.md @@ -0,0 +1,1082 @@ +# Inline Classes + +Author: eernst@google.com + +Status: Accepted. + + +## Change Log + +This document is built on several earlier proposals of a similar feature +(with different terminology and syntax). The most recent version of the +[views][1] proposal as well as the [extension struct][2] proposal provide +information about the process, including in their change logs. + +[1]: https://github.com/dart-lang/language/blob/master/working/1426-extension-types/feature-specification-views.md +[2]: https://github.com/dart-lang/language/blob/master/working/extension_structs/overview.md + +2022.12.20 + - Add rule about type modifiers. + +2022.11.11 + - Initial version, which is a copy of the views proposal, renaming 'view' + to 'inline', and adjusting the text accordingly. Also remove support for + primary constructors. + + +## Summary + +This document specifies a language feature that we call "inline classes". + +The feature introduces _inline classes_, which is a new kind of class +declared by a new `inline class` declaration. An inline class provides a +replacement of the members available on instances of an existing type: +when the static type of the instance is an inline type _V_, the available +instance members are exactly the ones provided by _V_ (noting that +there may also be some accessible and applicable extension members). + +In contrast, when the static type of an instance is not an inline type, +it is (by soundness) always the run-time type of the instance or a +supertype thereof. This means that the available instance members are +the members of the run-time type of the instance, or a subset thereof +(and there may also be some extension members). + +Hence, using a supertype as the static type allows us to see only a +subset of the members. Using an inline type allows us to _replace_ the +set of members, with subsetting as a special case. + +This functionality is entirely static. Invocation of an inline member is +resolved at compile-time, based on the static type of the receiver. + +An inline class may be considered to be a zero-cost abstraction in the +sense that it works similarly to a wrapper object that holds the +wrapped object in a final instance variable. The inline class thus +provides an interface which is chosen independently of the interface +of the wrapped object, and it provides implementations of the members +of the interface of the inline class, and those implementations can use +the members of the wrapped object as needed. + +However, even though an inline class behaves like a wrapping, the wrapper +object will never exist at run time, and a reference whose type is the +inline class will actually refer directly to the underlying wrapped +object. + +Consider a member access (e.g., a method call like `e.m(2)`) +where the static type of the receiver (`e`) is an inline class `V`. +In general, the member (`m`) will be a member of `V`, not a member of +the static type of the wrapped object, and the invocation of that +member will be resolved statically (just like extension methods). +This means that the wrapper object is not actually needed. + +Given that there is no wrapper object, we will refer to the "wrapped" +object as the _representation object_ of the inline class, or just the +_representation_. + +Inside the inline class declaration, the keyword `this` is a reference +to the representation whose static type is the enclosing inline +class. A member access to a member of the enclosing inline class may +rely on `this` being induced implicitly (for example, `foo()` means +`this.foo()` if the inline class contains a method declaration named +`foo`, or it has a superinterface that has a `foo`, and no `foo` +exists in the enclosing top-level scope). In other words, scopes and +`this` have exactly the same interaction as in regular classes. + +A reference to the representation typed by its run-time type or a +supertype thereof (that is, typed by a "normal" type for the +representation) is available as a declared name: The inline class must +have exactly one instance variable whose type is the representation +type, and it must be `final`. + +All in all, an inline class allows us to replace the interface of a given +representation object and specify how to implement the new interface +in terms of the interface of the representation object. + +This is something that we could obviously do with a regular class used +as a wrapper, but when it is done with an inline class there is no +wrapper object, and hence there is no run-time performance cost. In +particular, in the case where we have an inline type `V` with +representation type `R`, we can refer to an object `theRList` of type +`List` using the type `List` (e.g., we could use the cast +`theRList as List`), and this corresponds to "wrapping every +element in the list", but it only takes time _O(1)_ and no space, no +matter how many elements the list contains. + + +## Motivation + +A _inline class_ is a zero-cost abstraction mechanism that allows +developers to replace the set of available operations on a given object +(that is, the instance members of its type) by a different set of +operations (the members declared by the given inline type). + +It is zero-cost in the sense that the value denoted by an expression +whose type is an inline type is an object of a different type (known as +the _representation type_ of the inline type), and there is no wrapper +object, in spite of the fact that the inline class behaves similarly to +a wrapping. + +The point is that the inline type allows for a convenient and safe treatment +of a given object `o` (and objects reachable from `o`) for a specialized +purpose. It is in particular aimed at the situation where that purpose +requires a certain discipline in the use of `o`'s instance methods: We may +call certain methods, but only in specific ways, and other methods should +not be called at all. This kind of added discipline can be enforced by +accessing `o` typed as an inline type, rather than typed as its run-time +type `R` or some supertype of `R` (which is what we normally do). For +example: + +```dart +inline class IdNumber { + final int i; + + IdNumber(this.i); + + // Declare a few members. + + // Assume that it makes sense to compare ID numbers + // because they are allocated with increasing values, + // so "smaller" means "older". + operator <(IdNumber other) => i < other.i; + + // Assume that we can verify an ID number relative to + // `Some parameters`, filtering out some fake ID numbers. + bool verify(Some parameters) => ...; + + ... // Some other members, whatever is needed. + + // We do not declare, e.g., an operator +, because addition + // does not make sense for ID numbers. +} + +void main() { + int myUnsafeId = 42424242; + myUnsafeId = myUnsafeId + 10; // No complaints. + + var safeId = IdNumber(42424242); + + safeId.verify(); // OK, could be true. + safeId + 10; // Compile-time error, no operator `+`. + 10 + safeId; // Compile-time error, wrong argument type. + myUnsafeId = safeId; // Compile-time error, wrong type. + myUnsafeId = safeId as int; // OK, we can force it. + myUnsafeId = safeId.i; // OK, and safer than a cast. +} +``` + +In short, we want an `int` representation, but we want to make sure +that we don't accidentally add ID numbers or multiply them, and we +don't want to silently pass an ID number (e.g., as an actual arguments +or in an assignment) where an `int` is expected. The inline class +`IdNumber` will do all these things. + +We can actually cast away the inline type and hence get access to the +interface of the representation, but we assume that the developer +wishes to maintain this extra discipline, and won't cast away the +inline type unless there is a good reason to do so. Similarly, we can +access the representation using the representation name as a getter. +There is no reason to consider the latter to be a violation of any +kind of encapsulation or protection barrier, it's just like any other +getter invocation. If desired, the author of the inline class can +choose to use a private representation name, to obtain a small amount +of extra encapsulation. + +The extra discipline is enforced because the inline member +implementations will only treat the representation object in ways that +are written with the purpose of conforming to this particular +discipline (and thereby defines what this discipline is). For example, +if the discipline includes the rule that you should never call a +method `foo` on the representation, then the author of the inline class +will simply need to make sure that none of the inline member +declarations ever calls `foo`. + +Another example would be that we're using interop with JavaScript, and +we wish to work on a given `JSObject` representing a button, using a +`Button` interface which is meaningful for buttons. In this case the +implementation of the members of `Button` will call some low-level +functions like `js_util.getProperty`, but a client who uses the inline +class will have a full implementation of the `Button` interface, and +will hence never need to call `js_util.getProperty`. + +(We _can_ just call `js_util.getProperty` anyway, because it accepts +two arguments of type `Object`. But we assume that the developer will +be happy about sticking to the rule that the low-level functions +aren't invoked in application code, and they can do that by using inline +classes like `Button`. It is then easy to `grep` your application code +and verify that it never calls `js_util.getProperty`.) + +Another potential application would be to generate inline class +declarations handling the navigation of dynamic object trees that are +known to satisfy some sort of schema outside the Dart type system. For +instance, they could be JSON values, modeled using `num`, `bool`, +`String`, `List`, and `Map`, and those JSON +values might again be structured according to some schema. + +Without inline types, the JSON value would most likely be handled with +static type `dynamic`, and all operations on it would be unsafe. If the +JSON value is assumed to satisfy a specific schema, then it would be +possible to reason about this dynamic code and navigate the tree correctly +according to the schema. However, the code where this kind of careful +reasoning is required may be fragmented into many different locations, and +there is no help detecting that some of those locations are treating the +tree incorrectly according to the schema. + +If inline classes are available then we can declare a set of inline types +with operations that are tailored to work correctly with the given +schema and its subschemas. This is less error-prone and more +maintainable than the approach where the tree is handled with static +type `dynamic` everywhere. + +Here's an example that shows the core of that scenario. The schema that +we're assuming allows for nested `List` with numbers at the +leaves, and nothing else. + +```dart +inline class TinyJson { + final Object it; + TinyJson(this.it); + Iterable get leaves sync* { + if (it is num) { + yield it; + } else if (it is List) { + for (var element in it) { + yield* TinyJson(element).leaves; + } + } else { + throw "Unexpected object encountered in TinyJson value"; + } + } +} + +void main() { + var tiny = TinyJson([[1, 2], 3, []]); + print(tiny.leaves); + tiny.add("Hello!"); // Error. +} +``` + +Note that `it` is subject to promotion in the above example. This is safe +because there is no way to override this would-be final instance variable. + +An instance creation of an inline type, `Inline(o)`, will evaluate +to a reference to the value of the final instance variable of the +inline class, with the static type `Inline` (and there is no object +at run time that represents the inline class itself). + +Returning to the example, the name `TinyJson` can be used as a type, +and a reference with that type can refer to an instance of the +underlying representation type `Object`. In the example, the inferred +type of `tiny` is `TinyJson`. + +We can now impose an enhanced discipline on the use of `tiny`, because +the inline type allows for invocations of the members of the inline class, +which enables a specific treatment of the underlying instance of +`Object`, consistent with the assumed schema. + +The getter `leaves` is an example of a disciplined use of the given object +structure. The run-time type may be a `List`, but the schema which +is assumed allows only for certain elements in this list (that is, nested +lists or numbers), and in particular it should never be a `String`. The use +of the `add` method on `tiny` would have been allowed if we had used the +type `List` (or `dynamic`) for `tiny`, and that could break the +schema. + +When the type of the receiver is the inline type `TinyJson`, it is a +compile-time error to invoke any members that are not in the interface of +the inline type (in this case that means: the members declared in the +body of `TinyJson`). So it is an error to call `add` on `tiny`, and that +protects us from this kind of schema violations. + +In general, the use of an inline class allows us to keep some unsafe +operations in a specific location (namely inside the inline class, or +inside one of a set of collaborating inline classes). We can then +reason carefully about each operation once and for all. Clients use +the inline class to access objects conforming to the given schema, and +that gives them access to a set of known-safe operations, making all +other operations in the interface of the representation type a +compile-time error. + +One possible perspective is that an inline type corresponds to an abstract +data type: There is an underlying representation, but we wish to restrict +the access to that representation to a set of operations that are +independent of the operations available on the representation. In other +words, the inline type ensures that we only work with the representation in +specific ways, even though the representation itself has an interface that +allows us to do many other (wrong) things. + +It would be straightforward to enforce an added discipline like this by +writing a wrapper class with the allowed operations as members, and +working on a wrapper object rather than accessing the representation +object and its methods directly: + +```dart +// Emulate the inline class using a class. + +class TinyJson { + // The representation is assumed to be a nested list of numbers. + final Object it; + + TinyJson(this.it); + + Iterable get leaves sync* { + var localIt = it; // To get promotion. + if (localIt is num) { + yield localIt; + } else if (localIt is List) { + for (var element in localIt) { + yield* TinyJson(element).leaves; + } + } else { + throw "Unexpected object encountered in TinyJson value"; + } + } +} + +void main() { + var tiny = TinyJson([[1, 2], 3, []]); + print(tiny.leaves); + tiny.add("Hello!"); // Error. +} +``` + +This is similar to the inline type in that it enforces the use of +specific operations only (here we have just one: `leaves`), and it +makes it an error to use instance methods of the representation (e.g., +`add`). + +Creation of wrapper objects consumes space and time. In the case where +we wish to work on an entire data structure, we'd need to wrap each +object as we navigate the data structure. For instance, we need to +create a wrapper `TinyJson(element)` in order to invoke `leaves` +recursively. + +In contrast, the inline class is zero-cost, in the sense that it does +_not_ use a wrapper object, it enforces the desired discipline +statically. In the inline class, the invocation of `TinyJson(element)` +in the body of `leaves` can be eliminated entirely by inlining. + +Inline classes are static in nature, like extension members: an inline +class declaration may declare some type parameters. The type +parameters will be bound to types which are determined by the static +type of the receiver. Similarly, members of an inline type are resolved +statically, i.e., if `tiny.leaves` is an invocation of an inline getter +`leaves`, then the declaration named `leaves` whose body is executed +is determined at compile-time. There is no support for late binding of +an inline member, and hence there is no notion of overriding. In return +for this lack of expressive power, we get improved performance. + + +## Syntax + +A rule for `` is added to the grammar, along +with some rules for elements used in inline class declarations: + +```ebnf + ::= + 'final'? 'inline' 'class' ? ? + '{' + ( )* + '}' + + ::= +``` + +*The token `inline` is not made a built-in identifier: the reserved +word `class` that occurs right after `inline` serves to disambiguate +the inline class declaration with a fixed lookahead.* + +A few errors can be detected immediately from the syntax: + +A compile-time error occurs if the inline class does not declare any +instance variables, and if it declares two or more instance +variables. Let `id` be the name of unique instance variable that it +declares. The declaration of `id` must have the modifier `final`, and it +can not have the modifier `late`; otherwise a compile-time error +occurs. + +The _name of the representation_ in an inline class declaration is the +name `id` of the unique final instance variable that it declares, and +the _type of the representation_ is the declared type of `id`. + +A compile-time error occurs if an inline class declaration declares an +abstract member. + +*There are no special rules for static members in inline classes. They +can be declared and called or torn off as usual, e.g., +`Inline.myStaticMethod(42)`.* + + +## Inline Method Invocations + +This document needs to refer to inline method invocations including +each part that determines the static analysis and semantics of this +invocation, so we will use a standardized phrase and talk about: +An invocation of the inline member `m` on the receiver `e` +according to the inline type `V` and with the actual type arguments +T1, ..., Ts. + +In the case where `m` is a method, the invocation could be an inline +member property extraction (*a tear-off*) or a method invocation, +and in the latter case there will also be an ``, +optionally passing some actual type arguments, and (non-optionally) +passing an actual argument list. + +The case where `m` is a setter is syntactically different, but the +treatment is the same as with a method accepting a single argument +(*so we will not specify that case explicitly*). + +*We need to mention all these elements together, because each of them +plays a role in the static analysis and the dynamic semantics of the +invocation. There is no syntactic representation in the language for +this concept, because the same inline method invocation can have many +different syntactic forms, and both `V` and T1, ..., +Ts are implicit in the actual syntax.* + + +### Static Analysis of an Inline Member Invocation + +We need to introduce a concept that is similar to existing concepts +for regular classes. + +We say that an inline class declaration _DV_ _has_ a member named `n` +in the case where _DV_ declares a member named `n`, and in the case +where _DV_ has no such declaration, but _DV_ has a direct +superinterface `V` that has a member named `n`. In both cases, +_the member declaration named `n` that DV has_ is said declaration. + +*This definition is unambiguous for an inline class that has no +compile-time errors, because name clashes must be resolved by `V`.* + +Consider an invocation of the inline member `m` on the receiver `e` +according to the inline type `V` and with the actual type arguments +T1, ..., Ts. If the invocation +includes an actual argument part (possibly including some actual type +arguments) then call it `args`. Finally, assume that `V` declares the +type variables X1, ..., Xs. + +*Note that it is known that +V<T1, ..., Ts> +has no compile-time errors. In particular, the number of actual type +arguments is correct, and it is a regular-bounded type, +and the static type of `e` is a subtype of +V<T1, ..., Ts>, +or a subtype of the corresponding instantiated representation type +(defined below). This is required when we decide that a given +expression is an inline member invocation.* + +If the name of `m` is a name in the interface of `Object` (*that is, +`toString`, `==`, etc.*), the static analysis of the invocation is +treated as an ordinary instance member invocation on a receiver of +type `Object` and with the same `args`, if any. + +Otherwise, a compile-time error occurs if `V` does not have a member +named `m`. + +Otherwise, let _Dm_ be the declaration of `m` that `V` has. + +If _Dm_ is a getter declaration with return type `R` then the static +type of the invocation is +[T1/X1 .. Ts/Xs]R. + +If _Dm_is a method with function type `F`, and `args` is omitted, the +invocation has static type +[T1/X1 .. Ts/Xs]F. +*This is an inline method tear-off.* + +If _Dm_ is a method with function type `F`, and `args` exists, the +static analysis of the inline member invocation is the same as that of +an invocation with argument part `args` of a function with type +[T1/X1 .. Ts/Xs]F. +*This determines the compile-time errors, if any, and it determines +the type of the invocation as a whole.* + + +### Dynamic Semantics of an Inline Member Invocation + +Consider an invocation of the inline member `m` on the receiver `e` +according to the inline type `V` and with actual type arguments +T1, ..., Ts. If the invocation +includes an actual argument part (possibly including some actual type +arguments) then call it `args`. Assume that `V` declares the +type variables X1, ..., Xs. + +Let _Dm_ be the declaration named `m` thath `V` has. + +Evaluation of this invocation proceeds by evaluating `e` to an object +`o`. + +Then, if `args` is omitted and _Dm_ is a getter, execute the body of +said getter in an environment where `this` and the name of the +representation are bound to `o`, and the type variables of `V` are +bound to the actual values of +T1, .. Ts. +If the body completes returning an object `o2` then the invocation +evaluates to `o2`. If the body throws an object and a stack trace +then the invocation completes throwing the same object and stack +trace. + +Otherwise, if `args` is omitted and _Dm_ is a method, the invocation +evaluates to a closurization of _Dm_ where +`this` and the name of the representation are bound to `o`, and the +type variables of `V` are bound to the actual values of +T1, .. Ts. +The operator `==` of the closurization returns true if and only if the +operand is the same object. + +*Loosely said, these function objects simply use the equality +inherited from `Object`, there are no special exceptions. Note that +we can tear off the same method from the same inline class with the +same representation object twice, and still get different behavior, +because the inline type had different actual type arguments. Hence, +we can not consider two inline method tear-offs equal just because +they have the same receiver.* + +Otherwise, the following is known: `args` is included, and _Dm_ is a +method. The invocation proceeds to evaluate `args` to an actual +argument list `args1`. Then it executes the body of _Dm_ in an +environment where `this` and the name of the representation are bound +to `o`, the type variables of `V` are bound to the actual values of +T1, .. Ts, +and the formal parameters of `m` are bound to `args1` in the same way +that they would be bound for a normal function call. +If the body completes returning an object `o2` then the invocation +evaluates to `o2`. If the body throws an object and a stack trace +then the invocation completes throwing the same object and stack +trace. + + +## Static Analysis of Inline Classes + +The unique instance variable declared by an inline class must have a +type annotation. + +*In particular, the type of this variable cannot be obtained by +inference. An important part of the rationale for this rule is that +the representation type of an inline class plays a role in the +semantics of the class which is broader and more significant than that +of an instance variable in a normal class or mixin, and hence it +should be documented explicitly.* + +Assume that +T1, .. Ts +are types, and `V` resolves to an inline class declaration of the +following form: + +```dart +inline class V ... { + final T id; + V(this.id); + + ... // Other members. +} +``` + +It is then allowed to use +V<T1, .. Ts> +as a type. + +*For example, it can occur as the declared type of a variable or +parameter, as the return type of a function or getter, as a type +argument in a type, as the representation type of an inline class, as +the on-type of an extension, as the type in the `onPart` of a +try/catch statement, in a type test `o is V<...>`, in a type cast +`o as V<...>`, or as the body of a type alias. It is also allowed to +create a new instance where one or more inline types occur as type +arguments (e.g., `List.empty()`).* + +A compile-time error occurs if the type +V<T1, .. Ts> +is not regular-bounded. + +*In other words, such types can not be super-bounded. The reason for this +restriction is that it is unsound to execute code in the body of `V` in +the case where the values of the type variables do not satisfy their +declared bounds, and those values will be obtained directly from the static +type of the receiver in each member invocation on `V`.* + +A compile-time error occurs if a type parameter of an inline class +occurs in a non-covariant position in the representation type. + +When `s` is zero, +V<T1, .. Ts> +simply stands for `V`, a non-generic inline type. +When `s` is greater than zero, a raw occurrence `V` is treated like a raw +type: Instantiation to bound is used to obtain the omitted type arguments. +*Note that this may yield a super-bounded type, which is then a +compile-time error.* + +We say that the static type of said variable, parameter, etc. +_is the inline type_ +V<T1, .. Ts>, +and that its static type _is an inline type_. + +It is a compile-time error if `await e` occurs, and the static type of +`e` is an inline type. + +*We may loosen this restriction in the future, especially if we +introduce a way for an inline type to be a subtype of a type of the +form `Future` or `FutureOr`.* + +A compile-time error occurs if an inline type declares a member whose +name is declared by `Object` as well. + +*For example, an inline class cannot define an operator `==` or a +member named `noSuchMethod` or `toString`. The rationale is that these +inline methods would be highly confusing and error prone. For example, +collections would still call the instance operator `==`, not the inline +class operator, and string interpolation would call the instance +method `toString`, not the inline class method. Also, when we make +this an error for now, we have the option to allow it, perhaps with +some restrictions, in a future version of Dart.* + +A compile-time error occurs if an inline type is used as a +superinterface of a class or a mixin, or if an inline type is used to +derive a mixin. + +*In other words, an inline type cannot occur as a superinterface in an +`extends`, `with`, `implements`, or `on` clause of a class or mixin. +On the other hand, it can occur in other ways, e.g., as a type +argument of a superinterface of a class.* + +If `e` is an expression whose static type `V` is the inline type +Inline<T1, .. Ts> +and `m` is the name of a member that `V` has, a member access +like `e.m(args)` is treated as +an invocation of the inline member `m` on the receiver `e` +according to the inline type `Inline` and with the actual type arguments +T1, ..., Ts, with the actual +argument part `args`. + +Similarly, `e.m` is treated an invocation of the inline member `m` on +the receiver `e` according to the inline type `Inline` and with the +actual type arguments +T1, ..., Ts +and no actual argument part. + +*Setter invocations are treated as invocations of methods with a +single argument.* + +If `e` is an expression whose static type `V` is the inline type +Inline<T1, .. Ts> +and `V` has no member whose basename is the basename of `m`, a member +access like `e.m(args)` may be an extension member access, following +the normal rules about applicability and accessibility of extensions, +in particular that `V` must match the on-type of the extension. + +*In the body of an inline class declaration _DV_ with name `Inline` +and type parameters +X1, .. Xs, for an invocation like +`m(args)`, if a declaration named `m` is found in the body of _DV_ +then that invocation is treated as +an invocation of the inline member `m` on the receiver `this` +according to the inline type `Inline` and with the actual type arguments +T1, ..., Ts, with the actual +argument part `args`. +This is just the same treatment of `this` as in the body of a class.* + +*For example:* + +```dart +extension E1 on int { + void foo() { print('E1.foo'); } +} + +inline class V1 { + final int it; + V1(this.it); + void foo() { print('V1.foo'); } + void baz() { print('V1.baz'); } + void qux() { print('V1.qux'); } +} + +void qux() { print('qux'); } + +inline class V2 { + final V1 it; + V2(this.it); + void foo() { print('V2.foo); } + void bar() { + foo(); // Prints 'V2.foo'. + it.foo(); // Prints 'V1.foo'. + it.baz(); // Prints 'V1.baz'. + 1.foo(); // Prints 'E1.foo'. + 1.baz(); // Compile-time error. + qux(); // Prints 'qux'. + } +} +``` + +*That is, when the static type of an expression is an inline type `V` +with representation type `R`, each method invocation on that +expression will invoke an instance method declared by `V` or inherited +from a superinterface (or it could be an extension method with on-type +`V`). Similarly for other member accesses.* + +Let _DV_ be an inline class declaration named `Inline` with type +parameters +X1 extends B1, .. Xs extends Bs. +Assume that _DV_ declares a final instance variable with name `id` and +type `R`. + +We say that the _declared representation type_ of `Inline` +is `R`, and the _instantiated representation type_ corresponding to +Inline<T1,.. Ts> is +[T1/X1, .. Ts/Xs]R. + +We will omit 'declared' and 'instantiated' from the phrase when it is +clear from the context whether we are talking about the inline class +itself, or we're talking about a particular instantiation of a generic +inline class. *For non-generic inline classes, the representation type +is the same in either case.* + +Let `V` be an inline type of the form +Inline<T1, .. Ts>, and let +`R` be the corresponding instantiated representation type. If `R` is +non-nullable then `V` is a proper subtype of `Object`, and `V` is +non-nullable. Otherwise, `V` is a proper subtype of `Object?`, and +`V` is potentially nullable. + +*That is, an expression of an inline type can be assigned to a top +type (like all other expressions), and if the representation type is +non-nullable then it can also be assigned to `Object`. Non-inline +types (except bottom types) cannot be assigned to inline types without +a cast. Similarly, null cannot be assigned to an inline type without a +cast, even in the case where the representation type is nullable (even +better: don't use a cast, call a constructor instead). Another consequence of +the fact that the inline type is potentially non-nullable is that it +is an error to have an instance variable whose type is an inline type, +and then relying on implicit initialization to null.* + +In the body of a member of an inline class declaration _DV_ named +`Inline` and declaring the type parameters +X1, .. Xs, +the static type of `this` is +Inline<X1 .. Xs>. +The static type of the representation name is the representation +type. + +*For example, in `inline class V { final R id; ...}`, `id` has type +`R`, and `this` has type `V`.* + +Let _DV_ be an inline class declaration named `V` with representation +type `R`. Assuming that all types have been fully alias expanded, we +say that _DV_ has a representation dependency on an inline class +declaration _DV2_ if `R` contains an identifier `id` (possibly +qualified) that resolves to _DV2_, or `id` resolves to an inline class +declaration _DV3_ and _DV3_ has a representation dependency on _DV2_. + +It is a compile-time error if an inline class declaration has a +representation dependency on itself. + +*In other words, cycles are not allowed. This ensures that it is +always possible to find a non-inline type which is the ultimate +representation type of any given inline type.* + +The *inline erasure* of an inline type `V` is obtained by recursively +replacing every subterm of `V` which is an inline type by the +corresponding representation type. + +*Note that this inline erasure exists, because it is a compile-time +error to have a dependency cycle among inline types.* + +Let +X1 extends B1, .. Xs extends Bs +be a declaration of the type parameters of a generic entity (*it could +be a generic class, inline or not, or mixin, or typedef, or function*). +Let BBj be the inline erasure of +Bj, for _j_ in _1 .. s_. +It is a compile-time error if +X1 extends BB1, .. Xs extends BBs +has any compile-time errors. + +*For example, the inline erasure could map +X extends C, Y extends X to +X extends Y, Y extends X, +which is an error.* + +An inline class declaration _DV_ named `Inline` may declare one or +more constructors. A constructor which is declared in an inline class +declaration is also known as an _inline class constructor_. + +*The purpose of having an inline class constructor is that it bundles +an approach for building an instance of the representation type of an +inline declaration _DV_ with _DV_ itself, which makes it easy to +recognize that this is a way to obtain a value of that inline type. It +can also be used to verify that an existing object (provided as an +actual argument to the constructor) satisfies the requirements for +having that inline type.* + +A compile-time error occurs if an inline class constructor includes a +superinitializer. *That is, a term of the form `super(...)` or +`super.id(...)` as the last element of the initializer list.* + +A compile-time error occurs if an inline class constructor declares a +super parameter. *For instance, `Inline(super.x);`.* + +*In the body of a generative inline class constructor, the static type +of `this` is the same as it is in any instance member of the inline +class, that is, `Inline`, where `X1 .. Xk` are the type +parameters declared by `Inline`.* + +An instance creation expression of the form +Inline<T1, .. Ts>(...) +or +Inline<T1, .. Ts>.name(...) +is used to invoke these constructors, and the type of such an expression is +Inline<T1, .. Ts>. + +*In short, inline class constructors appear to be very similar to +constructors in regular classes, and they correspond to the situation +where the enclosing class has a single, non-late, final instance +variable, which is initialized according to the normal rules for +constructors (in particular, it can occur by means of `this.id`, or in +an initializer list, or by an initializing expression in the +declaration itself, but it is an error if it does not occur at all).* + +An inline type `V` used as an expression (*a type literal*) is allowed +and has static type `Type`. + +An inline class declaration _DV_ can have the type modifier +`final`. In this case it is a compile-time error for another inline +class declaration _DV2_ to have a superinterface which is a reference +to _DV_. + +*As the grammar shows, any occurrence of the keywords `abstract`, +`base`, `interface`, `sealed`, or `mixin` in an inline class +declaration is a syntax error.* + + +### Composing Inline Classes + +This section describes the effect of including a clause derived from +`` in an inline class declaration. We use the phrase +_the implements clause_ to refer to this clause. + +*The rationale is that the set of members and member implementations +of a given inline class may need to overlap with that of other inline +classes. The implements clause allows for implementation reuse by +putting some shared members in an inline class `V`, and including `V` +in the implements clause of several inline class declarations +V1 .. Vk, thus "inheriting" the +members of `V` into all of V1 .. Vk +without code duplication.* + +*The reason why this mechanism uses the keyword `implements` rather +than `extends` to declare a relation that involves "inheritance" is +that it has a similar semantics as that of extension members (in that +they are statically resolved).* + +Assume that _DV_ is an inline class declaration named `Inline`, and +`V1` occurs as one of the ``s in the `` of _DV_. In +this case we say that `V1` is a _superinterface_ of _DV_. + +If _DV_ does not include an `` clause then _DV_ has no +superinterfaces. + +A compile-time error occurs if `V1` is a type name or a parameterized +type which occurs as a superinterface in an inline class declaration +_DV_, but `V1` does not denote an inline type. + +A compile-time error occurs if any direct or indirect superinterface +of _DV_ is the type `Inline` or a type of the form `Inline<...>`. *As +usual, subtype cycles are not allowed.* + +Assume that _DV_ has two direct or indirect superinterface of the form +W<T1, .. Tk> +respectively +W<S1, .. Sk>. +A compile-time error +occurs if +Tj +is not equal to +Sj +for any _j_ in _1 .. k_. The notion of equality used here is the same +as with the corresponding rule about superinterfaces of classes. + +Assume that an inline class declaration _DV_ named `Inline` has +representation type `R`, and that the inline type `V1` with +declaration _DV1_ is a superinterface of _DV_ (*note that `V1` may +have some actual type arguments*). Assume that `S` is the +instantiated representation type corresponding to `V1`. A compile-time +error occurs if `R` is not a subtype of `S`. + +*This ensures that it is sound to bind the value of `id` in _DV_ to `id1` +in `V1` when invoking members of `V1`, where `id` is the representation +name of _DV_ and `id1` is the representation name of _DV1_.* + +Assume that _DV_ declares an inline class named `Inline` with type +parameters +X1 .. Xs, +and `V1` is a superinterface of _DV_. Then +Inline<T1, .. Ts> +is a subtype of +[T1/X1 .. Ts/Xs]V1 +for all T1, .. Ts. + +*In short, if `V1` is a superinterface of `V` then `V1` is also a +supertype of `V`.* + +A compile-time error occurs if an inline class declaration _DV_ has +two superinterfaces `V1` and `V2`, where both `V1` and `V2` have a +member named _m_, and the two declarations of _m_ are distinct +declarations, and _DV_ does not declare a member named _m_. + +*In other words, if two different declarations of _m_ are inherited +from two superinterfaces then the subinterface must resolve the +conflict. The so-called diamond inheritance pattern can create the +case where two superinterfaces have an _m_, but they are both declared by +the same declaration (so `V` is a subinterface of `V1` and `V2`, and both +`V1` and `V2` are subinterfaces of `V3`, and only `V3` declares _m_, +in which case there is no conflict in `V`).* + +*Assume that _DV_ is an inline class declaration named `Inline`, and +the inline type `V1`, declared by _DV1_, is a superinterface of +_DV_. Let `m` be the name of a member of `V1`. If _DV_ also declares a +member named `m` then the latter may be considered similar to a +declaration that "overrides" the former. However, it should be noted +that inline method invocation is resolved statically, and hence there +is no override relationship among the two in the traditional +object-oriented sense (that is, it will never occur that the +statically known declaration is the member of `V1`, and the member +invoked at run time is the one in _DV_). A receiver with static +type `V1` will invoke the declaration in _DV1_, and a receiver with +static type `Inline` (or `Inline<...>`) will invoke the one in _DV_.* + +Hence, we use a different word to describe the relationship between a +member named _m_ of a superinterface, and a member named _m_ which is +declared by the subinterface: We say that the latter _redeclares_ the +former. + +*In particular, if two different declarations of _m_ is inherited +from two superinterface then the subinterface can resolve the conflict +by redeclaring _m_.* + +*Note that there is no notion of having a 'correct override relation' +here. With inline classes, any member signature can redeclare any +other member signature with the same name, including the case where a +method is redeclared by a getter, or vice versa. The reason for this +is that no call site will resolve to one of several declarations at +run time. Each invocation will statically resolve to one particular +declaration, and this makes it possible to ensure that the invocation +is type correct.* + +Assume that _DV_ is an inline class declaration, and that the inline +types `V1` and `V2` are superinterfaces of _DV_. Let `M1` be the +members of `V1`, and `M2` the members of `V2`. A compile-time error +occurs if there is a member name `m` such that `V1` as well as `V2` +has a member named `m`, and they are distinct declarations, and _DV_ +does not declare a member named `m`. *In other words, a name clash +among distinct "inherited" members is an error, but it can be +eliminated by redeclaring the clashing name.* + +The effect of having an inline class declaration _DV_ with +superinterfaces `V1, .. Vk` is that the members declared by _DV_ as +well as all members of `V1, .. Vk` that are not redeclared by a +declaration in _DV_ can be invoked on a receiver of the type +introduced by _DV_. + + +## Dynamic Semantics of Inline Classes + +For any given syntactic construct which has been characterized as an +inline member invocation during the static analysis, the dynamic +semantics of the construct is the dynamic semantics of said +inline member invocation. + +Consider an inline class declaration _DV_ named `Inline` with +representation name `id` and representation type `R`. Invocation of a +non-redirecting generative inline class constructor proceeds as follows: A +fresh, non-late, final variable `v` is created. An initializing formal +`this.id` has the side-effect that it initializes `v` to the actual +argument passed to this formal. An initializer list element of the +form `id = e` or `this.id = e` is evaluated by evaluating `e` to an +object `o` and binding `v` to `o`. During the execution of the +constructor body, `this` and `id` are bound to the value of `v`. The +value of the instance creation expression that gave rise to this +constructor execution is the value of `this`. + +At run time, for a given instance `o` typed as an inline type `V`, there +is _no_ reification of `V` associated with `o`. + +*This means that, at run time, an object never "knows" that it is being +viewed as having an inline type. By soundness, the run-time type of `o` +will be a subtype of the representation type of `V`.* + +The run-time representation of a type argument which is an inline type +`V` is the corresponding instantiated representation type. + +*This means that an inline type and the underlying representation type +are considered as being the same type at run time. So we can freely +use a cast to introduce or discard the inline type, as the static type +of an instance, or as a type argument in the static type of a data +structure or function involving the inline type.* + +A type test, `o is U` or `o is! U`, and a type cast, `o as U`, where +`U` is or contains an inline type, is performed at run time as a type +test and type cast on the run-time representation of the inline type +as described above. + +An inline type `V` used as an expression (*a type literal*) evaluates +to the value of the corresponding instantiated representation type +used as an expression. + + +### Summary of Typing Relationships + +*Here is an overview of the subtype relationships of an inline type +`V0` with instantiated representation type `R` and superinterfaces +`V1 .. Vk`, as well as other typing relationships involving `V0`:* + +- *`V0` is a proper subtype of `Object?`.* +- *`V0` is a supertype of `Never`.* +- *If `R` is a non-nullable type then `V0` is a proper subtype of + `Object`, and a non-nullable type.* +- *`V0` is a proper subtype of each of `V1 .. Vk`.* +- *At run time, the type `V0` is identical to the type `R`. In + particular, `o is V0` and `o as V0` have the same dynamic + semantics as `o is R` respectively `o as R`, and + `t1 == t2` evaluates to true if `t1` is a `Type` that reifies + `V0` and `t2` reifies `R`, and the equality also holds if + `t1` and `t2` reify types where `V0` and `R` occur as subterms + (e.g., `List` is equal to `List`).* + + +## Discussion + +This section mentions a few topics that have given rise to +discussions. + + +### Support "private inheritance"? + +In the current proposal there is a subtype relationship between every +inline class and each of its superinterfaces. So if we have +`inline class V implements V1, V2 ...` then `V <: V1` and `V <: V2`. + +In some cases it might be preferable to omit the subtype relationship, +even though there is a code reuse element (because `V1` is a +superinterface of `V`, we just don't need or want `V <: V1`). + +A possible workaround would be to write forwarding methods manually: + +```dart +inline class V1 { + final R it; + V1(this.it); + void foo() {...} +} + +// `V` can reuse code from `V1` by using `implements`. Note that +// `S <: R`, because otherwise it is a compile-time error. +inline class V implements V1 { + final S it; + V(this.it); +} + +// Alternatively, we can write forwarders, in order to avoid +// having the subtype relationship `V <: V1`. +inline class V { + final S it; + V(this.it); + void foo() => V1(it).foo(); +} +``` From 37bc301ea225db7f534040e3da281e8e90b01c93 Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Fri, 30 Jun 2023 14:32:54 +0200 Subject: [PATCH 02/29] Proposal for extension type feature specification --- .../extension-types/feature-specification.md | 861 +++++++++--------- 1 file changed, 426 insertions(+), 435 deletions(-) diff --git a/accepted/future-releases/extension-types/feature-specification.md b/accepted/future-releases/extension-types/feature-specification.md index dde3d646ba..07f9e3e522 100644 --- a/accepted/future-releases/extension-types/feature-specification.md +++ b/accepted/future-releases/extension-types/feature-specification.md @@ -1,4 +1,4 @@ -# Inline Classes +# Extension Types Author: eernst@google.com @@ -15,6 +15,11 @@ information about the process, including in their change logs. [1]: https://github.com/dart-lang/language/blob/master/working/1426-extension-types/feature-specification-views.md [2]: https://github.com/dart-lang/language/blob/master/working/extension_structs/overview.md +2023.06.30 + - Change the feature name and keywords to `extension type`, adjust + representation type and name declaration to be similar to a primary + constructor. + 2022.12.20 - Add rule about type modifiers. @@ -26,75 +31,93 @@ information about the process, including in their change logs. ## Summary -This document specifies a language feature that we call "inline classes". +This document specifies a language feature that we call "extension types". -The feature introduces _inline classes_, which is a new kind of class -declared by a new `inline class` declaration. An inline class provides a -replacement of the members available on instances of an existing type: -when the static type of the instance is an inline type _V_, the available -instance members are exactly the ones provided by _V_ (noting that -there may also be some accessible and applicable extension members). +The feature introduces the _extension type_ feature. Is feature introduces a new +kind of type which is declared by a new `extension type` declaration. An +extension type provides a replacement of the members available on instances +of an existing type: when the static type of the instance is an extension +type _V_, the available instance members are exactly the ones provided by +_V_ (noting that there may also be some accessible and applicable extension +members). -In contrast, when the static type of an instance is not an inline type, +In contrast, when the static type of an instance is not an extension type, it is (by soundness) always the run-time type of the instance or a -supertype thereof. This means that the available instance members are -the members of the run-time type of the instance, or a subset thereof -(and there may also be some extension members). +supertype thereof. This means that the available instance members are the +members of the run-time type of the instance, or a subset thereof (and +there may also be some extension members). Hence, using a supertype as the static type allows us to see only a -subset of the members. Using an inline type allows us to _replace_ the -set of members, with subsetting as a special case. - -This functionality is entirely static. Invocation of an inline member is -resolved at compile-time, based on the static type of the receiver. - -An inline class may be considered to be a zero-cost abstraction in the -sense that it works similarly to a wrapper object that holds the -wrapped object in a final instance variable. The inline class thus -provides an interface which is chosen independently of the interface -of the wrapped object, and it provides implementations of the members -of the interface of the inline class, and those implementations can use -the members of the wrapped object as needed. - -However, even though an inline class behaves like a wrapping, the wrapper +subset of the members, possibly with a more general member signature. Using +an extension type allows us to _replace_ the set of members, with +subsetting as a special case. + +This functionality is entirely static. Invocation of an extension type +member is resolved at compile-time, based on the static type of the +receiver. + +An extension type may be considered to be a zero-cost abstraction in the +sense that it works similarly to a wrapper object that holds the wrapped +object in a final instance variable. The extension type thus provides an +interface which is chosen independently of the interface of the wrapped +object, and it provides implementations of the members of the interface of +the extension type, and those implementations can use the members of the +wrapped object as needed. + +However, even though an extension type behaves like a wrapping, the wrapper object will never exist at run time, and a reference whose type is the -inline class will actually refer directly to the underlying wrapped +extension type will actually refer directly to the underlying wrapped object. Consider a member access (e.g., a method call like `e.m(2)`) -where the static type of the receiver (`e`) is an inline class `V`. +where the static type of the receiver (`e`) is an extension type `V`. In general, the member (`m`) will be a member of `V`, not a member of the static type of the wrapped object, and the invocation of that member will be resolved statically (just like extension methods). -This means that the wrapper object is not actually needed. Given that there is no wrapper object, we will refer to the "wrapped" -object as the _representation object_ of the inline class, or just the +object as the _representation object_ of the extension type, or just the _representation_. -Inside the inline class declaration, the keyword `this` is a reference -to the representation whose static type is the enclosing inline -class. A member access to a member of the enclosing inline class may -rely on `this` being induced implicitly (for example, `foo()` means -`this.foo()` if the inline class contains a method declaration named -`foo`, or it has a superinterface that has a `foo`, and no `foo` -exists in the enclosing top-level scope). In other words, scopes and -`this` have exactly the same interaction as in regular classes. - -A reference to the representation typed by its run-time type or a +The mechanism has many traits in common with classes, and many traits in +common with regular `extension` declarations (aka extension methods). The +fact that it is called `extension type` serves as a reminder that member +invocations are similar to extension methods, which is a property that has +so deep implications that it should be kept in mind at all times when +creating or using an extension type: There is never a dynamic dispatch +step, an extension type member invocation is resolved at compile time, and +the dynamic type of the underlying representation object makes no +difference at all. On the other hand, an extension type is similar to a +class in that it has constructors, and in the treatment of `this`. + +Inside the extension type declaration, the keyword `this` is a reference to +the representation whose static type is the enclosing extension type. A +member access to a member of the enclosing extension type may rely on `this` +being induced implicitly (for example, `foo()` means `this.foo()` if the +extension type contains a method declaration named `foo`, or it has a +superinterface that has a `foo`, and no `foo` exists in the enclosing +top-level scope). In other words, scopes and `this` have exactly the same +interaction as in a regular class. + +A reference to the representation object typed by its run-time type or a supertype thereof (that is, typed by a "normal" type for the -representation) is available as a declared name: The inline class must -have exactly one instance variable whose type is the representation -type, and it must be `final`. +representation) is available as a declared name: The extension type +declares the name and type of the representation in a way which is a +special case of the [primary constructor proposal][]. +In the body of the extension type the representation object is in scope, +with the declared name and type, as if it had been a final instance +variable in a class. + +[primary constructor proposal]: https://github.com/dart-lang/language/pull/3023 -All in all, an inline class allows us to replace the interface of a given -representation object and specify how to implement the new interface -in terms of the interface of the representation object. +All in all, an extension type allows us to replace the interface of a given +representation object and specify how to implement the new interface in +terms of the interface of the representation object. This is something that we could obviously do with a regular class used -as a wrapper, but when it is done with an inline class there is no +as a wrapper, but when it is done with an extension type there is no wrapper object, and hence there is no run-time performance cost. In -particular, in the case where we have an inline type `V` with +particular, in the case where we have an extension type `V` with representation type `R`, we can refer to an object `theRList` of type `List` using the type `List` (e.g., we could use the cast `theRList as List`), and this corresponds to "wrapping every @@ -104,35 +127,29 @@ matter how many elements the list contains. ## Motivation -A _inline class_ is a zero-cost abstraction mechanism that allows -developers to replace the set of available operations on a given object -(that is, the instance members of its type) by a different set of -operations (the members declared by the given inline type). - -It is zero-cost in the sense that the value denoted by an expression -whose type is an inline type is an object of a different type (known as -the _representation type_ of the inline type), and there is no wrapper -object, in spite of the fact that the inline class behaves similarly to -a wrapping. - -The point is that the inline type allows for a convenient and safe treatment -of a given object `o` (and objects reachable from `o`) for a specialized -purpose. It is in particular aimed at the situation where that purpose -requires a certain discipline in the use of `o`'s instance methods: We may -call certain methods, but only in specific ways, and other methods should -not be called at all. This kind of added discipline can be enforced by -accessing `o` typed as an inline type, rather than typed as its run-time -type `R` or some supertype of `R` (which is what we normally do). For -example: +An _extension type_ declaration is a zero-cost abstraction mechanism that +allows developers to replace the set of available operations on a given +object (that is, the instance members of its type) by a different set of +operations (the members declared by the given extension type declaration). + +It is zero-cost in the sense that the value denoted by an expression whose +type is an extension type is an object of a different type (known as the +_representation type_ of the extension type), and there is no wrapper +object, in spite of the fact that the extension type declaration behaves +similarly to a wrapping. + +The point is that the extension type allows for a convenient and safe +treatment of a given object `o` (and objects reachable from `o`) for a +specialized purpose. It is in particular aimed at the situation where that +purpose requires a certain discipline in the use of `o`'s instance methods: +We may call certain methods, but only in specific ways, and other methods +should not be called at all. This kind of added discipline can be enforced +by accessing `o` typed as an extension type, rather than typed as its +run-time type `R` or some supertype of `R` (which is what we normally +do). For example: ```dart -inline class IdNumber { - final int i; - - IdNumber(this.i); - - // Declare a few members. - +extension type IdNumber(int i) { // Assume that it makes sense to compare ID numbers // because they are allocated with increasing values, // so "smaller" means "older". @@ -166,52 +183,52 @@ void main() { In short, we want an `int` representation, but we want to make sure that we don't accidentally add ID numbers or multiply them, and we don't want to silently pass an ID number (e.g., as an actual arguments -or in an assignment) where an `int` is expected. The inline class +or in an assignment) where an `int` is expected. The extension type `IdNumber` will do all these things. -We can actually cast away the inline type and hence get access to the -interface of the representation, but we assume that the developer -wishes to maintain this extra discipline, and won't cast away the -inline type unless there is a good reason to do so. Similarly, we can -access the representation using the representation name as a getter. -There is no reason to consider the latter to be a violation of any -kind of encapsulation or protection barrier, it's just like any other -getter invocation. If desired, the author of the inline class can -choose to use a private representation name, to obtain a small amount -of extra encapsulation. - -The extra discipline is enforced because the inline member +We can actually cast away the extension type and hence get access to the +interface of the representation, but we assume that the developer wishes to +maintain this extra discipline, and won't cast away the extension type +unless there is a good reason to do so. Similarly, we can access the +representation using the representation name as a getter. There is no +reason to consider the latter to be a violation of any kind of +encapsulation or protection barrier, it's just like any other getter +invocation. If desired, the author of the extension type can choose to use +a private representation name, to obtain a small amount of extra +encapsulation. + +The extra discipline is enforced because the extension type member implementations will only treat the representation object in ways that are written with the purpose of conforming to this particular discipline (and thereby defines what this discipline is). For example, if the discipline includes the rule that you should never call a -method `foo` on the representation, then the author of the inline class -will simply need to make sure that none of the inline member +method `foo` on the representation, then the author of the extension type +will simply need to make sure that none of the extension type member declarations ever calls `foo`. Another example would be that we're using interop with JavaScript, and we wish to work on a given `JSObject` representing a button, using a `Button` interface which is meaningful for buttons. In this case the implementation of the members of `Button` will call some low-level -functions like `js_util.getProperty`, but a client who uses the inline -class will have a full implementation of the `Button` interface, and +functions like `js_util.getProperty`, but a client who uses the extension +type will have a full implementation of the `Button` interface, and will hence never need to call `js_util.getProperty`. -(We _can_ just call `js_util.getProperty` anyway, because it accepts -two arguments of type `Object`. But we assume that the developer will -be happy about sticking to the rule that the low-level functions -aren't invoked in application code, and they can do that by using inline -classes like `Button`. It is then easy to `grep` your application code -and verify that it never calls `js_util.getProperty`.) +(We _can_ just call `js_util.getProperty` anyway, because it accepts two +arguments of type `Object`. But we assume that the developer will be happy +about sticking to the rule that the low-level functions aren't invoked in +application code, and they can do that by using extension types like +`Button`. It is then easy to `grep` your application code and verify that +it never calls `js_util.getProperty`.) -Another potential application would be to generate inline class +Another potential application would be to generate extension type declarations handling the navigation of dynamic object trees that are known to satisfy some sort of schema outside the Dart type system. For instance, they could be JSON values, modeled using `num`, `bool`, `String`, `List`, and `Map`, and those JSON values might again be structured according to some schema. -Without inline types, the JSON value would most likely be handled with +Without extension types, the JSON value would most likely be handled with static type `dynamic`, and all operations on it would be unsafe. If the JSON value is assumed to satisfy a specific schema, then it would be possible to reason about this dynamic code and navigate the tree correctly @@ -220,20 +237,18 @@ reasoning is required may be fragmented into many different locations, and there is no help detecting that some of those locations are treating the tree incorrectly according to the schema. -If inline classes are available then we can declare a set of inline types -with operations that are tailored to work correctly with the given -schema and its subschemas. This is less error-prone and more -maintainable than the approach where the tree is handled with static -type `dynamic` everywhere. +If extension types are available then we can declare a set of extension +types with operations that are tailored to work correctly with the given +schema and its subschemas. This is less error-prone and more maintainable +than the approach where the tree is handled with static type `dynamic` +everywhere. Here's an example that shows the core of that scenario. The schema that we're assuming allows for nested `List` with numbers at the leaves, and nothing else. ```dart -inline class TinyJson { - final Object it; - TinyJson(this.it); +extension type TinyJson(Object it) { Iterable get leaves sync* { if (it is num) { yield it; @@ -257,20 +272,19 @@ void main() { Note that `it` is subject to promotion in the above example. This is safe because there is no way to override this would-be final instance variable. -An instance creation of an inline type, `Inline(o)`, will evaluate -to a reference to the value of the final instance variable of the -inline class, with the static type `Inline` (and there is no object -at run time that represents the inline class itself). +An instance creation of an extension type, `V(o)`, will evaluate to a +reference to the representation object, with the static type `V` (and +there is no object at run time that represents the extension type itself). Returning to the example, the name `TinyJson` can be used as a type, and a reference with that type can refer to an instance of the underlying representation type `Object`. In the example, the inferred type of `tiny` is `TinyJson`. -We can now impose an enhanced discipline on the use of `tiny`, because -the inline type allows for invocations of the members of the inline class, -which enables a specific treatment of the underlying instance of -`Object`, consistent with the assumed schema. +We can now impose an enhanced discipline on the use of `tiny`, because the +extension type allows for invocations of the members of the extension type, +which enables a specific treatment of the underlying instance of `Object`, +consistent with the assumed schema. The getter `leaves` is an example of a disciplined use of the given object structure. The run-time type may be a `List`, but the schema which @@ -280,28 +294,28 @@ of the `add` method on `tiny` would have been allowed if we had used the type `List` (or `dynamic`) for `tiny`, and that could break the schema. -When the type of the receiver is the inline type `TinyJson`, it is a +When the type of the receiver is the extension type `TinyJson`, it is a compile-time error to invoke any members that are not in the interface of -the inline type (in this case that means: the members declared in the +the extension type (in this case that means: the members declared in the body of `TinyJson`). So it is an error to call `add` on `tiny`, and that protects us from this kind of schema violations. -In general, the use of an inline class allows us to keep some unsafe -operations in a specific location (namely inside the inline class, or -inside one of a set of collaborating inline classes). We can then -reason carefully about each operation once and for all. Clients use -the inline class to access objects conforming to the given schema, and -that gives them access to a set of known-safe operations, making all -other operations in the interface of the representation type a +In general, the use of an extension type allows us to keep some unsafe +operations in a specific location (namely inside the extension type +declaration, or inside one of a set of collaborating extension type +declarations). We can then reason carefully about each operation once and +for all. Clients use the extension type to access objects conforming to the +given schema, and that gives them access to a set of known-safe operations, +making all other operations in the interface of the representation type a compile-time error. -One possible perspective is that an inline type corresponds to an abstract -data type: There is an underlying representation, but we wish to restrict -the access to that representation to a set of operations that are +One possible perspective is that an extension type corresponds to an +abstract data type: There is an underlying representation, but we wish to +restrict the access to that representation to a set of operations that are independent of the operations available on the representation. In other -words, the inline type ensures that we only work with the representation in -specific ways, even though the representation itself has an interface that -allows us to do many other (wrong) things. +words, the extension type ensures that we only work with the representation +in specific ways, even though the representation itself has an interface +that allows us to do many other (wrong) things. It would be straightforward to enforce an added discipline like this by writing a wrapper class with the allowed operations as members, and @@ -309,7 +323,7 @@ working on a wrapper object rather than accessing the representation object and its methods directly: ```dart -// Emulate the inline class using a class. +// Emulate the extension type using a class. class TinyJson { // The representation is assumed to be a nested list of numbers. @@ -338,10 +352,9 @@ void main() { } ``` -This is similar to the inline type in that it enforces the use of -specific operations only (here we have just one: `leaves`), and it -makes it an error to use instance methods of the representation (e.g., -`add`). +This is similar to the extension type in that it enforces the use of +specific operations only (here we have just one: `leaves`), and it makes it +an error to use instance methods of the representation (e.g., `add`). Creation of wrapper objects consumes space and time. In the case where we wish to work on an entire data structure, we'd need to wrap each @@ -349,109 +362,107 @@ object as we navigate the data structure. For instance, we need to create a wrapper `TinyJson(element)` in order to invoke `leaves` recursively. -In contrast, the inline class is zero-cost, in the sense that it does -_not_ use a wrapper object, it enforces the desired discipline -statically. In the inline class, the invocation of `TinyJson(element)` -in the body of `leaves` can be eliminated entirely by inlining. +In contrast, the extension type declaration is zero-cost, in the sense that +it does _not_ use a wrapper object, it enforces the desired discipline +statically. In the extension type declaration, the invocation of +`TinyJson(element)` in the body of `leaves` can be eliminated entirely by +inlining. -Inline classes are static in nature, like extension members: an inline -class declaration may declare some type parameters. The type -parameters will be bound to types which are determined by the static -type of the receiver. Similarly, members of an inline type are resolved -statically, i.e., if `tiny.leaves` is an invocation of an inline getter -`leaves`, then the declaration named `leaves` whose body is executed -is determined at compile-time. There is no support for late binding of -an inline member, and hence there is no notion of overriding. In return -for this lack of expressive power, we get improved performance. +Extension type declarations are static in nature, like extension members: +an extension type declaration may declare some type parameters. The type +parameters will be bound to types which are determined by the static type +of the receiver. Similarly, members of an extension type declaration are +resolved statically, i.e., if `tiny.leaves` is an invocation of an +extension type getter `leaves`, then the declaration named `leaves` whose +body is executed is determined at compile-time. There is no support for +late binding of an extension type member, and hence there is no notion of +overriding. In return for this lack of expressive power, we get improved +performance. ## Syntax -A rule for `` is added to the grammar, along -with some rules for elements used in inline class declarations: +A rule for `` is added to the grammar, along +with some rules for elements used in extension type declarations: ```ebnf - ::= - 'final'? 'inline' 'class' ? ? + ::= + 'final'? 'extension' 'type' ? + ? '{' - ( )* + ( )* '}' - ::= -``` + ::= '(' ')' -*The token `inline` is not made a built-in identifier: the reserved -word `class` that occurs right after `inline` serves to disambiguate -the inline class declaration with a fixed lookahead.* + ::= +``` -A few errors can be detected immediately from the syntax: +*The token `type` is not made a built-in identifier: the built-in +identifier `extension` that occurs right before `type` serves to +disambiguate the extension type declaration with a fixed lookahead.* -A compile-time error occurs if the inline class does not declare any -instance variables, and if it declares two or more instance -variables. Let `id` be the name of unique instance variable that it -declares. The declaration of `id` must have the modifier `final`, and it -can not have the modifier `late`; otherwise a compile-time error -occurs. +Some errors can be detected immediately from the syntax: -The _name of the representation_ in an inline class declaration is the -name `id` of the unique final instance variable that it declares, and -the _type of the representation_ is the declared type of `id`. +A compile-time error occurs if the extension type declaration declares any +instance variables. -A compile-time error occurs if an inline class declaration declares an -abstract member. +The _name of the representation_ in an extension type declaration is the +identifier `id` that occurs in the representation declaration, and the +_type of the representation_ is the declared type of `id`. -*There are no special rules for static members in inline classes. They -can be declared and called or torn off as usual, e.g., -`Inline.myStaticMethod(42)`.* +*There are no special rules for static members in extension types. They can +be declared and called or torn off as usual, e.g., +`AnExtensionType.myStaticMethod(42)`.* -## Inline Method Invocations +## Extension Type Member Invocations -This document needs to refer to inline method invocations including +This document needs to refer to extension type method invocations including each part that determines the static analysis and semantics of this -invocation, so we will use a standardized phrase and talk about: -An invocation of the inline member `m` on the receiver `e` -according to the inline type `V` and with the actual type arguments +invocation, so we will use a standardized phrase and talk about: An +invocation of the extension type member `m` on the receiver `e` according +to the extension type `V` and with the actual type arguments T1, ..., Ts. -In the case where `m` is a method, the invocation could be an inline -member property extraction (*a tear-off*) or a method invocation, -and in the latter case there will also be an ``, -optionally passing some actual type arguments, and (non-optionally) -passing an actual argument list. +In the case where `m` is a method, the invocation could be an extension +type member property extraction (*a tear-off*) or a method invocation, and +in the latter case there will also be an ``, optionally +passing some actual type arguments, and (non-optionally) passing an actual +argument list. The case where `m` is a setter is syntactically different, but the treatment is the same as with a method accepting a single argument (*so we will not specify that case explicitly*). -*We need to mention all these elements together, because each of them -plays a role in the static analysis and the dynamic semantics of the -invocation. There is no syntactic representation in the language for -this concept, because the same inline method invocation can have many -different syntactic forms, and both `V` and T1, ..., +*We need to mention all these elements together, because each of them plays +a role in the static analysis and the dynamic semantics of the invocation. +There is no syntactic representation in the language for this concept, +because the same extension type method invocation can have many different +syntactic forms, and both `V` and T1, ..., Ts are implicit in the actual syntax.* -### Static Analysis of an Inline Member Invocation +### Static Analysis of an Extension Type Member Invocation We need to introduce a concept that is similar to existing concepts for regular classes. -We say that an inline class declaration _DV_ _has_ a member named `n` +We say that an extension type declaration _DV_ _has_ a member named `n` in the case where _DV_ declares a member named `n`, and in the case where _DV_ has no such declaration, but _DV_ has a direct superinterface `V` that has a member named `n`. In both cases, _the member declaration named `n` that DV has_ is said declaration. -*This definition is unambiguous for an inline class that has no +*This definition is unambiguous for an extension type that has no compile-time errors, because name clashes must be resolved by `V`.* -Consider an invocation of the inline member `m` on the receiver `e` -according to the inline type `V` and with the actual type arguments -T1, ..., Ts. If the invocation +Consider an invocation of the extension type member `m` on the receiver `e` +according to the extension type declaration `V` and with the actual type +arguments T1, ..., Ts. If the invocation includes an actual argument part (possibly including some actual type -arguments) then call it `args`. Finally, assume that `V` declares the -type variables X1, ..., Xs. +arguments) then call it `args`. Finally, assume that `V` declares the type +variables X1, ..., Xs. *Note that it is known that V<T1, ..., Ts> @@ -461,7 +472,7 @@ and the static type of `e` is a subtype of V<T1, ..., Ts>, or a subtype of the corresponding instantiated representation type (defined below). This is required when we decide that a given -expression is an inline member invocation.* +expression is an extension type member invocation.* If the name of `m` is a name in the interface of `Object` (*that is, `toString`, `==`, etc.*), the static analysis of the invocation is @@ -480,24 +491,24 @@ type of the invocation is If _Dm_is a method with function type `F`, and `args` is omitted, the invocation has static type [T1/X1 .. Ts/Xs]F. -*This is an inline method tear-off.* +*This is an extension type method tear-off.* -If _Dm_ is a method with function type `F`, and `args` exists, the -static analysis of the inline member invocation is the same as that of -an invocation with argument part `args` of a function with type +If _Dm_ is a method with function type `F`, and `args` exists, the static +analysis of the extension type member invocation is the same as that of an +invocation with argument part `args` of a function with type [T1/X1 .. Ts/Xs]F. -*This determines the compile-time errors, if any, and it determines -the type of the invocation as a whole.* +*This determines the compile-time errors, if any, and it determines the +type of the invocation as a whole.* -### Dynamic Semantics of an Inline Member Invocation +### Dynamic Semantics of an Extension Type Member Invocation -Consider an invocation of the inline member `m` on the receiver `e` -according to the inline type `V` and with actual type arguments -T1, ..., Ts. If the invocation +Consider an invocation of the extension type member `m` on the receiver `e` +according to the extension type declaration `V` and with actual type +arguments T1, ..., Ts. If the invocation includes an actual argument part (possibly including some actual type -arguments) then call it `args`. Assume that `V` declares the -type variables X1, ..., Xs. +arguments) then call it `args`. Assume that `V` declares the type variables +X1, ..., Xs. Let _Dm_ be the declaration named `m` thath `V` has. @@ -522,13 +533,13 @@ type variables of `V` are bound to the actual values of The operator `==` of the closurization returns true if and only if the operand is the same object. -*Loosely said, these function objects simply use the equality -inherited from `Object`, there are no special exceptions. Note that -we can tear off the same method from the same inline class with the -same representation object twice, and still get different behavior, -because the inline type had different actual type arguments. Hence, -we can not consider two inline method tear-offs equal just because -they have the same receiver.* +*Loosely said, these function objects simply use the equality inherited +from `Object`, there are no special exceptions. Note that we can tear off +the same method from the same extension type with the same representation +object twice, and still get different behavior, because the extension type +had different actual type arguments. Hence, we can not consider two +extension type method tear-offs equal just because they have the same +receiver.* Otherwise, the following is known: `args` is included, and _Dm_ is a method. The invocation proceeds to evaluate `args` to an actual @@ -544,29 +555,16 @@ then the invocation completes throwing the same object and stack trace. -## Static Analysis of Inline Classes - -The unique instance variable declared by an inline class must have a -type annotation. - -*In particular, the type of this variable cannot be obtained by -inference. An important part of the rationale for this rule is that -the representation type of an inline class plays a role in the -semantics of the class which is broader and more significant than that -of an instance variable in a normal class or mixin, and hence it -should be documented explicitly.* +## Static Analysis of Extension Types Assume that T1, .. Ts -are types, and `V` resolves to an inline class declaration of the +are types, and `V` resolves to an extension type declaration of the following form: ```dart -inline class V ... { - final T id; - V(this.id); - - ... // Other members. +extension type V(T id) ... { + ... // Members. } ``` @@ -574,14 +572,14 @@ It is then allowed to use V<T1, .. Ts> as a type. -*For example, it can occur as the declared type of a variable or -parameter, as the return type of a function or getter, as a type -argument in a type, as the representation type of an inline class, as -the on-type of an extension, as the type in the `onPart` of a -try/catch statement, in a type test `o is V<...>`, in a type cast -`o as V<...>`, or as the body of a type alias. It is also allowed to -create a new instance where one or more inline types occur as type -arguments (e.g., `List.empty()`).* +*For example, it can occur as the declared type of a variable or parameter, +as the return type of a function or getter, as a type argument in a type, +as the representation type of an extension type declaration, as the on-type +of an extension declaration, as the type in the `onPart` of a try/catch +statement, in a type test `o is V<...>`, in a type cast `o as V<...>`, or +as the body of a type alias. It is also allowed to create a new instance +where one or more extension types occur as type arguments (e.g., +`List.empty()`).* A compile-time error occurs if the type V<T1, .. Ts> @@ -593,96 +591,89 @@ the case where the values of the type variables do not satisfy their declared bounds, and those values will be obtained directly from the static type of the receiver in each member invocation on `V`.* -A compile-time error occurs if a type parameter of an inline class -occurs in a non-covariant position in the representation type. +A compile-time error occurs if a type parameter of an extension type +declaration occurs in a non-covariant position in the representation type. When `s` is zero, V<T1, .. Ts> -simply stands for `V`, a non-generic inline type. +simply stands for `V`, a non-generic extension type. When `s` is greater than zero, a raw occurrence `V` is treated like a raw type: Instantiation to bound is used to obtain the omitted type arguments. *Note that this may yield a super-bounded type, which is then a compile-time error.* We say that the static type of said variable, parameter, etc. -_is the inline type_ +_is the extension type_ V<T1, .. Ts>, -and that its static type _is an inline type_. +and that its static type _is an extension type_. It is a compile-time error if `await e` occurs, and the static type of -`e` is an inline type. +`e` is an extension type which is not a subtype of `Future` or +`FutureOr` for any `T`.. -*We may loosen this restriction in the future, especially if we -introduce a way for an inline type to be a subtype of a type of the -form `Future` or `FutureOr`.* - -A compile-time error occurs if an inline type declares a member whose +A compile-time error occurs if an extension type declares a member whose name is declared by `Object` as well. -*For example, an inline class cannot define an operator `==` or a -member named `noSuchMethod` or `toString`. The rationale is that these -inline methods would be highly confusing and error prone. For example, -collections would still call the instance operator `==`, not the inline -class operator, and string interpolation would call the instance -method `toString`, not the inline class method. Also, when we make -this an error for now, we have the option to allow it, perhaps with -some restrictions, in a future version of Dart.* - -A compile-time error occurs if an inline type is used as a -superinterface of a class or a mixin, or if an inline type is used to +*For example, an extension type declaration cannot declare an operator `==` +or a member named `noSuchMethod` or `toString`. The rationale is that these +extension type methods would be highly confusing and error prone. For +example, collections would still call the instance operator `==`, not the +extension type operator, and string interpolation would call the instance +method `toString`, not the extension type method. Also, when we make this an +error for now, we have the option to allow it, perhaps with some +restrictions, in a future version of Dart.* + +A compile-time error occurs if an extension type is used as a +superinterface of a class or a mixin, or if an extension type is used to derive a mixin. -*In other words, an inline type cannot occur as a superinterface in an -`extends`, `with`, `implements`, or `on` clause of a class or mixin. -On the other hand, it can occur in other ways, e.g., as a type -argument of a superinterface of a class.* - -If `e` is an expression whose static type `V` is the inline type -Inline<T1, .. Ts> -and `m` is the name of a member that `V` has, a member access -like `e.m(args)` is treated as -an invocation of the inline member `m` on the receiver `e` -according to the inline type `Inline` and with the actual type arguments -T1, ..., Ts, with the actual +*In other words, an extension type cannot occur as a superinterface in an +`extends`, `with`, `implements`, or `on` clause of a class or mixin. On +the other hand, it can occur in other ways, e.g., as a type argument of a +superinterface of a class.* + +If `e` is an expression whose static type `V` is the extension type +Name<T1, .. Ts> and `m` is the +name of a member that `V` has, a member access like `e.m(args)` is treated +as an invocation of the extension type member `m` on the receiver `e` +according to the extension type declaration `Name` and with the actual type +arguments T1, ..., Ts, with the actual argument part `args`. -Similarly, `e.m` is treated an invocation of the inline member `m` on -the receiver `e` according to the inline type `Inline` and with the -actual type arguments -T1, ..., Ts -and no actual argument part. +Similarly, `e.m` is treated an invocation of the extension type member `m` +on the receiver `e` according to the extension type declaration `Name` and +with the actual type arguments T1, ..., +Ts and no actual argument part. *Setter invocations are treated as invocations of methods with a single argument.* -If `e` is an expression whose static type `V` is the inline type -Inline<T1, .. Ts> +If `e` is an expression whose static type `V` is the extension type +Name<T1, .. Ts> and `V` has no member whose basename is the basename of `m`, a member access like `e.m(args)` may be an extension member access, following the normal rules about applicability and accessibility of extensions, in particular that `V` must match the on-type of the extension. -*In the body of an inline class declaration _DV_ with name `Inline` +*In the body of an extension type declaration _DV_ with name `Name` and type parameters X1, .. Xs, for an invocation like `m(args)`, if a declaration named `m` is found in the body of _DV_ -then that invocation is treated as -an invocation of the inline member `m` on the receiver `this` -according to the inline type `Inline` and with the actual type arguments -T1, ..., Ts, with the actual -argument part `args`. -This is just the same treatment of `this` as in the body of a class.* +then that invocation is treated as an invocation of the extension type +member `m` on the receiver `this` according to the extension type +declaraten `Name` and with the actual type arguments +T1, ..., Ts, and with the actual +argument part `args`. This is just the same treatment of `this` as in the +body of a class.* *For example:* ```dart -extension E1 on int { +extension E1 on int { // NB: Just an extension, not `extension type`. void foo() { print('E1.foo'); } } -inline class V1 { - final int it; - V1(this.it); +extension type V1(int it) { void foo() { print('V1.foo'); } void baz() { print('V1.baz'); } void qux() { print('V1.qux'); } @@ -690,9 +681,7 @@ inline class V1 { void qux() { print('qux'); } -inline class V2 { - final V1 it; - V2(this.it); +extension type V2(V1 it) { void foo() { print('V2.foo); } void bar() { foo(); // Prints 'V2.foo'. @@ -705,182 +694,182 @@ inline class V2 { } ``` -*That is, when the static type of an expression is an inline type `V` +*That is, when the static type of an expression is an extension type `V` with representation type `R`, each method invocation on that expression will invoke an instance method declared by `V` or inherited from a superinterface (or it could be an extension method with on-type `V`). Similarly for other member accesses.* -Let _DV_ be an inline class declaration named `Inline` with type +Let _DV_ be an extension type declaration named `Name` with type parameters X1 extends B1, .. Xs extends Bs. -Assume that _DV_ declares a final instance variable with name `id` and -type `R`. +Assume that the representation declaration of _DV_ is `(R id)`. -We say that the _declared representation type_ of `Inline` +We then say that the _declared representation type_ of `Name` is `R`, and the _instantiated representation type_ corresponding to -Inline<T1,.. Ts> is +Name<T1,.. Ts> is [T1/X1, .. Ts/Xs]R. -We will omit 'declared' and 'instantiated' from the phrase when it is -clear from the context whether we are talking about the inline class -itself, or we're talking about a particular instantiation of a generic -inline class. *For non-generic inline classes, the representation type -is the same in either case.* +We will omit 'declared' and 'instantiated' from the phrase when it is clear +from the context whether we are talking about the extension type +declaration itself, or we're talking about a particular generic +instantiation of an extension type. *For non-generic extension type +declarations, the representation type is the same in either case.* -Let `V` be an inline type of the form -Inline<T1, .. Ts>, and let +Let `V` be an extension type of the form +Name<T1, .. Ts>, and let `R` be the corresponding instantiated representation type. If `R` is non-nullable then `V` is a proper subtype of `Object`, and `V` is non-nullable. Otherwise, `V` is a proper subtype of `Object?`, and `V` is potentially nullable. -*That is, an expression of an inline type can be assigned to a top +*That is, an expression of an extension type can be assigned to a top type (like all other expressions), and if the representation type is -non-nullable then it can also be assigned to `Object`. Non-inline -types (except bottom types) cannot be assigned to inline types without -a cast. Similarly, null cannot be assigned to an inline type without a +non-nullable then it can also be assigned to `Object`. Non-extension +types (except bottom types) cannot be assigned to extension types without +a cast. Similarly, null cannot be assigned to an extension type without a cast, even in the case where the representation type is nullable (even better: don't use a cast, call a constructor instead). Another consequence of -the fact that the inline type is potentially non-nullable is that it -is an error to have an instance variable whose type is an inline type, +the fact that the extension type is potentially non-nullable is that it +is an error to have an instance variable whose type is an extension type, and then relying on implicit initialization to null.* -In the body of a member of an inline class declaration _DV_ named -`Inline` and declaring the type parameters +In the body of a member of an extension type declaration _DV_ named +`Name` and declaring the type parameters X1, .. Xs, the static type of `this` is -Inline<X1 .. Xs>. +Name<X1 .. Xs>. The static type of the representation name is the representation type. -*For example, in `inline class V { final R id; ...}`, `id` has type +*For example, in `extension type V(R id) ...`, `id` has type `R`, and `this` has type `V`.* -Let _DV_ be an inline class declaration named `V` with representation -type `R`. Assuming that all types have been fully alias expanded, we -say that _DV_ has a representation dependency on an inline class -declaration _DV2_ if `R` contains an identifier `id` (possibly -qualified) that resolves to _DV2_, or `id` resolves to an inline class -declaration _DV3_ and _DV3_ has a representation dependency on _DV2_. +Let _DV_ be an extension type declaration named `V` with representation +type `R`. Assuming that all types have been fully alias expanded, we say +that _DV_ has a representation dependency on an extension type declaration +_DV2_ if `R` contains an identifier `id` (possibly qualified) that resolves +to _DV2_, or `id` resolves to an extension type declaration _DV3_ and _DV3_ +has a representation dependency on _DV2_. -It is a compile-time error if an inline class declaration has a +It is a compile-time error if an extension type declaration has a representation dependency on itself. *In other words, cycles are not allowed. This ensures that it is -always possible to find a non-inline type which is the ultimate -representation type of any given inline type.* +always possible to find a non-extension type which is the ultimate +representation type of any given extension type.* -The *inline erasure* of an inline type `V` is obtained by recursively -replacing every subterm of `V` which is an inline type by the -corresponding representation type. +The *extension type erasure* of an extension type `V` is obtained by +recursively replacing every subterm of `V` which is an extension type by +the corresponding representation type. -*Note that this inline erasure exists, because it is a compile-time -error to have a dependency cycle among inline types.* +*Note that this extension type erasure exists, because it is a compile-time +error to have a dependency cycle among extension type declarations.* Let X1 extends B1, .. Xs extends Bs be a declaration of the type parameters of a generic entity (*it could -be a generic class, inline or not, or mixin, or typedef, or function*). -Let BBj be the inline erasure of +be a generic class, extension type, or mixin, or typedef, or function*). +Let BBj be the extension type erasure of Bj, for _j_ in _1 .. s_. It is a compile-time error if X1 extends BB1, .. Xs extends BBs has any compile-time errors. -*For example, the inline erasure could map +*For example, the extension type erasure could map X extends C, Y extends X to X extends Y, Y extends X, which is an error.* -An inline class declaration _DV_ named `Inline` may declare one or -more constructors. A constructor which is declared in an inline class -declaration is also known as an _inline class constructor_. +An extension type declaration _DV_ named `Name` may declare one or +more constructors. A constructor which is declared in an extension type +declaration is also known as an _extension type constructor_. -*The purpose of having an inline class constructor is that it bundles +*The purpose of having an extension type constructor is that it bundles an approach for building an instance of the representation type of an -inline declaration _DV_ with _DV_ itself, which makes it easy to -recognize that this is a way to obtain a value of that inline type. It +extension type declaration _DV_ with _DV_ itself, which makes it easy to +recognize that this is a way to obtain a value of that extension type. It can also be used to verify that an existing object (provided as an actual argument to the constructor) satisfies the requirements for -having that inline type.* +having that extension type.* -A compile-time error occurs if an inline class constructor includes a +A compile-time error occurs if an extension type constructor includes a superinitializer. *That is, a term of the form `super(...)` or `super.id(...)` as the last element of the initializer list.* -A compile-time error occurs if an inline class constructor declares a -super parameter. *For instance, `Inline(super.x);`.* +A compile-time error occurs if an extension type constructor declares a +super parameter. *For instance, `Name(super.x);`.* -*In the body of a generative inline class constructor, the static type -of `this` is the same as it is in any instance member of the inline -class, that is, `Inline`, where `X1 .. Xk` are the type -parameters declared by `Inline`.* +*In the body of a generative extension type constructor, the static type of +`this` is the same as it is in any instance member of the extension type +declaration, that is, `Name`, where `X1 .. Xk` are the type +parameters declared by `Name`.* An instance creation expression of the form -Inline<T1, .. Ts>(...) +Name<T1, .. Ts>(...) or -Inline<T1, .. Ts>.name(...) +Name<T1, .. Ts>.name(...) is used to invoke these constructors, and the type of such an expression is -Inline<T1, .. Ts>. +Name<T1, .. Ts>. -*In short, inline class constructors appear to be very similar to -constructors in regular classes, and they correspond to the situation +*In short, extension type constructors appear to be very similar to +constructors in classes, and they correspond to the situation where the enclosing class has a single, non-late, final instance variable, which is initialized according to the normal rules for constructors (in particular, it can occur by means of `this.id`, or in an initializer list, or by an initializing expression in the declaration itself, but it is an error if it does not occur at all).* -An inline type `V` used as an expression (*a type literal*) is allowed +An extension type `V` used as an expression (*a type literal*) is allowed and has static type `Type`. -An inline class declaration _DV_ can have the type modifier -`final`. In this case it is a compile-time error for another inline -class declaration _DV2_ to have a superinterface which is a reference -to _DV_. +An extension type declaration _DV_ can have the type modifier +`final`. In this case it is a compile-time error for another extension type +declaration _DV2_ to have _DV_ as a superinterface. *As the grammar shows, any occurrence of the keywords `abstract`, -`base`, `interface`, `sealed`, or `mixin` in an inline class +`base`, `interface`, `sealed`, or `mixin` in an extension type declaration is a syntax error.* -### Composing Inline Classes +### Composing Extension Types This section describes the effect of including a clause derived from -`` in an inline class declaration. We use the phrase +`` in an extension type declaration. We use the phrase _the implements clause_ to refer to this clause. -*The rationale is that the set of members and member implementations -of a given inline class may need to overlap with that of other inline -classes. The implements clause allows for implementation reuse by -putting some shared members in an inline class `V`, and including `V` -in the implements clause of several inline class declarations -V1 .. Vk, thus "inheriting" the -members of `V` into all of V1 .. Vk -without code duplication.* +*The rationale is that the set of members and member implementations of a +given extension type may need to overlap with that of other extension type +declarations. The implements clause allows for implementation reuse by +putting some shared members in an extension type `V`, and including `V` in +the implements clause of several extension type declarations +V1 .. Vk, thus "inheriting" the members +of `V` into all of V1 .. Vk without code +duplication.* *The reason why this mechanism uses the keyword `implements` rather than `extends` to declare a relation that involves "inheritance" is that it has a similar semantics as that of extension members (in that -they are statically resolved).* +they are statically resolved, and each member is applicable to every +subtype).* -Assume that _DV_ is an inline class declaration named `Inline`, and +Assume that _DV_ is an extension type declaration named `Name`, and `V1` occurs as one of the ``s in the `` of _DV_. In this case we say that `V1` is a _superinterface_ of _DV_. If _DV_ does not include an `` clause then _DV_ has no -superinterfaces. +superinterfaces. *But note that `Object?` and perhaps `Object` are still +supertypes.* -A compile-time error occurs if `V1` is a type name or a parameterized -type which occurs as a superinterface in an inline class declaration -_DV_, but `V1` does not denote an inline type. +A compile-time error occurs if `V1` is a type name or a parameterized type +which occurs as a superinterface in an extension type declaration _DV_, but +`V1` does not denote an extension type. A compile-time error occurs if any direct or indirect superinterface -of _DV_ is the type `Inline` or a type of the form `Inline<...>`. *As +of _DV_ is the type `Name` or a type of the form `Name<...>`. *As usual, subtype cycles are not allowed.* -Assume that _DV_ has two direct or indirect superinterface of the form +Assume that _DV_ has two direct or indirect superinterfaces of the form W<T1, .. Tk> respectively W<S1, .. Sk>. @@ -892,8 +881,8 @@ is not equal to for any _j_ in _1 .. k_. The notion of equality used here is the same as with the corresponding rule about superinterfaces of classes. -Assume that an inline class declaration _DV_ named `Inline` has -representation type `R`, and that the inline type `V1` with +Assume that an extension type declaration _DV_ named `Name` has +representation type `R`, and that the extension type `V1` with declaration _DV1_ is a superinterface of _DV_ (*note that `V1` may have some actual type arguments*). Assume that `S` is the instantiated representation type corresponding to `V1`. A compile-time @@ -903,11 +892,10 @@ error occurs if `R` is not a subtype of `S`. in `V1` when invoking members of `V1`, where `id` is the representation name of _DV_ and `id1` is the representation name of _DV1_.* -Assume that _DV_ declares an inline class named `Inline` with type -parameters -X1 .. Xs, +Assume that _DV_ declares an extension type declaration named `Name` with +type parameters X1 .. Xs, and `V1` is a superinterface of _DV_. Then -Inline<T1, .. Ts> +Name<T1, .. Ts> is a subtype of [T1/X1 .. Ts/Xs]V1 for all T1, .. Ts. @@ -915,7 +903,7 @@ for all T1, .. Ts. *In short, if `V1` is a superinterface of `V` then `V1` is also a supertype of `V`.* -A compile-time error occurs if an inline class declaration _DV_ has +A compile-time error occurs if an extension type declaration _DV_ has two superinterfaces `V1` and `V2`, where both `V1` and `V2` have a member named _m_, and the two declarations of _m_ are distinct declarations, and _DV_ does not declare a member named _m_. @@ -928,30 +916,30 @@ the same declaration (so `V` is a subinterface of `V1` and `V2`, and both `V1` and `V2` are subinterfaces of `V3`, and only `V3` declares _m_, in which case there is no conflict in `V`).* -*Assume that _DV_ is an inline class declaration named `Inline`, and -the inline type `V1`, declared by _DV1_, is a superinterface of +*Assume that _DV_ is an extension type declaration named `Name`, and +the extension type `V1`, declared by _DV1_, is a superinterface of _DV_. Let `m` be the name of a member of `V1`. If _DV_ also declares a member named `m` then the latter may be considered similar to a declaration that "overrides" the former. However, it should be noted -that inline method invocation is resolved statically, and hence there -is no override relationship among the two in the traditional +that extension type method invocation is resolved statically, and hence +there is no override relationship among the two in the traditional object-oriented sense (that is, it will never occur that the statically known declaration is the member of `V1`, and the member invoked at run time is the one in _DV_). A receiver with static type `V1` will invoke the declaration in _DV1_, and a receiver with -static type `Inline` (or `Inline<...>`) will invoke the one in _DV_.* +static type `Name` (or `Name<...>`) will invoke the one in _DV_.* Hence, we use a different word to describe the relationship between a member named _m_ of a superinterface, and a member named _m_ which is declared by the subinterface: We say that the latter _redeclares_ the former. -*In particular, if two different declarations of _m_ is inherited -from two superinterface then the subinterface can resolve the conflict +*In particular, if two different declarations of _m_ are inherited +from two superinterfaces then the subinterface can resolve the conflict by redeclaring _m_.* *Note that there is no notion of having a 'correct override relation' -here. With inline classes, any member signature can redeclare any +here. With extension types, any member signature can redeclare any other member signature with the same name, including the case where a method is redeclared by a getter, or vice versa. The reason for this is that no call site will resolve to one of several declarations at @@ -959,32 +947,42 @@ run time. Each invocation will statically resolve to one particular declaration, and this makes it possible to ensure that the invocation is type correct.* -Assume that _DV_ is an inline class declaration, and that the inline -types `V1` and `V2` are superinterfaces of _DV_. Let `M1` be the -members of `V1`, and `M2` the members of `V2`. A compile-time error -occurs if there is a member name `m` such that `V1` as well as `V2` -has a member named `m`, and they are distinct declarations, and _DV_ -does not declare a member named `m`. *In other words, a name clash -among distinct "inherited" members is an error, but it can be -eliminated by redeclaring the clashing name.* - -The effect of having an inline class declaration _DV_ with +*Note that extension methods (in a regular `extension` declaration, not an +`extension type`) have a similar nature: An extension declaration `E1` on +`T1` and another extension declaration `E2` on `T2` where `T1 <: T2` behave +in a way which is similar to overriding in that a more special receiver can +invoke `T1.foo` at a call site like `e.foo()`, whereas it would have +invoked `T2.foo` if the static type of `e` had been more general (if +that type would then match `T2`, but not `T1`). In this case there is also +no override relationship between `T1.foo` and `T2.foo`, they are just +independent member signatures.* + +Assume that _DV_ is an extension type declaration, and that the extension +types `V1` and `V2` are extension type superinterfaces of _DV_. Let `M1` be +the members of `V1`, and `M2` the members of `V2`. A compile-time error +occurs if there is a member name `m` such that `V1` as well as `V2` has a +member named `m`, and they are distinct declarations, and _DV_ does not +declare a member named `m`. *In other words, a name clash among distinct +"inherited" members is an error, but it can be eliminated by redeclaring +the clashing name.* + +The effect of having an extension type declaration _DV_ with superinterfaces `V1, .. Vk` is that the members declared by _DV_ as well as all members of `V1, .. Vk` that are not redeclared by a declaration in _DV_ can be invoked on a receiver of the type introduced by _DV_. -## Dynamic Semantics of Inline Classes +## Dynamic Semantics of Extension Types For any given syntactic construct which has been characterized as an -inline member invocation during the static analysis, the dynamic +extension type member invocation during the static analysis, the dynamic semantics of the construct is the dynamic semantics of said -inline member invocation. +extension type member invocation. -Consider an inline class declaration _DV_ named `Inline` with +Consider an extension type declaration _DV_ named `Name` with representation name `id` and representation type `R`. Invocation of a -non-redirecting generative inline class constructor proceeds as follows: A +non-redirecting generative extension type constructor proceeds as follows: A fresh, non-late, final variable `v` is created. An initializing formal `this.id` has the side-effect that it initializes `v` to the actual argument passed to this formal. An initializer list element of the @@ -994,35 +992,35 @@ constructor body, `this` and `id` are bound to the value of `v`. The value of the instance creation expression that gave rise to this constructor execution is the value of `this`. -At run time, for a given instance `o` typed as an inline type `V`, there +At run time, for a given instance `o` typed as an extension type `V`, there is _no_ reification of `V` associated with `o`. *This means that, at run time, an object never "knows" that it is being -viewed as having an inline type. By soundness, the run-time type of `o` +viewed as having an extension type. By soundness, the run-time type of `o` will be a subtype of the representation type of `V`.* -The run-time representation of a type argument which is an inline type +The run-time representation of a type argument which is an extension type `V` is the corresponding instantiated representation type. -*This means that an inline type and the underlying representation type -are considered as being the same type at run time. So we can freely -use a cast to introduce or discard the inline type, as the static type -of an instance, or as a type argument in the static type of a data -structure or function involving the inline type.* +*This means that an extension type and the underlying representation type +are considered as being the same type at run time. So we can freely use a +cast to introduce or discard the extension type, as the static type of an +instance, or as a type argument in the static type of a data structure or +function involving the extension type.* A type test, `o is U` or `o is! U`, and a type cast, `o as U`, where -`U` is or contains an inline type, is performed at run time as a type -test and type cast on the run-time representation of the inline type +`U` is or contains an extension type, is performed at run time as a type +test and type cast on the run-time representation of the extension type as described above. -An inline type `V` used as an expression (*a type literal*) evaluates +An extension type `V` used as an expression (*a type literal*) evaluates to the value of the corresponding instantiated representation type used as an expression. ### Summary of Typing Relationships -*Here is an overview of the subtype relationships of an inline type +*Here is an overview of the subtype relationships of an extension type `V0` with instantiated representation type `R` and superinterfaces `V1 .. Vk`, as well as other typing relationships involving `V0`:* @@ -1049,8 +1047,8 @@ discussions. ### Support "private inheritance"? In the current proposal there is a subtype relationship between every -inline class and each of its superinterfaces. So if we have -`inline class V implements V1, V2 ...` then `V <: V1` and `V <: V2`. +extension type and each of its superinterfaces. So if we have +`extension V(T id) implements V1, V2 ...` then `V <: V1` and `V <: V2`. In some cases it might be preferable to omit the subtype relationship, even though there is a code reuse element (because `V1` is a @@ -1059,24 +1057,17 @@ superinterface of `V`, we just don't need or want `V <: V1`). A possible workaround would be to write forwarding methods manually: ```dart -inline class V1 { - final R it; - V1(this.it); +extension type V1(R it) { void foo() {...} } // `V` can reuse code from `V1` by using `implements`. Note that // `S <: R`, because otherwise it is a compile-time error. -inline class V implements V1 { - final S it; - V(this.it); -} +extension type V(S it) implements V1 {} // Alternatively, we can write forwarders, in order to avoid // having the subtype relationship `V <: V1`. -inline class V { - final S it; - V(this.it); +extension type V(S it) { void foo() => V1(it).foo(); } ``` From 52f4d2f3dcb0d7151b88af4a75daf350d9fa5992 Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Fri, 30 Jun 2023 14:39:50 +0200 Subject: [PATCH 03/29] Allow non-extension type superinterfaces --- .../future-releases/extension-types/feature-specification.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/accepted/future-releases/extension-types/feature-specification.md b/accepted/future-releases/extension-types/feature-specification.md index 07f9e3e522..28cb393bd9 100644 --- a/accepted/future-releases/extension-types/feature-specification.md +++ b/accepted/future-releases/extension-types/feature-specification.md @@ -863,7 +863,8 @@ supertypes.* A compile-time error occurs if `V1` is a type name or a parameterized type which occurs as a superinterface in an extension type declaration _DV_, but -`V1` does not denote an extension type. +`V1` does not denote an extension type, and `V1` does not denote a +supertype of the representation type of _DV_. A compile-time error occurs if any direct or indirect superinterface of _DV_ is the type `Name` or a type of the form `Name<...>`. *As From 873dd8be0b50ce161ad2e60f9110ae2b8ceb398e Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Fri, 30 Jun 2023 14:52:18 +0200 Subject: [PATCH 04/29] Added rules about "inheritance" conflicts --- .../extension-types/feature-specification.md | 68 ++++++++++++------- 1 file changed, 43 insertions(+), 25 deletions(-) diff --git a/accepted/future-releases/extension-types/feature-specification.md b/accepted/future-releases/extension-types/feature-specification.md index 28cb393bd9..730d35bbf9 100644 --- a/accepted/future-releases/extension-types/feature-specification.md +++ b/accepted/future-releases/extension-types/feature-specification.md @@ -857,9 +857,9 @@ Assume that _DV_ is an extension type declaration named `Name`, and `V1` occurs as one of the ``s in the `` of _DV_. In this case we say that `V1` is a _superinterface_ of _DV_. -If _DV_ does not include an `` clause then _DV_ has no -superinterfaces. *But note that `Object?` and perhaps `Object` are still -supertypes.* +If _DV_ does not include an `` clause then _DV_ has +`Object?` or `Object` as a direct superinterface, according to the subtype +relation which was specified earlier. A compile-time error occurs if `V1` is a type name or a parameterized type which occurs as a superinterface in an extension type declaration _DV_, but @@ -905,30 +905,48 @@ for all T1, .. Ts. supertype of `V`.* A compile-time error occurs if an extension type declaration _DV_ has -two superinterfaces `V1` and `V2`, where both `V1` and `V2` have a -member named _m_, and the two declarations of _m_ are distinct +two extension type superinterfaces `V1` and `V2`, where both `V1` and `V2` +have a member named _m_, and the two declarations of _m_ are distinct declarations, and _DV_ does not declare a member named _m_. -*In other words, if two different declarations of _m_ are inherited -from two superinterfaces then the subinterface must resolve the -conflict. The so-called diamond inheritance pattern can create the -case where two superinterfaces have an _m_, but they are both declared by -the same declaration (so `V` is a subinterface of `V1` and `V2`, and both -`V1` and `V2` are subinterfaces of `V3`, and only `V3` declares _m_, -in which case there is no conflict in `V`).* - -*Assume that _DV_ is an extension type declaration named `Name`, and -the extension type `V1`, declared by _DV1_, is a superinterface of -_DV_. Let `m` be the name of a member of `V1`. If _DV_ also declares a -member named `m` then the latter may be considered similar to a -declaration that "overrides" the former. However, it should be noted -that extension type method invocation is resolved statically, and hence -there is no override relationship among the two in the traditional -object-oriented sense (that is, it will never occur that the -statically known declaration is the member of `V1`, and the member -invoked at run time is the one in _DV_). A receiver with static -type `V1` will invoke the declaration in _DV1_, and a receiver with -static type `Name` (or `Name<...>`) will invoke the one in _DV_.* +*In other words, if two different declarations of _m_ are inherited from +two extension type superinterfaces then the subinterface must resolve the +conflict. The so-called diamond inheritance pattern can create the case +where two superinterfaces have an _m_, but they are both declared by the +same declaration (so `V` is a subinterface of `V1` and `V2`, and both `V1` +and `V2` are subinterfaces of `V3`, and only `V3` declares _m_, in which +case there is no conflict in `V`).* + +A compile-time error occurs if an extension type declaration _DV_ has +two superinterfaces `V1` and `V2`, where `V1` is an extension type and `V2` +is a non-extension type, and both `V1` and `V2` have a member named _m_, +and _DV_ does not declare a member named _m_. + +*In other words, member name clashes among an extension type and a +non-extension type superinterface is always an error. _DV_ must override +the given name to eliminate the error.* + +A compile-time error occurs if an extension type declaration _DV_ has +two or more non-extension type superinterfaces `V1 .. Vk`, where each `Vj` +has a member named _m_, and the combined member signature of these members +does not exist, and _DV_ does not declare a member named _m_. + +*In other words, when the extension type has some members which are +"inherited" from some non-extension type superinterfaces, they must have a +well-defined signature just like if _DV_ had been a class.* + +*Assume that _DV_ is an extension type declaration named `Name`, and the +extension type `V1`, declared by _DV1_, is a superinterface of _DV_ (could +be an extension type or a non-extension type). Let `m` be the name of a +member of `V1`. If _DV_ also declares a member named `m` then the latter +may be considered similar to a declaration that "overrides" the former. +However, it should be noted that extension type method invocation is +resolved statically, and hence there is no override relationship among the +two in the traditional object-oriented sense (that is, it will never occur +that the statically known declaration is the member of `V1`, and the member +invoked at run time is the one in _DV_). A receiver with static type `V1` +will invoke the declaration in _DV1_, and a receiver with static type +`Name` (or `Name<...>`) will invoke the one in _DV_.* Hence, we use a different word to describe the relationship between a member named _m_ of a superinterface, and a member named _m_ which is From 031398be4ede3bcac32589177957648f97c11d2f Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Fri, 30 Jun 2023 15:04:13 +0200 Subject: [PATCH 05/29] Add support for named constructors based on the primary constructor syntax --- .../extension-types/feature-specification.md | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/accepted/future-releases/extension-types/feature-specification.md b/accepted/future-releases/extension-types/feature-specification.md index 730d35bbf9..0231a1d118 100644 --- a/accepted/future-releases/extension-types/feature-specification.md +++ b/accepted/future-releases/extension-types/feature-specification.md @@ -106,7 +106,10 @@ declares the name and type of the representation in a way which is a special case of the [primary constructor proposal][]. In the body of the extension type the representation object is in scope, with the declared name and type, as if it had been a final instance -variable in a class. +variable in a class. It differs from an instance variable declaration in +that it is not available in the interface of the extension type (that is, +we can use `id` inside the extension type declaration, but we can't use +`e.id` from the outside). [primary constructor proposal]: https://github.com/dart-lang/language/pull/3023 @@ -176,7 +179,6 @@ void main() { 10 + safeId; // Compile-time error, wrong argument type. myUnsafeId = safeId; // Compile-time error, wrong type. myUnsafeId = safeId as int; // OK, we can force it. - myUnsafeId = safeId.i; // OK, and safer than a cast. } ``` @@ -190,12 +192,8 @@ We can actually cast away the extension type and hence get access to the interface of the representation, but we assume that the developer wishes to maintain this extra discipline, and won't cast away the extension type unless there is a good reason to do so. Similarly, we can access the -representation using the representation name as a getter. There is no -reason to consider the latter to be a violation of any kind of -encapsulation or protection barrier, it's just like any other getter -invocation. If desired, the author of the extension type can choose to use -a private representation name, to obtain a small amount of extra -encapsulation. +representation using the representation name as a getter inside the body of +the extension type declaration. The extra discipline is enforced because the extension type member implementations will only treat the representation object in ways that @@ -393,7 +391,7 @@ with some rules for elements used in extension type declarations: ( )* '}' - ::= '(' ')' + ::= ('.' )? '(' ')' ::= ``` @@ -793,6 +791,14 @@ can also be used to verify that an existing object (provided as an actual argument to the constructor) satisfies the requirements for having that extension type.* +The `` works as a constructor. The optional +`('.' )` in the grammar is used to declare this constructor +with a name of the form ` '.' ` *(at times +described as a "named constructor")*. It is a constant constructor: If `e` +is a constant expression and `V(e)` is not an error, then `V(e)` is a +constant expression. Other constructors may be declared `const` or not, +following the normal rules for constant constructors. + A compile-time error occurs if an extension type constructor includes a superinitializer. *That is, a term of the form `super(...)` or `super.id(...)` as the last element of the initializer list.* From e7d0c967f7136c55d764ded49395d3d02122f123 Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Fri, 30 Jun 2023 18:09:29 +0200 Subject: [PATCH 06/29] Many small fixes --- .../extension-types/feature-specification.md | 114 +++++++++++------- 1 file changed, 72 insertions(+), 42 deletions(-) diff --git a/accepted/future-releases/extension-types/feature-specification.md b/accepted/future-releases/extension-types/feature-specification.md index 0231a1d118..b41d152e1f 100644 --- a/accepted/future-releases/extension-types/feature-specification.md +++ b/accepted/future-releases/extension-types/feature-specification.md @@ -18,7 +18,7 @@ information about the process, including in their change logs. 2023.06.30 - Change the feature name and keywords to `extension type`, adjust representation type and name declaration to be similar to a primary - constructor. + constructor. Allow non-extension type superinterfaces. 2022.12.20 - Add rule about type modifiers. @@ -33,13 +33,15 @@ information about the process, including in their change logs. This document specifies a language feature that we call "extension types". -The feature introduces the _extension type_ feature. Is feature introduces a new -kind of type which is declared by a new `extension type` declaration. An -extension type provides a replacement of the members available on instances -of an existing type: when the static type of the instance is an extension -type _V_, the available instance members are exactly the ones provided by -_V_ (noting that there may also be some accessible and applicable extension -members). +The feature introduces the _extension type_ feature. This feature +introduces a new kind of type which is declared by a new `extension type` +declaration. An extension type provides a replacement of the members +available on instances of an existing type: when the static type of the +instance is an extension type _V_, the available instance members are +exactly the ones provided by _V_. There may also be some accessible and +applicable extension members (noting that this is from the existing +feature expressed as an `extension` declaration, it is not an `extension +type` declaration). In contrast, when the static type of an instance is not an extension type, it is (by soundness) always the run-time type of the instance or a @@ -66,13 +68,13 @@ wrapped object as needed. However, even though an extension type behaves like a wrapping, the wrapper object will never exist at run time, and a reference whose type is the -extension type will actually refer directly to the underlying wrapped +extension type will actually refer directly to the underlying "wrapped" object. Consider a member access (e.g., a method call like `e.m(2)`) where the static type of the receiver (`e`) is an extension type `V`. In general, the member (`m`) will be a member of `V`, not a member of -the static type of the wrapped object, and the invocation of that +the static type of the "wrapped" object, and the invocation of that member will be resolved statically (just like extension methods). Given that there is no wrapper object, we will refer to the "wrapped" @@ -127,6 +129,13 @@ representation type `R`, we can refer to an object `theRList` of type element in the list", but it only takes time _O(1)_ and no space, no matter how many elements the list contains. +It is also possible to declare a non-extension type as a superinterface in +an extension type declaration, if certain conditions are satisfied. This +can be viewed as a partial unveiling of the representation object, in the +sense that it enables some members of the representation type to be invoked +on the extension type, and it makes the extension type a subtype of that +non-extension type. + ## Motivation @@ -197,12 +206,11 @@ the extension type declaration. The extra discipline is enforced because the extension type member implementations will only treat the representation object in ways that -are written with the purpose of conforming to this particular -discipline (and thereby defines what this discipline is). For example, -if the discipline includes the rule that you should never call a -method `foo` on the representation, then the author of the extension type -will simply need to make sure that none of the extension type member -declarations ever calls `foo`. +conform to this particular discipline (and thereby defines what this +discipline is). For example, if the discipline includes the rule that you +should never call a method `foo` on the representation, then the author of +the extension type will simply need to make sure that none of the extension +type member declarations ever calls `foo`. Another example would be that we're using interop with JavaScript, and we wish to work on a given `JSObject` representing a button, using a @@ -420,7 +428,7 @@ This document needs to refer to extension type method invocations including each part that determines the static analysis and semantics of this invocation, so we will use a standardized phrase and talk about: An invocation of the extension type member `m` on the receiver `e` according -to the extension type `V` and with the actual type arguments +to the extension type declaration `V` and with the actual type arguments T1, ..., Ts. In the case where `m` is a method, the invocation could be an extension @@ -437,8 +445,9 @@ treatment is the same as with a method accepting a single argument a role in the static analysis and the dynamic semantics of the invocation. There is no syntactic representation in the language for this concept, because the same extension type method invocation can have many different -syntactic forms, and both `V` and T1, ..., -Ts are implicit in the actual syntax.* +syntactic forms, and both `V` and +T1, ..., Ts are implicit in the actual +syntax.* ### Static Analysis of an Extension Type Member Invocation @@ -452,8 +461,11 @@ where _DV_ has no such declaration, but _DV_ has a direct superinterface `V` that has a member named `n`. In both cases, _the member declaration named `n` that DV has_ is said declaration. -*This definition is unambiguous for an extension type that has no -compile-time errors, because name clashes must be resolved by `V`.* +*For a declaration in an extension type, this definition is unambiguous for +an extension type that has no compile-time errors, because name clashes +must be resolved by `V`. If the declaration is from a superinterface which +is not an extension type then it is handled specially, and we do not need +to have a unique declaration of `n` "that _DV_ has".* Consider an invocation of the extension type member `m` on the receiver `e` according to the extension type declaration `V` and with the actual type @@ -480,13 +492,26 @@ type `Object` and with the same `args`, if any. Otherwise, a compile-time error occurs if `V` does not have a member named `m`. +If `V` has a member named `m` which is declared in a non-extension type +superinterface `S` and not redeclared by any extension type superinterfaces +that have `S` as a superinterface, the invocation of `m` is treated as an +invocation of a regular class instance member whose member signature is the +combined member signatures of all declarations of `m` in the direct +superinterfaces of `V`. + +*In other words, members "inherited" from non-extension type +superinterfaces are invoked as normal class instance members, as if we +could "see through the veil" that is the extension type and call members of +the representation type which have been unveiled by including `S` as a +superinterface, directly or indirectly.* + Otherwise, let _Dm_ be the declaration of `m` that `V` has. If _Dm_ is a getter declaration with return type `R` then the static type of the invocation is [T1/X1 .. Ts/Xs]R. -If _Dm_is a method with function type `F`, and `args` is omitted, the +If _Dm_ is a method with function type `F`, and `args` is omitted, the invocation has static type [T1/X1 .. Ts/Xs]F. *This is an extension type method tear-off.* @@ -508,7 +533,7 @@ includes an actual argument part (possibly including some actual type arguments) then call it `args`. Assume that `V` declares the type variables X1, ..., Xs. -Let _Dm_ be the declaration named `m` thath `V` has. +Let _Dm_ be the declaration named `m` that `V` has. Evaluation of this invocation proceeds by evaluating `e` to an object `o`. @@ -607,7 +632,7 @@ and that its static type _is an extension type_. It is a compile-time error if `await e` occurs, and the static type of `e` is an extension type which is not a subtype of `Future` or -`FutureOr` for any `T`.. +`FutureOr` for any `T`. A compile-time error occurs if an extension type declares a member whose name is declared by `Object` as well. @@ -651,7 +676,9 @@ If `e` is an expression whose static type `V` is the extension type and `V` has no member whose basename is the basename of `m`, a member access like `e.m(args)` may be an extension member access, following the normal rules about applicability and accessibility of extensions, -in particular that `V` must match the on-type of the extension. +in particular that `V` must match the on-type of the extension +*(again, this is an `extension` declaration that we have today, not an +`extension type` declaration)*. *In the body of an extension type declaration _DV_ with name `Name` and type parameters @@ -870,7 +897,7 @@ relation which was specified earlier. A compile-time error occurs if `V1` is a type name or a parameterized type which occurs as a superinterface in an extension type declaration _DV_, but `V1` does not denote an extension type, and `V1` does not denote a -supertype of the representation type of _DV_. +supertype of the ultimate representation type of _DV_. A compile-time error occurs if any direct or indirect superinterface of _DV_ is the type `Name` or a type of the form `Name<...>`. *As @@ -982,15 +1009,6 @@ that type would then match `T2`, but not `T1`). In this case there is also no override relationship between `T1.foo` and `T2.foo`, they are just independent member signatures.* -Assume that _DV_ is an extension type declaration, and that the extension -types `V1` and `V2` are extension type superinterfaces of _DV_. Let `M1` be -the members of `V1`, and `M2` the members of `V2`. A compile-time error -occurs if there is a member name `m` such that `V1` as well as `V2` has a -member named `m`, and they are distinct declarations, and _DV_ does not -declare a member named `m`. *In other words, a name clash among distinct -"inherited" members is an error, but it can be eliminated by redeclaring -the clashing name.* - The effect of having an extension type declaration _DV_ with superinterfaces `V1, .. Vk` is that the members declared by _DV_ as well as all members of `V1, .. Vk` that are not redeclared by a @@ -1017,6 +1035,13 @@ constructor body, `this` and `id` are bound to the value of `v`. The value of the instance creation expression that gave rise to this constructor execution is the value of `this`. +The dynamic semantics of an instance creation that references the +`` follows the semantics of primary +constructors: Consider the representation declaration as a constant primary +constructor, then consider the corresponding non-primary constructor _k_. +The execution of the representation declaration as a constructor has the +same semantics as an execution of _k_. + At run time, for a given instance `o` typed as an extension type `V`, there is _no_ reification of `V` associated with `o`. @@ -1025,13 +1050,18 @@ viewed as having an extension type. By soundness, the run-time type of `o` will be a subtype of the representation type of `V`.* The run-time representation of a type argument which is an extension type -`V` is the corresponding instantiated representation type. - -*This means that an extension type and the underlying representation type -are considered as being the same type at run time. So we can freely use a -cast to introduce or discard the extension type, as the static type of an -instance, or as a type argument in the static type of a data structure or -function involving the extension type.* +`V` is the run-time representation of the corresponding instantiated +representation type. + +*This wording ensures that we unfold the instantiated representation type +recursively, until it is a non-extension type that does not contain any +subterms which are extension types.* + +*Moreover, this means that an extension type and the underlying +representation type are considered as being the same type at run time. So +we can freely use a cast to introduce or discard the extension type, as the +static type of an instance, or as a type argument in the static type of a +data structure or function involving the extension type.* A type test, `o is U` or `o is! U`, and a type cast, `o as U`, where `U` is or contains an extension type, is performed at run time as a type From 999f51209f0427c63fedd4abf14ef93b39314e3f Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Fri, 30 Jun 2023 18:13:10 +0200 Subject: [PATCH 07/29] Review response --- .../extension-types/feature-specification.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/accepted/future-releases/extension-types/feature-specification.md b/accepted/future-releases/extension-types/feature-specification.md index b41d152e1f..f064930dfb 100644 --- a/accepted/future-releases/extension-types/feature-specification.md +++ b/accepted/future-releases/extension-types/feature-specification.md @@ -413,9 +413,9 @@ Some errors can be detected immediately from the syntax: A compile-time error occurs if the extension type declaration declares any instance variables. -The _name of the representation_ in an extension type declaration is the -identifier `id` that occurs in the representation declaration, and the -_type of the representation_ is the declared type of `id`. +The _name of the representation_ in an extension type declaration with a +representation declaration of the form `(T id)` is the identifier `id`, and +the _type of the representation_ is `T`. *There are no special rules for static members in extension types. They can be declared and called or torn off as usual, e.g., From 5967dd2e83df203be180a0dfcb4d167769867e10 Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Fri, 30 Jun 2023 18:30:44 +0200 Subject: [PATCH 08/29] Review response --- .../extension-types/feature-specification.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/accepted/future-releases/extension-types/feature-specification.md b/accepted/future-releases/extension-types/feature-specification.md index f064930dfb..2aa84e226d 100644 --- a/accepted/future-releases/extension-types/feature-specification.md +++ b/accepted/future-releases/extension-types/feature-specification.md @@ -69,7 +69,9 @@ wrapped object as needed. However, even though an extension type behaves like a wrapping, the wrapper object will never exist at run time, and a reference whose type is the extension type will actually refer directly to the underlying "wrapped" -object. +object. This fact also determines the behavior of `as` and `is`: Those +operations will refer to the run-time type of the representation object, +and the run-time value of the extension type is the representation type. Consider a member access (e.g., a method call like `e.m(2)`) where the static type of the receiver (`e`) is an extension type `V`. @@ -858,7 +860,8 @@ and has static type `Type`. An extension type declaration _DV_ can have the type modifier `final`. In this case it is a compile-time error for another extension type -declaration _DV2_ to have _DV_ as a superinterface. +declaration _DV2_ to have _DV_ as a superinterface, if _DV2_ is declared in +a different library. *As the grammar shows, any occurrence of the keywords `abstract`, `base`, `interface`, `sealed`, or `mixin` in an extension type From a42a107a09b391e47a3a06cd32f49f00c2b241ca Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Mon, 3 Jul 2023 12:25:58 +0200 Subject: [PATCH 09/29] Add missing to primary constructor-ish syntax --- .../future-releases/extension-types/feature-specification.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/accepted/future-releases/extension-types/feature-specification.md b/accepted/future-releases/extension-types/feature-specification.md index 2aa84e226d..7dcb61c81a 100644 --- a/accepted/future-releases/extension-types/feature-specification.md +++ b/accepted/future-releases/extension-types/feature-specification.md @@ -401,7 +401,8 @@ with some rules for elements used in extension type declarations: ( )* '}' - ::= ('.' )? '(' ')' + ::= + ('.' )? '(' ')' ::= ``` From 900ae7ee2c36dfc618cb354bfa5b875c0132362a Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Mon, 3 Jul 2023 12:33:01 +0200 Subject: [PATCH 10/29] Review response: Reword introduction to emphasize that the main case is to receive the representation object as an actual argument to the constructor --- .../extension-types/feature-specification.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/accepted/future-releases/extension-types/feature-specification.md b/accepted/future-releases/extension-types/feature-specification.md index 7dcb61c81a..9fa5685a00 100644 --- a/accepted/future-releases/extension-types/feature-specification.md +++ b/accepted/future-releases/extension-types/feature-specification.md @@ -813,13 +813,13 @@ An extension type declaration _DV_ named `Name` may declare one or more constructors. A constructor which is declared in an extension type declaration is also known as an _extension type constructor_. -*The purpose of having an extension type constructor is that it bundles -an approach for building an instance of the representation type of an -extension type declaration _DV_ with _DV_ itself, which makes it easy to -recognize that this is a way to obtain a value of that extension type. It -can also be used to verify that an existing object (provided as an -actual argument to the constructor) satisfies the requirements for -having that extension type.* +*The purpose of having an extension type constructor is that it bundles an +approach for receiving or building an instance of the representation type +of an extension type declaration _DV_ with _DV_ itself, and creating an +expression whose static type is the extension type and whose value at run +time is said representation object. Extension type constructor bodies can +also be used to verify that the representation object satisfies the +requirements for having that extension type.* The `` works as a constructor. The optional `('.' )` in the grammar is used to declare this constructor From 0bb106c367bb77fc50d637a0c829aa72d8cd037e Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Mon, 3 Jul 2023 12:37:10 +0200 Subject: [PATCH 11/29] Review response: Removed "initializing expression", the new syntax does not allow that --- .../extension-types/feature-specification.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/accepted/future-releases/extension-types/feature-specification.md b/accepted/future-releases/extension-types/feature-specification.md index 9fa5685a00..968c83cec5 100644 --- a/accepted/future-releases/extension-types/feature-specification.md +++ b/accepted/future-releases/extension-types/feature-specification.md @@ -849,12 +849,11 @@ is used to invoke these constructors, and the type of such an expression is Name<T1, .. Ts>. *In short, extension type constructors appear to be very similar to -constructors in classes, and they correspond to the situation -where the enclosing class has a single, non-late, final instance -variable, which is initialized according to the normal rules for -constructors (in particular, it can occur by means of `this.id`, or in -an initializer list, or by an initializing expression in the -declaration itself, but it is an error if it does not occur at all).* +constructors in classes, and they correspond to the situation where the +enclosing class has a single, non-late, final instance variable, which is +initialized according to the normal rules for constructors (in particular, +it can occur by means of `this.id`, or in an initializer list, but it is an +error if the initialization does not occur at all).* An extension type `V` used as an expression (*a type literal*) is allowed and has static type `Type`. From 7edefe86bd4f2f8d1198441ff2f0b02417dc6571 Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Tue, 4 Jul 2023 17:21:18 +0200 Subject: [PATCH 12/29] Add discussion section about `implements T` where `T` is final --- .../extension-types/feature-specification.md | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/accepted/future-releases/extension-types/feature-specification.md b/accepted/future-releases/extension-types/feature-specification.md index 968c83cec5..8866f0bcf0 100644 --- a/accepted/future-releases/extension-types/feature-specification.md +++ b/accepted/future-releases/extension-types/feature-specification.md @@ -1129,3 +1129,81 @@ extension type V(S it) { void foo() => V1(it).foo(); } ``` + + +### Allow implementing a final extension type? + +Consider the following program: + +```dart +final class F {} // A regular class. Could also use `int`. +extension type V1(F f) implements F; // OK. + +final extension type V2(num n); +extension type V3(int i) implements V2; // Error when `V2` is final. +extension type V4(V2 v2) implements V2; // Error or not? +``` + +The declaration of `V1` introduces a subtype of the final class `F`, even +though the purpose of `final` on a class is otherwise to prevent the +declaration of such subtypes (at least outside the current library). + +This is accepted because an extension type does not introduce subsumption +relative to the `final` class like a subclass would. If we allow a +declaration like `class G implements F {...}` in some other library then we +could have a reference of type `F` and it could be an instance of `G`, and +this means that we do not have any guarantees about which implementation of +any member we would execute. (So `G` destroys a lot of optimizations.) From +a software engineering point of view, we don't want to allow `G` because a +new instance member added to `F` could break `G`. (So `G` turns a lot of +otherwise safe updates in `F` into breaking changes.) + +In contrast, the declaration of `V` does not turn addition of members of +`F` into a breaking change, because `V` is allowed to redeclare any member +with any signature. So callers of `V.someMember()` will still run the same +code based on the same signature. It might be seen as a problem that `V` +redeclares a member of `F`, but that can be changed in a major update of +`V`. + +`V3` is an error because `V2` declares that it is `final`, which is taken +to indicate that the maintainers of `V2` do not want to have a large number +of dependent declarations "out there", such that they can't change anything +at all about the implementation of `V2` because some of those dependent +declarations will break. It's exactly the same kind of reasoning that we'd +use for a `final` class: I do this in order to reserve some freedom to +change my implementation. + +The discussion in this section is concerned with `V4`. Do we want to make +that declaration a compile-time error? + +An argument in favor of making it an error would be that `V2` is declared +to be `final`, and this implies that `implements V2` is an error. No +exceptions. + +An argument in the opposite direction is that `V4` does not actually depend +on the representation type of `V2` (because it uses `V2` as its +representation type, not `num` or a subtype thereof), and this makes the +relationship between `V4` and `V2` similar to the relationship between `V1` +and `F`. + +In general, it is confusing that we accept `implements F`, but `implements +V2` is an error, and in both cases the would-be superinterface has the +modifier `final`. So developers would need to learn a rule along the lines +of "an extension type ignores `final` on a regular class, but it respects +`final` on an extension type, unless it is a supertype of a representation +type". + +If we stick to the rules as proposed in this feature specification then +we'll have something slightly simpler: "an extension type ignores `final` +on a regular class, but it respects `final` on an extension type". + +However, I don't think we should go all the way to "`implements T` is +always an error when `T` is `final`" (`T` could be an extension type or +a non-extension type, that doesn't matter). The reason is that the ability +to create an extension type whose representation type is a `final` type (in +general: any type that can't have non-bottom subtypes) is crucial: The +performance benefits (size and speed) of using an `int` rather than using a +wrapper class is so huge that it is likely to be a major use case for +extension types that they can allow us to use a built-in class as the +representation, and still have a specialized interface—that is, an +extension type. From 8feeeb835286e5ec098ca6c909615a930063e391 Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Thu, 6 Jul 2023 17:50:52 +0200 Subject: [PATCH 13/29] Remove the support for `final extension type` --- .../extension-types/feature-specification.md | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/accepted/future-releases/extension-types/feature-specification.md b/accepted/future-releases/extension-types/feature-specification.md index 8866f0bcf0..2f102418d0 100644 --- a/accepted/future-releases/extension-types/feature-specification.md +++ b/accepted/future-releases/extension-types/feature-specification.md @@ -15,6 +15,9 @@ information about the process, including in their change logs. [1]: https://github.com/dart-lang/language/blob/master/working/1426-extension-types/feature-specification-views.md [2]: https://github.com/dart-lang/language/blob/master/working/extension_structs/overview.md +2023.07.06 + - Removed the support for `final` extension types. + 2023.06.30 - Change the feature name and keywords to `extension type`, adjust representation type and name declaration to be similar to a primary @@ -395,7 +398,7 @@ with some rules for elements used in extension type declarations: ```ebnf ::= - 'final'? 'extension' 'type' ? + 'extension' 'type' ? ? '{' ( )* @@ -858,14 +861,10 @@ error if the initialization does not occur at all).* An extension type `V` used as an expression (*a type literal*) is allowed and has static type `Type`. -An extension type declaration _DV_ can have the type modifier -`final`. In this case it is a compile-time error for another extension type -declaration _DV2_ to have _DV_ as a superinterface, if _DV2_ is declared in -a different library. - -*As the grammar shows, any occurrence of the keywords `abstract`, -`base`, `interface`, `sealed`, or `mixin` in an extension type -declaration is a syntax error.* +*Class modifiers can not be used with extension types. As the grammar +shows, any occurrence of the keywords `abstract`, `final`, `base`, +`interface`, `sealed`, or `mixin` in an extension type declaration header +is a syntax error.* ### Composing Extension Types @@ -1133,6 +1132,12 @@ extension type V(S it) { ### Allow implementing a final extension type? +Note that this section is about the use of a `final` modifier on an +extension type declaration (similar to a class modifier), but that +mechanism has been removed entirely as of July 6 2023. This makes the +current section rather hypothetical; we're just keeping it to document some +discussions along the way. + Consider the following program: ```dart From afc9703416f2ec70a763460e1cba90f8a88c5d11 Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Thu, 6 Jul 2023 17:51:16 +0200 Subject: [PATCH 14/29] Typo --- .../future-releases/extension-types/feature-specification.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accepted/future-releases/extension-types/feature-specification.md b/accepted/future-releases/extension-types/feature-specification.md index 2f102418d0..939f50d145 100644 --- a/accepted/future-releases/extension-types/feature-specification.md +++ b/accepted/future-releases/extension-types/feature-specification.md @@ -16,7 +16,7 @@ information about the process, including in their change logs. [2]: https://github.com/dart-lang/language/blob/master/working/extension_structs/overview.md 2023.07.06 - - Removed the support for `final` extension types. + - Remove the support for `final` extension types. 2023.06.30 - Change the feature name and keywords to `extension type`, adjust From e2917dacff4bd64a9ac586adc7828f3834196511 Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Thu, 6 Jul 2023 18:10:16 +0200 Subject: [PATCH 15/29] Clarify description of extension type member invocation (static resolution) --- .../extension-types/feature-specification.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/accepted/future-releases/extension-types/feature-specification.md b/accepted/future-releases/extension-types/feature-specification.md index 939f50d145..c5602bdbc9 100644 --- a/accepted/future-releases/extension-types/feature-specification.md +++ b/accepted/future-releases/extension-types/feature-specification.md @@ -971,7 +971,7 @@ does not exist, and _DV_ does not declare a member named _m_. well-defined signature just like if _DV_ had been a class.* *Assume that _DV_ is an extension type declaration named `Name`, and the -extension type `V1`, declared by _DV1_, is a superinterface of _DV_ (could +type `V1`, declared by _DV1_, is a superinterface of _DV_ (`V1` could be an extension type or a non-extension type). Let `m` be the name of a member of `V1`. If _DV_ also declares a member named `m` then the latter may be considered similar to a declaration that "overrides" the former. @@ -980,8 +980,9 @@ resolved statically, and hence there is no override relationship among the two in the traditional object-oriented sense (that is, it will never occur that the statically known declaration is the member of `V1`, and the member invoked at run time is the one in _DV_). A receiver with static type `V1` -will invoke the declaration in _DV1_, and a receiver with static type -`Name` (or `Name<...>`) will invoke the one in _DV_.* +will invoke the declaration in _DV1_, and a receiver with a static type +which is a reference to _DV_ (like `Name` or `Name<...>`) will invoke the +one in _DV_.* Hence, we use a different word to describe the relationship between a member named _m_ of a superinterface, and a member named _m_ which is From fb0dec828497f0d13f2851ffd1d3c4686d4a89ba Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Fri, 7 Jul 2023 11:57:28 +0200 Subject: [PATCH 16/29] Adjust the proposal to make the representation getter accessible from the outside (in other words: drop the lexical-only-access exception) --- .../future-releases/extension-types/feature-specification.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/accepted/future-releases/extension-types/feature-specification.md b/accepted/future-releases/extension-types/feature-specification.md index c5602bdbc9..78dec6a44c 100644 --- a/accepted/future-releases/extension-types/feature-specification.md +++ b/accepted/future-releases/extension-types/feature-specification.md @@ -113,10 +113,7 @@ declares the name and type of the representation in a way which is a special case of the [primary constructor proposal][]. In the body of the extension type the representation object is in scope, with the declared name and type, as if it had been a final instance -variable in a class. It differs from an instance variable declaration in -that it is not available in the interface of the extension type (that is, -we can use `id` inside the extension type declaration, but we can't use -`e.id` from the outside). +variable in a class. [primary constructor proposal]: https://github.com/dart-lang/language/pull/3023 From a6a62b8475fe42711da7a0893d4a48e5ffcfcb92 Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Tue, 11 Jul 2023 20:43:53 +0200 Subject: [PATCH 17/29] Review response --- .../extension-types/feature-specification.md | 239 ++++++++++++------ 1 file changed, 157 insertions(+), 82 deletions(-) diff --git a/accepted/future-releases/extension-types/feature-specification.md b/accepted/future-releases/extension-types/feature-specification.md index 78dec6a44c..ba137a7e11 100644 --- a/accepted/future-releases/extension-types/feature-specification.md +++ b/accepted/future-releases/extension-types/feature-specification.md @@ -15,6 +15,9 @@ information about the process, including in their change logs. [1]: https://github.com/dart-lang/language/blob/master/working/1426-extension-types/feature-specification-views.md [2]: https://github.com/dart-lang/language/blob/master/working/extension_structs/overview.md +2023.07.11 + - !!!TODO!!! + 2023.07.06 - Remove the support for `final` extension types. @@ -59,7 +62,8 @@ subsetting as a special case. This functionality is entirely static. Invocation of an extension type member is resolved at compile-time, based on the static type of the -receiver. +receiver, and the actual type arguments (if any) are obtained directly from +the static type of the receiver. An extension type may be considered to be a zero-cost abstraction in the sense that it works similarly to a wrapper object that holds the wrapped @@ -74,7 +78,8 @@ object will never exist at run time, and a reference whose type is the extension type will actually refer directly to the underlying "wrapped" object. This fact also determines the behavior of `as` and `is`: Those operations will refer to the run-time type of the representation object, -and the run-time value of the extension type is the representation type. +and the extension type is just an alias for the representation type at run +time. Consider a member access (e.g., a method call like `e.m(2)`) where the static type of the receiver (`e`) is an extension type `V`. @@ -104,7 +109,7 @@ being induced implicitly (for example, `foo()` means `this.foo()` if the extension type contains a method declaration named `foo`, or it has a superinterface that has a `foo`, and no `foo` exists in the enclosing top-level scope). In other words, scopes and `this` have exactly the same -interaction as in a regular class. +interaction as in a class. A reference to the representation object typed by its run-time type or a supertype thereof (that is, typed by a "normal" type for the @@ -113,7 +118,9 @@ declares the name and type of the representation in a way which is a special case of the [primary constructor proposal][]. In the body of the extension type the representation object is in scope, with the declared name and type, as if it had been a final instance -variable in a class. +variable in a class. Similarly, if the representation name is `id` then the +representation object can be accessed using `this.id` in the body or `e.id` +anywhere, as long as the static type of `e` is that representation type. [primary constructor proposal]: https://github.com/dart-lang/language/pull/3023 @@ -169,9 +176,9 @@ extension type IdNumber(int i) { // so "smaller" means "older". operator <(IdNumber other) => i < other.i; - // Assume that we can verify an ID number relative to + // Assume that we can validate an ID number relative to // `Some parameters`, filtering out some fake ID numbers. - bool verify(Some parameters) => ...; + bool isValid(Some parameters) => ...; ... // Some other members, whatever is needed. @@ -185,7 +192,7 @@ void main() { var safeId = IdNumber(42424242); - safeId.verify(); // OK, could be true. + if (!safeId.isValid(someArguments)) throw "Bad IdNumber!"; // OK. safeId + 10; // Compile-time error, no operator `+`. 10 + safeId; // Compile-time error, wrong argument type. myUnsafeId = safeId; // Compile-time error, wrong type. @@ -402,7 +409,9 @@ with some rules for elements used in extension type declarations: '}' ::= - ('.' )? '(' ')' + ('.' )? '(' ')' + + ::= | 'new' ::= ``` @@ -431,7 +440,7 @@ This document needs to refer to extension type method invocations including each part that determines the static analysis and semantics of this invocation, so we will use a standardized phrase and talk about: An invocation of the extension type member `m` on the receiver `e` according -to the extension type declaration `V` and with the actual type arguments +to the extension type declaration `V` with the actual type arguments T1, ..., Ts. In the case where `m` is a method, the invocation could be an extension @@ -466,47 +475,57 @@ _the member declaration named `n` that DV has_ is said declaration. *For a declaration in an extension type, this definition is unambiguous for an extension type that has no compile-time errors, because name clashes -must be resolved by `V`. If the declaration is from a superinterface which +must be resolved by _DV_. If the declaration is from a superinterface which is not an extension type then it is handled specially, and we do not need to have a unique declaration of `n` "that _DV_ has".* -Consider an invocation of the extension type member `m` on the receiver `e` -according to the extension type declaration `V` and with the actual type -arguments T1, ..., Ts. If the invocation -includes an actual argument part (possibly including some actual type -arguments) then call it `args`. Finally, assume that `V` declares the type -variables X1, ..., Xs. +Consider an invocation of the extension type member `m` on the receiver +expression `e` according to the extension type declaration `V` with the +actual type arguments T1, ..., Ts. If +the invocation includes an actual argument part (possibly including some +actual type arguments) then call it `args`. Finally, assume that `V` +declares the type variables X1, ..., Xs. *Note that it is known that -V<T1, ..., Ts> +V\1, ..., Ts> has no compile-time errors. In particular, the number of actual type arguments is correct, and it is a regular-bounded type, and the static type of `e` is a subtype of -V<T1, ..., Ts>, +V\1, ..., Ts>, or a subtype of the corresponding instantiated representation type (defined below). This is required when we decide that a given -expression is an extension type member invocation.* +expression is an extension type member invocation, but it is already +ensured by normal static analysis of subexpressions like `e`.* -If the name of `m` is a name in the interface of `Object` (*that is, -`toString`, `==`, etc.*), the static analysis of the invocation is -treated as an ordinary instance member invocation on a receiver of -type `Object` and with the same `args`, if any. +If the name of `m` is a name in the interface of `Object` (that is, +`toString`, `==`, `hashCode`, `runtimeType`, or `noSuchMethod`), the static +analysis of the invocation is treated as an ordinary instance member +invocation on a receiver of type `Object?` and with the same `args`, if +any. Otherwise, a compile-time error occurs if `V` does not have a member named `m`. -If `V` has a member named `m` which is declared in a non-extension type -superinterface `S` and not redeclared by any extension type superinterfaces -that have `S` as a superinterface, the invocation of `m` is treated as an -invocation of a regular class instance member whose member signature is the -combined member signatures of all declarations of `m` in the direct -superinterfaces of `V`. +Otherwise, `V` has a member named `m`. It is provided by a unique +declaration which is an extension type member, or it is provided by a set +of members of the interfaces of non-extension types, as described in the +following. + +If `V` has a direct or indirect non-extension type superinterface `S` which +has a member named `m` which is not declared by any direct or indirect +extension type superinterface of `V`, the invocation of `m` is treated as +an invocation of a regular class instance member whose member signature is +the combined member signatures of all declarations of `m` in the direct +superinterfaces of `V`. *In other words, members "inherited" from non-extension type superinterfaces are invoked as normal class instance members, as if we could "see through the veil" that is the extension type and call members of the representation type which have been unveiled by including `S` as a -superinterface, directly or indirectly.* +superinterface, directly or indirectly. Note that this behavior is only +supported in the case where no direct or indirect extension type +superinterface declares a member named `m`, otherwise a compile-time error +occurs as specified below.* Otherwise, let _Dm_ be the declaration of `m` that `V` has. @@ -519,6 +538,9 @@ invocation has static type [T1/X1 .. Ts/Xs]F. *This is an extension type method tear-off.* +An extension type method tear-off can be followed by actual type arguments, +which yields a generic function instantiation. + If _Dm_ is a method with function type `F`, and `args` exists, the static analysis of the extension type member invocation is the same as that of an invocation with argument part `args` of a function with type @@ -529,12 +551,12 @@ type of the invocation as a whole.* ### Dynamic Semantics of an Extension Type Member Invocation -Consider an invocation of the extension type member `m` on the receiver `e` -according to the extension type declaration `V` and with actual type -arguments T1, ..., Ts. If the invocation -includes an actual argument part (possibly including some actual type -arguments) then call it `args`. Assume that `V` declares the type variables -X1, ..., Xs. +Consider an invocation of the extension type member `m` on the receiver +expression `e` according to the extension type declaration `V` with actual +type arguments T1, ..., Ts. If the +invocation includes an actual argument part (possibly including some actual +type arguments) then call it `args`. Assume that `V` declares the type +variables X1, ..., Xs. Let _Dm_ be the declaration named `m` that `V` has. @@ -542,9 +564,9 @@ Evaluation of this invocation proceeds by evaluating `e` to an object `o`. Then, if `args` is omitted and _Dm_ is a getter, execute the body of -said getter in an environment where `this` and the name of the -representation are bound to `o`, and the type variables of `V` are -bound to the actual values of +said getter in an environment where `this` is bound to `o` and the +representation name denotes a getter that returns `o`, and the type +variables of `V` are bound to the actual values of T1, .. Ts. If the body completes returning an object `o2` then the invocation evaluates to `o2`. If the body throws an object and a stack trace @@ -567,6 +589,10 @@ had different actual type arguments. Hence, we can not consider two extension type method tear-offs equal just because they have the same receiver.* +The closurization is subject to generic function instantiation in the case +where `args` is omitted, but the invocation is followed by actual type +arguments. + Otherwise, the following is known: `args` is included, and _Dm_ is a method. The invocation proceeds to evaluate `args` to an actual argument list `args1`. Then it executes the body of _Dm_ in an @@ -583,6 +609,23 @@ trace. ## Static Analysis of Extension Types +For the purpose of the static analysis, the extension type is considered to +have a final instance variable whose name is the representation name and +whose declared type is the representation type. + +Compile-time errors associated with constructors occur accordingly. + +*For example, any non-primary constructor must initialize said instance +variable in their initializer list, or using an initializing formal +parameter (`this.id`).* + +All name conflicts specified in the language specification section 'Class +Member Conflicts' occur as well in an extension type declaration. + +*For example, it is a compile-time error if an extension type has name `V` +and has an instance member named `V`, and it is a compile-time error if it +has a type parameter named `X` and it has an instance member named `X`.* + Assume that T1, .. Ts are types, and `V` resolves to an extension type declaration of the @@ -595,7 +638,7 @@ extension type V(T id) ... { ``` It is then allowed to use -V<T1, .. Ts> +V\1, .. Ts> as a type. *For example, it can occur as the declared type of a variable or parameter, @@ -608,10 +651,10 @@ where one or more extension types occur as type arguments (e.g., `List.empty()`).* A compile-time error occurs if the type -V<T1, .. Ts> +V\1, .. Ts> is not regular-bounded. -*In other words, such types can not be super-bounded. The reason for this +*In other words, such types cannot be super-bounded. The reason for this restriction is that it is unsound to execute code in the body of `V` in the case where the values of the type variables do not satisfy their declared bounds, and those values will be obtained directly from the static @@ -620,17 +663,20 @@ type of the receiver in each member invocation on `V`.* A compile-time error occurs if a type parameter of an extension type declaration occurs in a non-covariant position in the representation type. -When `s` is zero, -V<T1, .. Ts> +When `s` is zero *(that is, the declaration of `V` is not generic)*, +V\1, .. Ts> simply stands for `V`, a non-generic extension type. When `s` is greater than zero, a raw occurrence `V` is treated like a raw type: Instantiation to bound is used to obtain the omitted type arguments. *Note that this may yield a super-bounded type, which is then a compile-time error.* -We say that the static type of said variable, parameter, etc. +If such a type V\1, .. Ts> +is used as the type of a declared name +*(of a variable, parameter, etc)*, +we say that the static type of the declared name _is the extension type_ -V<T1, .. Ts>, +V\1, .. Ts>, and that its static type _is an extension type_. It is a compile-time error if `await e` occurs, and the static type of @@ -638,7 +684,12 @@ It is a compile-time error if `await e` occurs, and the static type of `FutureOr` for any `T`. A compile-time error occurs if an extension type declares a member whose -name is declared by `Object` as well. +basename is the basename of an instance member declared by `Object` as +well. + +*Given that the static analysis considers the representation as a final +instance variable, it follows that it is an error to use these names as the +representation name as well.* *For example, an extension type declaration cannot declare an operator `==` or a member named `noSuchMethod` or `toString`. The rationale is that these @@ -650,32 +701,33 @@ error for now, we have the option to allow it, perhaps with some restrictions, in a future version of Dart.* A compile-time error occurs if an extension type is used as a -superinterface of a class or a mixin, or if an extension type is used to -derive a mixin. +superinterface of a class, mixin, or enum declaration, or if an extension +type is used in a mixin application as a superclass or as a mixin. *In other words, an extension type cannot occur as a superinterface in an -`extends`, `with`, `implements`, or `on` clause of a class or mixin. On -the other hand, it can occur in other ways, e.g., as a type argument of a -superinterface of a class.* +`extends`, `with`, `implements`, or `on` clause of a class, mixin, or enum. +On the other hand, it can occur in other ways, e.g., as a type argument of +a superinterface of a class.* If `e` is an expression whose static type `V` is the extension type -Name<T1, .. Ts> and `m` is the +Name\1, .. Ts> and `m` is the name of a member that `V` has, a member access like `e.m(args)` is treated as an invocation of the extension type member `m` on the receiver `e` -according to the extension type declaration `Name` and with the actual type +according to the extension type declaration `Name` with the actual type arguments T1, ..., Ts, with the actual argument part `args`. Similarly, `e.m` is treated an invocation of the extension type member `m` -on the receiver `e` according to the extension type declaration `Name` and +on the receiver `e` according to the extension type declaration `Name` with the actual type arguments T1, ..., Ts and no actual argument part. -*Setter invocations are treated as invocations of methods with a -single argument.* +*Setter invocations are treated as invocations of methods with a single +argument. Similarly, operator invocations are treated as method invocations +with unusual member names.* If `e` is an expression whose static type `V` is the extension type -Name<T1, .. Ts> +Name\1, .. Ts> and `V` has no member whose basename is the basename of `m`, a member access like `e.m(args)` may be an extension member access, following the normal rules about applicability and accessibility of extensions, @@ -689,7 +741,7 @@ and type parameters `m(args)`, if a declaration named `m` is found in the body of _DV_ then that invocation is treated as an invocation of the extension type member `m` on the receiver `this` according to the extension type -declaraten `Name` and with the actual type arguments +declaraten `Name` with the actual type arguments T1, ..., Ts, and with the actual argument part `args`. This is just the same treatment of `this` as in the body of a class.* @@ -735,7 +787,7 @@ Assume that the representation declaration of _DV_ is `(R id)`. We then say that the _declared representation type_ of `Name` is `R`, and the _instantiated representation type_ corresponding to -Name<T1,.. Ts> is +Name\1,.. Ts> is [T1/X1, .. Ts/Xs]R. We will omit 'declared' and 'instantiated' from the phrase when it is clear @@ -745,7 +797,7 @@ instantiation of an extension type. *For non-generic extension type declarations, the representation type is the same in either case.* Let `V` be an extension type of the form -Name<T1, .. Ts>, and let +Name\1, .. Ts>, and let `R` be the corresponding instantiated representation type. If `R` is non-nullable then `V` is a proper subtype of `Object`, and `V` is non-nullable. Otherwise, `V` is a proper subtype of `Object?`, and @@ -766,7 +818,7 @@ In the body of a member of an extension type declaration _DV_ named `Name` and declaring the type parameters X1, .. Xs, the static type of `this` is -Name<X1 .. Xs>. +Name\1 .. Xs>. The static type of the representation name is the representation type. @@ -785,7 +837,7 @@ representation dependency on itself. *In other words, cycles are not allowed. This ensures that it is always possible to find a non-extension type which is the ultimate -representation type of any given extension type.* +representation type of the given extension type.* The *extension type erasure* of an extension type `V` is obtained by recursively replacing every subterm of `V` which is an extension type by @@ -822,12 +874,13 @@ also be used to verify that the representation object satisfies the requirements for having that extension type.* The `` works as a constructor. The optional -`('.' )` in the grammar is used to declare this constructor -with a name of the form ` '.' ` *(at times -described as a "named constructor")*. It is a constant constructor: If `e` -is a constant expression and `V(e)` is not an error, then `V(e)` is a -constant expression. Other constructors may be declared `const` or not, -following the normal rules for constant constructors. +`('.' )` in the grammar is used to declare this +constructor with a name of the form ` '.' ` *(at +times described as a "named constructor")*, or ` '.' 'new'`. It +is a constant constructor: If `e` is a constant expression and `V(e)` is +not an error, then `V(e)` is a constant expression. Other constructors may +be declared `const` or not, following the normal rules for constant +constructors. A compile-time error occurs if an extension type constructor includes a superinitializer. *That is, a term of the form `super(...)` or @@ -836,17 +889,17 @@ superinitializer. *That is, a term of the form `super(...)` or A compile-time error occurs if an extension type constructor declares a super parameter. *For instance, `Name(super.x);`.* -*In the body of a generative extension type constructor, the static type of -`this` is the same as it is in any instance member of the extension type -declaration, that is, `Name`, where `X1 .. Xk` are the type -parameters declared by `Name`.* +*In the body of a non-redirecting generative extension type constructor, +the static type of `this` is the same as it is in any instance member of +the extension type declaration, that is, `Name`, where `X1 .. Xk` +are the type parameters declared by `Name`.* An instance creation expression of the form -Name<T1, .. Ts>(...) +Name\1, .. Ts>(...) or -Name<T1, .. Ts>.name(...) +Name\1, .. Ts>.name(...) is used to invoke these constructors, and the type of such an expression is -Name<T1, .. Ts>. +Name\1, .. Ts>. *In short, extension type constructors appear to be very similar to constructors in classes, and they correspond to the situation where the @@ -896,16 +949,17 @@ relation which was specified earlier. A compile-time error occurs if `V1` is a type name or a parameterized type which occurs as a superinterface in an extension type declaration _DV_, but `V1` does not denote an extension type, and `V1` does not denote a -supertype of the ultimate representation type of _DV_. +supertype of the extension type erasure of the representation type of +_DV_. A compile-time error occurs if any direct or indirect superinterface of _DV_ is the type `Name` or a type of the form `Name<...>`. *As usual, subtype cycles are not allowed.* Assume that _DV_ has two direct or indirect superinterfaces of the form -W<T1, .. Tk> +W\1, .. Tk> respectively -W<S1, .. Sk>. +W\1, .. Sk>. A compile-time error occurs if Tj @@ -928,7 +982,7 @@ name of _DV_ and `id1` is the representation name of _DV1_.* Assume that _DV_ declares an extension type declaration named `Name` with type parameters X1 .. Xs, and `V1` is a superinterface of _DV_. Then -Name<T1, .. Ts> +Name\1, .. Ts> is a subtype of [T1/X1 .. Ts/Xs]V1 for all T1, .. Ts. @@ -1069,8 +1123,8 @@ test and type cast on the run-time representation of the extension type as described above. An extension type `V` used as an expression (*a type literal*) evaluates -to the value of the corresponding instantiated representation type -used as an expression. +to the value of the extension type erasure of the representation type +used as an expression *(also known as the ultimate representation type)*. ### Summary of Typing Relationships @@ -1099,6 +1153,27 @@ This section mentions a few topics that have given rise to discussions. +### Is the primary constructor constant? + +This proposal specifies that the primary constructor is always +constant. The point is that this is possible, because a primary constructor +and the unique `final` pretend instance variable will always satisfy the +requirements. + +We may then allow `const` to be specified explicitly on the primary +constructor later on, in case the developer wishes to make that explicit. + +We could also require `const` in the future and simply say that the primary +constructor of an extension type cannot be constant at this time. + +Finally, we could require `const` explicitly on the primary constructor +already now, if it should be a constant constructor. + +In any case, every non-primary constructor in an extension type would +potentially have properties that would make `const` an error, so they must +specify `const` explicitly as usual. + + ### Support "private inheritance"? In the current proposal there is a subtype relationship between every From 631ad4754a4fc10c54fe3d6d57444460fe6c4ffe Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Wed, 12 Jul 2023 15:07:27 +0200 Subject: [PATCH 18/29] Many small adjustments: Clarify extension member invocations (static/dynamic); what it means to "have" (non-)extension type members; add errors (incl implements T); cleaned up duplicated error messages --- .../extension-types/feature-specification.md | 349 +++++++++++------- 1 file changed, 224 insertions(+), 125 deletions(-) diff --git a/accepted/future-releases/extension-types/feature-specification.md b/accepted/future-releases/extension-types/feature-specification.md index ba137a7e11..90f8bd05d4 100644 --- a/accepted/future-releases/extension-types/feature-specification.md +++ b/accepted/future-releases/extension-types/feature-specification.md @@ -439,24 +439,32 @@ be declared and called or torn off as usual, e.g., This document needs to refer to extension type method invocations including each part that determines the static analysis and semantics of this invocation, so we will use a standardized phrase and talk about: An -invocation of the extension type member `m` on the receiver `e` according -to the extension type declaration `V` with the actual type arguments -T1, ..., Ts. +invocation of the extension type member `m` on the receiver expression `e` +according to the extension type declaration `V` with the actual type +arguments T1, ..., Ts. In the case where `m` is a method, the invocation could be an extension -type member property extraction (*a tear-off*) or a method invocation, and -in the latter case there will also be an ``, optionally -passing some actual type arguments, and (non-optionally) passing an actual -argument list. +type member property extraction (*a getter invocation or a tear-off*) or a +method invocation, and in the latter case there will also be an +``, optionally passing some actual type arguments, and +(non-optionally) passing an actual argument list. Finally, a property +extraction may be a property extraction followed by a term derived from +``, in which case it denotes a generic function +instantiation of the function object yielded by the property extraction. + +*These constructs are standard, but they have different semantics when `m` +is an extension type member, so we need to state their static analysis and +dynamic semantics separately.* The case where `m` is a setter is syntactically different, but the treatment is the same as with a method accepting a single argument -(*so we will not specify that case explicitly*). +(*so we will not specify that case explicitly*). Similarly for operator +invocations. *We need to mention all these elements together, because each of them plays a role in the static analysis and the dynamic semantics of the invocation. There is no syntactic representation in the language for this concept, -because the same extension type method invocation can have many different +because the same extension type method invocation can have different syntactic forms, and both `V` and T1, ..., Ts are implicit in the actual syntax.* @@ -464,29 +472,55 @@ syntax.* ### Static Analysis of an Extension Type Member Invocation +This section specifies the static analysis of an extension type member +invocation. Note that this is the meta-syntactic concept which was +introduced in the previous section, and the underlying concrete syntax does +not explicitly include all elements needed to give this specification. The +subsequent sections will specify all the elements ("an invocation of the +extension type member `m` ...") based on specific syntax, and then rely an +this section to specify the static analysis of the given situation. + We need to introduce a concept that is similar to existing concepts for regular classes. -We say that an extension type declaration _DV_ _has_ a member named `n` -in the case where _DV_ declares a member named `n`, and in the case -where _DV_ has no such declaration, but _DV_ has a direct -superinterface `V` that has a member named `n`. In both cases, -_the member declaration named `n` that DV has_ is said declaration. - -*For a declaration in an extension type, this definition is unambiguous for -an extension type that has no compile-time errors, because name clashes -must be resolved by _DV_. If the declaration is from a superinterface which -is not an extension type then it is handled specially, and we do not need -to have a unique declaration of `n` "that _DV_ has".* +We say that an extension type declaration _DV_ _has_ an extension type +member named `n` in the case where _DV_ declares a member named `n`, and in +the case where _DV_ has no such declaration, but _DV_ has a direct +extension type superinterface `V` that has an extension type member named +`n`. In both cases, when this is unique, _the extension type member +declaration named `n` that DV has_ is said declaration. + +The type (function type for a method, return type for a getter) of this +declaration relative to this invocation is determined by repeatedly +computing the instantiated type of the superinterface during the traversal +of the superinterface graph to the superinterface declaration that contains +this member daclaration, and then substituting the actual values of the +type parameters of the extension type into the type of that declaration. + +Similarly, we say that an extension type declaration _DV_ _has_ a +non-extension type member named `n` in the case where _DV_ does not declare +a member named `n`, but _DV_ has a direct extension type superinterface `V` +that has a non-extension type member named `n`, or _DV_ has a direct +non-extension type superinterface `T` whose interface contains a member +signature named `n`. + +The member signature of such a member is the combined member signature of +all non-extension type members named `n` that _DV_ has, again using a +repeated computation of the instantiated type of each superinterface on the +path to the given non-extension type superinterface. + +Now we are ready to specify the invocation itself. Consider an invocation of the extension type member `m` on the receiver expression `e` according to the extension type declaration `V` with the actual type arguments T1, ..., Ts. If the invocation includes an actual argument part (possibly including some -actual type arguments) then call it `args`. Finally, assume that `V` -declares the type variables X1, ..., Xs. +actual type arguments) then call it `args`. If the invocation does not +include an actual argument part, but it does include a list of actual type +arguments, call it `typeArgs`. Finally, assume that `V` declares the type +variables X1, ..., Xs. -*Note that it is known that +*Note that in this section it is known that V\1, ..., Ts> has no compile-time errors. In particular, the number of actual type arguments is correct, and it is a regular-bounded type, @@ -506,47 +540,48 @@ any. Otherwise, a compile-time error occurs if `V` does not have a member named `m`. -Otherwise, `V` has a member named `m`. It is provided by a unique +Otherwise, `V` has a member named `m`. *It is provided by a unique declaration which is an extension type member, or it is provided by a set of members of the interfaces of non-extension types, as described in the -following. +following.* + +*Note that it is a compile-time error for the extension type declaration if +`V` has an extension type member named `m` as well as a non-extension type +member named `m`. So there's no need to consider that case here.* -If `V` has a direct or indirect non-extension type superinterface `S` which -has a member named `m` which is not declared by any direct or indirect -extension type superinterface of `V`, the invocation of `m` is treated as -an invocation of a regular class instance member whose member signature is -the combined member signatures of all declarations of `m` in the direct -superinterfaces of `V`. +Consider the case where `V` has a non-extension type member named `m`. In +this case the invocation is treated as an invocation of the instance member +`m` on the given receiver, using the member signature for `m` as specified +earlier in this section. *In other words, members "inherited" from non-extension type -superinterfaces are invoked as normal class instance members, as if we -could "see through the veil" that is the extension type and call members of -the representation type which have been unveiled by including `S` as a -superinterface, directly or indirectly. Note that this behavior is only -supported in the case where no direct or indirect extension type -superinterface declares a member named `m`, otherwise a compile-time error -occurs as specified below.* +superinterfaces, directly or indirectly, are invoked as normal class +instance members, as if we could "see through the veil" that is the +extension type, and call members of the representation type which have been +unveiled by including certain non-extension types as superinterfaces.* -Otherwise, let _Dm_ be the declaration of `m` that `V` has. +Otherwise, `V` has an extension type member `m` with a uniquely determined +declaration _Dm_ *(whose type is obtained by substitutions along the +superinterface path as specified above)*. If _Dm_ is a getter declaration with return type `R` then the static -type of the invocation is -[T1/X1 .. Ts/Xs]R. +type of the invocation is `R`. If _Dm_ is a method with function type `F`, and `args` is omitted, the -invocation has static type -[T1/X1 .. Ts/Xs]F. +invocation has static type `F`. *This is an extension type method tear-off.* -An extension type method tear-off can be followed by actual type arguments, -which yields a generic function instantiation. +Assume that _Dm_ is a method with function type `F`, and `typeArgs` is +provided. A compile-time error occurs if `F` is not a generic function type +where `typeArgs` is a list of actual type arguments that conform to the +declared bounds. If no error occurred, the invocation has the static type +which is a non-generic function type where `typeArgs` are substituted into +the function type. *This is a generic function instantiation of an +extension type method tear-off.* If _Dm_ is a method with function type `F`, and `args` exists, the static analysis of the extension type member invocation is the same as that of an -invocation with argument part `args` of a function with type -[T1/X1 .. Ts/Xs]F. -*This determines the compile-time errors, if any, and it determines the -type of the invocation as a whole.* +invocation with argument part `args` of a function with the given type. ### Dynamic Semantics of an Extension Type Member Invocation @@ -555,10 +590,21 @@ Consider an invocation of the extension type member `m` on the receiver expression `e` according to the extension type declaration `V` with actual type arguments T1, ..., Ts. If the invocation includes an actual argument part (possibly including some actual -type arguments) then call it `args`. Assume that `V` declares the type -variables X1, ..., Xs. +type arguments) then call it `args`. If the invocation omits `args`, but +includes a list of actual type arguments then call them `typeArgs`. Assume +that `V` declares the type variables +X1, ..., Xs. + +The dynamic semantics of an invocation or tear-off or generic function +instantiation of a non-extension type member has the same dynamic semantics +as an instance member invocation on the representation object, using the +member signature. -Let _Dm_ be the declaration named `m` that `V` has. +*For instance, a dynamic type check according to this member signature may +be applied to an actual argument whose static type is `dynamic`.* + +Otherwise, `m` is an extension type member. Let _Dm_ be the declaration +named `m` that `V` has. Evaluation of this invocation proceeds by evaluating `e` to an object `o`. @@ -575,8 +621,8 @@ trace. Otherwise, if `args` is omitted and _Dm_ is a method, the invocation evaluates to a closurization of _Dm_ where -`this` and the name of the representation are bound to `o`, and the -type variables of `V` are bound to the actual values of +`this` and the name of the representation are bound as with the getter +invocation, and the type variables of `V` are bound to the actual values of T1, .. Ts. The operator `==` of the closurization returns true if and only if the operand is the same object. @@ -587,17 +633,21 @@ the same method from the same extension type with the same representation object twice, and still get different behavior, because the extension type had different actual type arguments. Hence, we can not consider two extension type method tear-offs equal just because they have the same -receiver.* +receiver. Optimizations whereby separately torn-off methods are represented +by the same object are allowed, as long as they behave as specified, so +there is no guarantee that two torn-off methods are unequal, unless they +must behave differently.* The closurization is subject to generic function instantiation in the case -where `args` is omitted, but the invocation is followed by actual type -arguments. +where `args` is omitted and `typeArgs` provided, using the same semantics +as usual for a given function object. Otherwise, the following is known: `args` is included, and _Dm_ is a method. The invocation proceeds to evaluate `args` to an actual argument list `args1`. Then it executes the body of _Dm_ in an environment where `this` and the name of the representation are bound -to `o`, the type variables of `V` are bound to the actual values of +in the same way as in the getter invocation, the type variables of `V` are +bound to the actual values of T1, .. Ts, and the formal parameters of `m` are bound to `args1` in the same way that they would be bound for a normal function call. @@ -638,10 +688,10 @@ extension type V(T id) ... { ``` It is then allowed to use -V\1, .. Ts> +V\1, .. Tk> as a type. -*For example, it can occur as the declared type of a variable or parameter, +*Such types can occur as the declared type of a variable or parameter, as the return type of a function or getter, as a type argument in a type, as the representation type of an extension type declaration, as the on-type of an extension declaration, as the type in the `onPart` of a try/catch @@ -651,8 +701,9 @@ where one or more extension types occur as type arguments (e.g., `List.empty()`).* A compile-time error occurs if the type -V\1, .. Ts> -is not regular-bounded. +V\1, .. Tk> +provides a wrong number of type arguments to `V` (when `k` is different +from `s`), and if it is not regular-bounded. *In other words, such types cannot be super-bounded. The reason for this restriction is that it is unsound to execute code in the body of `V` in @@ -672,7 +723,7 @@ type: Instantiation to bound is used to obtain the omitted type arguments. compile-time error.* If such a type V\1, .. Ts> -is used as the type of a declared name +is used as the type of a declared name *(of a variable, parameter, etc)*, we say that the static type of the declared name _is the extension type_ @@ -680,8 +731,8 @@ _is the extension type_ and that its static type _is an extension type_. It is a compile-time error if `await e` occurs, and the static type of -`e` is an extension type which is not a subtype of `Future` or -`FutureOr` for any `T`. +`e` is an extension type which is not a subtype of `Future` for any +`T`. A compile-time error occurs if an extension type declares a member whose basename is the basename of an instance member declared by `Object` as @@ -700,6 +751,16 @@ method `toString`, not the extension type method. Also, when we make this an error for now, we have the option to allow it, perhaps with some restrictions, in a future version of Dart.* +A compile-time error occurs if an extension type has a non-extension +superinterface whose transitive alias expansion is a type variable, a +deferred type, the type `dynamic`, the type `void`, the type `Null`, any +function type, or any type of the form `T?` or `FutureOr`. + +*Note that it is not an error to have `implements int` and similar platform +types, and it is not an error to have `implements T` where `T` is a type +that denotes a `sealed`, `final`, or `base` class in a different library, +or `T` is an enumerated type.* + A compile-time error occurs if an extension type is used as a superinterface of a class, mixin, or enum declaration, or if an extension type is used in a mixin application as a superclass or as a mixin. @@ -709,6 +770,31 @@ type is used in a mixin application as a superclass or as a mixin. On the other hand, it can occur in other ways, e.g., as a type argument of a superinterface of a class.* +It is a compile-time error if _DV_ is an extension type declaration, and +_DV_ has a non-extension type member named `m` as well as an extension type +member named `m`, for any `m`. *In case of conflicts, _DV_ must declare a +member named `m` to resolve the conflict.* + +It is a compile-time error if _DV_ is an extension type declaration, and +_DV_ has a non-extension type member named `m`, and the computation of a +combined member signature for all non-extension type members named `m` +fails. *_DV_ must again declare a member named `m` to resolve the +conflict.* + +A compile-time error occurs if an extension type declaration _DV_ has +two extension type superinterfaces `V1` and `V2`, and both `V1` and `V2` +has an extension type member named `m`, and the two members have distinct +declarations. + +*In other words, an extension type member conflict is always an error, even +in the case where they agree perfectly on the types. _DV_ must override the +given name to eliminate the error. Note that it is not an error if both +`V1` and `V2` have the member named `m` because they both get it from a +common extension type superinterface, such that it is the same +declaration.* + + + If `e` is an expression whose static type `V` is the extension type Name\1, .. Ts> and `m` is the name of a member that `V` has, a member access like `e.m(args)` is treated @@ -722,6 +808,9 @@ on the receiver `e` according to the extension type declaration `Name` with the actual type arguments T1, ..., Ts and no actual argument part. +Similarly, `e.m` is treated the same, but omits +`args`, includes ``. + *Setter invocations are treated as invocations of methods with a single argument. Similarly, operator invocations are treated as method invocations with unusual member names.* @@ -741,7 +830,7 @@ and type parameters `m(args)`, if a declaration named `m` is found in the body of _DV_ then that invocation is treated as an invocation of the extension type member `m` on the receiver `this` according to the extension type -declaraten `Name` with the actual type arguments +declaration `Name` with the actual type arguments T1, ..., Ts, and with the actual argument part `args`. This is just the same treatment of `this` as in the body of a class.* @@ -843,8 +932,9 @@ The *extension type erasure* of an extension type `V` is obtained by recursively replacing every subterm of `V` which is an extension type by the corresponding representation type. -*Note that this extension type erasure exists, because it is a compile-time -error to have a dependency cycle among extension type declarations.* +*Note that the extension type erasure always exists, because it is a +compile-time error to have a dependency cycle among extension type +declarations.* Let X1 extends B1, .. Xs extends Bs @@ -861,6 +951,8 @@ has any compile-time errors. X extends Y, Y extends X, which is an error.* +#### Extension type constructors and their static analysis + An extension type declaration _DV_ named `Name` may declare one or more constructors. A constructor which is declared in an extension type declaration is also known as an _extension type constructor_. @@ -889,10 +981,11 @@ superinitializer. *That is, a term of the form `super(...)` or A compile-time error occurs if an extension type constructor declares a super parameter. *For instance, `Name(super.x);`.* -*In the body of a non-redirecting generative extension type constructor, -the static type of `this` is the same as it is in any instance member of -the extension type declaration, that is, `Name`, where `X1 .. Xk` -are the type parameters declared by `Name`.* +In the body of a non-redirecting generative extension type constructor, the +static type of `this` is the same as it is in any instance member of the +extension type declaration. *That is, the type is `Name`, in an +extension type declaration named `Name` where `X1 .. Xk` are the type +parameters declared by `Name`.* An instance creation expression of the form Name\1, .. Ts>(...) @@ -908,8 +1001,8 @@ initialized according to the normal rules for constructors (in particular, it can occur by means of `this.id`, or in an initializer list, but it is an error if the initialization does not occur at all).* -An extension type `V` used as an expression (*a type literal*) is allowed -and has static type `Type`. +An extension type `V` used as an expression (*a type literal*) is allowed, +and it has static type `Type`. *Class modifiers can not be used with extension types. As the grammar shows, any occurrence of the keywords `abstract`, `final`, `base`, @@ -923,14 +1016,22 @@ This section describes the effect of including a clause derived from `` in an extension type declaration. We use the phrase _the implements clause_ to refer to this clause. -*The rationale is that the set of members and member implementations of a -given extension type may need to overlap with that of other extension type -declarations. The implements clause allows for implementation reuse by -putting some shared members in an extension type `V`, and including `V` in -the implements clause of several extension type declarations -V1 .. Vk, thus "inheriting" the members -of `V` into all of V1 .. Vk without code -duplication.* +*One part of the rationale is that the set of members and member +implementations of a given extension type may need to overlap with that of +other extension type declarations. The implements clause allows for +implementation reuse by putting some shared members in an extension type +`V`, and including `V` in the implements clause of several extension type +declarations V1 .. Vk, thus "inheriting" +the members of `V` into all of V1 .. Vk +without code duplication.* + +*Another part of the rationale is that it is possible to declare that a +given extension type declaration _DV_ can have `implements ... T ...` where +`T` is a non-extension type. This is used to unveil part of the +representation type, in the sense that it creates a subtype relation to `T` +(such that the extension type is assignable to targets of type `T`), and it +allows members of type `T` to be invoked on a receiver whose static type is +the extension type.* *The reason why this mechanism uses the keyword `implements` rather than `extends` to declare a relation that involves "inheritance" is @@ -942,7 +1043,7 @@ Assume that _DV_ is an extension type declaration named `Name`, and `V1` occurs as one of the ``s in the `` of _DV_. In this case we say that `V1` is a _superinterface_ of _DV_. -If _DV_ does not include an `` clause then _DV_ has +If _DV_ does not include an `` clause then _DV_ has `Object?` or `Object` as a direct superinterface, according to the subtype relation which was specified earlier. @@ -952,6 +1053,10 @@ which occurs as a superinterface in an extension type declaration _DV_, but supertype of the extension type erasure of the representation type of _DV_. +*In particular, in order to have `implements T` where `T` is a +non-extension type, it must be sound to consider the representation object +as having type `T`.* + A compile-time error occurs if any direct or indirect superinterface of _DV_ is the type `Name` or a type of the form `Name<...>`. *As usual, subtype cycles are not allowed.* @@ -968,6 +1073,9 @@ is not equal to for any _j_ in _1 .. k_. The notion of equality used here is the same as with the corresponding rule about superinterfaces of classes. +*Note that this is required for extension type superinterfaces as well as +non-extension type superinterfaces.* + Assume that an extension type declaration _DV_ named `Name` has representation type `R`, and that the extension type `V1` with declaration _DV1_ is a superinterface of _DV_ (*note that `V1` may @@ -979,6 +1087,15 @@ error occurs if `R` is not a subtype of `S`. in `V1` when invoking members of `V1`, where `id` is the representation name of _DV_ and `id1` is the representation name of _DV1_.* +*Note that we require `R <: S`, not just that the extension type erasure of +`R` is a subtype of the extension type erasure of `S`. It would have been +sound, and more flexible, to allow the latter. However, when `R <: S` does +not hold, even though the extension type erasures do have the subtype +relation, the author of _DV1_ has chosen to view the representation object +using a representation type `S` that does not "announce its representation +type publicly". Arguably, it is then indicated that _DV_ should not rely on +_DV1_ using that particular representation type.* + Assume that _DV_ declares an extension type declaration named `Name` with type parameters X1 .. Xs, and `V1` is a superinterface of _DV_. Then @@ -987,13 +1104,14 @@ is a subtype of [T1/X1 .. Ts/Xs]V1 for all T1, .. Ts. -*In short, if `V1` is a superinterface of `V` then `V1` is also a -supertype of `V`.* +*In short, if `V1` is a superinterface of `V` then `V1` is also a supertype +of `V`. This is true for extension type superinterfaces as well as +non-extension type superinterfaces.* -A compile-time error occurs if an extension type declaration _DV_ has -two extension type superinterfaces `V1` and `V2`, where both `V1` and `V2` -have a member named _m_, and the two declarations of _m_ are distinct -declarations, and _DV_ does not declare a member named _m_. +A compile-time error occurs if an extension type declaration _DV_ has two +extension type superinterfaces `V1` and `V2`, where both `V1` and `V2` have +an extension type member named _m_, and the two declarations of _m_ are +distinct declarations, and _DV_ does not declare a member named _m_. *In other words, if two different declarations of _m_ are inherited from two extension type superinterfaces then the subinterface must resolve the @@ -1003,24 +1121,6 @@ same declaration (so `V` is a subinterface of `V1` and `V2`, and both `V1` and `V2` are subinterfaces of `V3`, and only `V3` declares _m_, in which case there is no conflict in `V`).* -A compile-time error occurs if an extension type declaration _DV_ has -two superinterfaces `V1` and `V2`, where `V1` is an extension type and `V2` -is a non-extension type, and both `V1` and `V2` have a member named _m_, -and _DV_ does not declare a member named _m_. - -*In other words, member name clashes among an extension type and a -non-extension type superinterface is always an error. _DV_ must override -the given name to eliminate the error.* - -A compile-time error occurs if an extension type declaration _DV_ has -two or more non-extension type superinterfaces `V1 .. Vk`, where each `Vj` -has a member named _m_, and the combined member signature of these members -does not exist, and _DV_ does not declare a member named _m_. - -*In other words, when the extension type has some members which are -"inherited" from some non-extension type superinterfaces, they must have a -well-defined signature just like if _DV_ had been a class.* - *Assume that _DV_ is an extension type declaration named `Name`, and the type `V1`, declared by _DV1_, is a superinterface of _DV_ (`V1` could be an extension type or a non-extension type). Let `m` be the name of a @@ -1044,30 +1144,29 @@ former. from two superinterfaces then the subinterface can resolve the conflict by redeclaring _m_.* -*Note that there is no notion of having a 'correct override relation' -here. With extension types, any member signature can redeclare any -other member signature with the same name, including the case where a -method is redeclared by a getter, or vice versa. The reason for this -is that no call site will resolve to one of several declarations at -run time. Each invocation will statically resolve to one particular -declaration, and this makes it possible to ensure that the invocation -is type correct.* +*There is no notion of having a 'correct override relation' here. With +extension types, any member signature can redeclare any other member +signature with the same name, including the case where a method is +redeclared by a getter, or vice versa. The reason for this is that no call +site will resolve to one of several declarations at run time. Each +invocation will statically resolve to one particular declaration, and this +makes it possible to ensure that the invocation is type correct.* -*Note that extension methods (in a regular `extension` declaration, not an +*Extension methods (in a regular `extension` declaration, not an `extension type`) have a similar nature: An extension declaration `E1` on `T1` and another extension declaration `E2` on `T2` where `T1 <: T2` behave in a way which is similar to overriding in that a more special receiver can invoke `T1.foo` at a call site like `e.foo()`, whereas it would have -invoked `T2.foo` if the static type of `e` had been more general (if -that type would then match `T2`, but not `T1`). In this case there is also -no override relationship between `T1.foo` and `T2.foo`, they are just +invoked `T2.foo` if the static type of `e` had been more general (if that +type would then match `T2`, but not `T1`). In this case there is also no +override relationship between `T1.foo` and `T2.foo`, they are just independent member signatures.* -The effect of having an extension type declaration _DV_ with +*The effect of having an extension type declaration _DV_ with superinterfaces `V1, .. Vk` is that the members declared by _DV_ as well as all members of `V1, .. Vk` that are not redeclared by a declaration in _DV_ can be invoked on a receiver of the type -introduced by _DV_. +introduced by _DV_.* ## Dynamic Semantics of Extension Types @@ -1105,7 +1204,7 @@ will be a subtype of the representation type of `V`.* The run-time representation of a type argument which is an extension type `V` is the run-time representation of the corresponding instantiated -representation type. +representation type. *This wording ensures that we unfold the instantiated representation type recursively, until it is a non-extension type that does not contain any @@ -1129,9 +1228,9 @@ used as an expression *(also known as the ultimate representation type)*. ### Summary of Typing Relationships -*Here is an overview of the subtype relationships of an extension type -`V0` with instantiated representation type `R` and superinterfaces -`V1 .. Vk`, as well as other typing relationships involving `V0`:* +*Here is an overview of the subtype relationships of an extension type `V0` +with instantiated representation type `R` and instantiated superinterface +types `V1 .. Vk`, as well as other typing relationships involving `V0`:* - *`V0` is a proper subtype of `Object?`.* - *`V0` is a supertype of `Never`.* From e0aaff947232a8cbc93d4e0dd45cf9a0552bdbb6 Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Wed, 12 Jul 2023 18:12:53 +0200 Subject: [PATCH 19/29] Added record types to the cannot-implement list --- .../future-releases/extension-types/feature-specification.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/accepted/future-releases/extension-types/feature-specification.md b/accepted/future-releases/extension-types/feature-specification.md index 90f8bd05d4..a35668a976 100644 --- a/accepted/future-releases/extension-types/feature-specification.md +++ b/accepted/future-releases/extension-types/feature-specification.md @@ -754,7 +754,8 @@ restrictions, in a future version of Dart.* A compile-time error occurs if an extension type has a non-extension superinterface whose transitive alias expansion is a type variable, a deferred type, the type `dynamic`, the type `void`, the type `Null`, any -function type, or any type of the form `T?` or `FutureOr`. +function type, any record type, or any type of the form `T?` or +`FutureOr`. *Note that it is not an error to have `implements int` and similar platform types, and it is not an error to have `implements T` where `T` is a type From 832ec4c539e56b536b72f11b36ca8f38c6c17f75 Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Wed, 12 Jul 2023 18:16:10 +0200 Subject: [PATCH 20/29] Extended cannot-implement list with top types --- .../future-releases/extension-types/feature-specification.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/accepted/future-releases/extension-types/feature-specification.md b/accepted/future-releases/extension-types/feature-specification.md index a35668a976..b3acb2ebac 100644 --- a/accepted/future-releases/extension-types/feature-specification.md +++ b/accepted/future-releases/extension-types/feature-specification.md @@ -753,8 +753,8 @@ restrictions, in a future version of Dart.* A compile-time error occurs if an extension type has a non-extension superinterface whose transitive alias expansion is a type variable, a -deferred type, the type `dynamic`, the type `void`, the type `Null`, any -function type, any record type, or any type of the form `T?` or +deferred type, any top type *(including `dynamic` and `void`), the type +`Null`, any function type, any record type, or any type of the form `T?` or `FutureOr`. *Note that it is not an error to have `implements int` and similar platform From 044e3a4df08beaa5f84f4ae810458ef0be28500d Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Wed, 12 Jul 2023 18:24:30 +0200 Subject: [PATCH 21/29] Adjusted cannot-implement list --- .../future-releases/extension-types/feature-specification.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/accepted/future-releases/extension-types/feature-specification.md b/accepted/future-releases/extension-types/feature-specification.md index b3acb2ebac..cb119ccff7 100644 --- a/accepted/future-releases/extension-types/feature-specification.md +++ b/accepted/future-releases/extension-types/feature-specification.md @@ -753,9 +753,9 @@ restrictions, in a future version of Dart.* A compile-time error occurs if an extension type has a non-extension superinterface whose transitive alias expansion is a type variable, a -deferred type, any top type *(including `dynamic` and `void`), the type +deferred type, any top type *(including `dynamic` and `void`)*, the type `Null`, any function type, any record type, or any type of the form `T?` or -`FutureOr`. +`FutureOr` for any type `T`. *Note that it is not an error to have `implements int` and similar platform types, and it is not an error to have `implements T` where `T` is a type From c72869be21ee67da3ec07c5ce4f27d2f31565c6a Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Wed, 12 Jul 2023 19:29:28 +0200 Subject: [PATCH 22/29] Add Function and Record to the cannot-implement list --- .../future-releases/extension-types/feature-specification.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/accepted/future-releases/extension-types/feature-specification.md b/accepted/future-releases/extension-types/feature-specification.md index cb119ccff7..9243542fec 100644 --- a/accepted/future-releases/extension-types/feature-specification.md +++ b/accepted/future-releases/extension-types/feature-specification.md @@ -754,8 +754,8 @@ restrictions, in a future version of Dart.* A compile-time error occurs if an extension type has a non-extension superinterface whose transitive alias expansion is a type variable, a deferred type, any top type *(including `dynamic` and `void`)*, the type -`Null`, any function type, any record type, or any type of the form `T?` or -`FutureOr` for any type `T`. +`Null`, any function type, the type `Function`, any record type, the type +`Record`, or any type of the form `T?` or `FutureOr` for any type `T`. *Note that it is not an error to have `implements int` and similar platform types, and it is not an error to have `implements T` where `T` is a type From b01ce27d297348481a8e0b3eca5b01f25deb0517 Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Thu, 13 Jul 2023 15:39:37 +0200 Subject: [PATCH 23/29] Add change log entry --- .../future-releases/extension-types/feature-specification.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/accepted/future-releases/extension-types/feature-specification.md b/accepted/future-releases/extension-types/feature-specification.md index 9243542fec..f7058bd740 100644 --- a/accepted/future-releases/extension-types/feature-specification.md +++ b/accepted/future-releases/extension-types/feature-specification.md @@ -15,8 +15,9 @@ information about the process, including in their change logs. [1]: https://github.com/dart-lang/language/blob/master/working/1426-extension-types/feature-specification-views.md [2]: https://github.com/dart-lang/language/blob/master/working/extension_structs/overview.md -2023.07.11 - - !!!TODO!!! +2023.07.13 + - Revise many details, in particular the management of ambiguities + involving extension type members and non-extension type members. 2023.07.06 - Remove the support for `final` extension types. From 20562702294fb6574300ab0d9063e6deeedc8381 Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Fri, 14 Jul 2023 17:11:29 +0200 Subject: [PATCH 24/29] Add a paragraph about object patterns V() using an extension type --- .../extension-types/feature-specification.md | 67 +++++++++++-------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/accepted/future-releases/extension-types/feature-specification.md b/accepted/future-releases/extension-types/feature-specification.md index f7058bd740..b0509bec8f 100644 --- a/accepted/future-releases/extension-types/feature-specification.md +++ b/accepted/future-releases/extension-types/feature-specification.md @@ -46,9 +46,8 @@ declaration. An extension type provides a replacement of the members available on instances of an existing type: when the static type of the instance is an extension type _V_, the available instance members are exactly the ones provided by _V_. There may also be some accessible and -applicable extension members (noting that this is from the existing -feature expressed as an `extension` declaration, it is not an `extension -type` declaration). +applicable extension members (that is, from the existing feature expressed +as an `extension` declaration, not an `extension type` declaration). In contrast, when the static type of an instance is not an extension type, it is (by soundness) always the run-time type of the instance or a @@ -78,9 +77,9 @@ However, even though an extension type behaves like a wrapping, the wrapper object will never exist at run time, and a reference whose type is the extension type will actually refer directly to the underlying "wrapped" object. This fact also determines the behavior of `as` and `is`: Those -operations will refer to the run-time type of the representation object, -and the extension type is just an alias for the representation type at run -time. +operations will refer to the run-time type of the "wrapped" object, +and the extension type is just an alias for the declared type of the +"wrapped" object at run time. Consider a member access (e.g., a method call like `e.m(2)`) where the static type of the receiver (`e`) is an extension type `V`. @@ -121,7 +120,7 @@ In the body of the extension type the representation object is in scope, with the declared name and type, as if it had been a final instance variable in a class. Similarly, if the representation name is `id` then the representation object can be accessed using `this.id` in the body or `e.id` -anywhere, as long as the static type of `e` is that representation type. +anywhere, as long as the static type of `e` is that extension type. [primary constructor proposal]: https://github.com/dart-lang/language/pull/3023 @@ -139,12 +138,12 @@ representation type `R`, we can refer to an object `theRList` of type element in the list", but it only takes time _O(1)_ and no space, no matter how many elements the list contains. -It is also possible to declare a non-extension type as a superinterface in -an extension type declaration, if certain conditions are satisfied. This -can be viewed as a partial unveiling of the representation object, in the -sense that it enables some members of the representation type to be invoked -on the extension type, and it makes the extension type a subtype of that -non-extension type. +Finally, it is also possible to declare a non-extension type as a +superinterface in an extension type declaration, if certain conditions are +satisfied. This can be viewed as a partial unveiling of the representation +object, in the sense that it enables some members of the representation +type to be invoked on the extension type, and it makes the extension type a +subtype of that non-extension type. ## Motivation @@ -211,12 +210,12 @@ We can actually cast away the extension type and hence get access to the interface of the representation, but we assume that the developer wishes to maintain this extra discipline, and won't cast away the extension type unless there is a good reason to do so. Similarly, we can access the -representation using the representation name as a getter inside the body of -the extension type declaration. +representation using the representation name as a getter, inside or outside +the body of the extension type declaration. -The extra discipline is enforced because the extension type member -implementations will only treat the representation object in ways that -conform to this particular discipline (and thereby defines what this +The extra discipline is enforced in the sense that the extension type +member implementations will only treat the representation object in ways +that conform to this particular discipline (and thereby defines what this discipline is). For example, if the discipline includes the rule that you should never call a method `foo` on the representation, then the author of the extension type will simply need to make sure that none of the extension @@ -316,14 +315,14 @@ the extension type (in this case that means: the members declared in the body of `TinyJson`). So it is an error to call `add` on `tiny`, and that protects us from this kind of schema violations. -In general, the use of an extension type allows us to keep some unsafe -operations in a specific location (namely inside the extension type -declaration, or inside one of a set of collaborating extension type -declarations). We can then reason carefully about each operation once and -for all. Clients use the extension type to access objects conforming to the -given schema, and that gives them access to a set of known-safe operations, -making all other operations in the interface of the representation type a -compile-time error. +In general, the use of an extension type allows us to keep invocations of +certain unsafe operations in a specific location (namely inside the +extension type declaration, or inside one of a set of collaborating +extension type declarations). We can then reason carefully about each +operation once and for all. Clients use the extension type to access +objects conforming to the given schema, and that gives them access to a set +of known-safe operations, making all other operations in the interface of +the representation type a compile-time error. One possible perspective is that an extension type corresponds to an abstract data type: There is an underlying representation, but we wish to @@ -701,6 +700,18 @@ as the body of a type alias. It is also allowed to create a new instance where one or more extension types occur as type arguments (e.g., `List.empty()`).* +An extension type `V` may be used in an object pattern +*(e.g., `case V(): ...` where `V` is an extension type)*. +Exhaustiveness analysis will treat such patterns as if they had been an +object pattern matching the extension type erasure of `V`. + +*In other words, we make no attempt to hide the representation type during +the exhaustiveness analysis. The usage of such patterns is very similar to +a cast into the extension type in the sense that it provides a reference of +type `V` to an object without running any constructors of `V`. We will rely +on lints in order to inform developers about such situations, similarly to +the treatment of expressions like `e as V`.* + A compile-time error occurs if the type V\1, .. Tk> provides a wrong number of type arguments to `V` (when `k` is different @@ -793,9 +804,7 @@ in the case where they agree perfectly on the types. _DV_ must override the given name to eliminate the error. Note that it is not an error if both `V1` and `V2` have the member named `m` because they both get it from a common extension type superinterface, such that it is the same -declaration.* - - +declaration (which is also known as "diamond" inheritance).* If `e` is an expression whose static type `V` is the extension type Name\1, .. Ts> and `m` is the From 357a4a0d68c348b2752f3f49f96a8101ac2ebd40 Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Fri, 14 Jul 2023 17:18:21 +0200 Subject: [PATCH 25/29] Typo --- .../extension-types/feature-specification.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/accepted/future-releases/extension-types/feature-specification.md b/accepted/future-releases/extension-types/feature-specification.md index b0509bec8f..78be8845c8 100644 --- a/accepted/future-releases/extension-types/feature-specification.md +++ b/accepted/future-releases/extension-types/feature-specification.md @@ -700,10 +700,10 @@ as the body of a type alias. It is also allowed to create a new instance where one or more extension types occur as type arguments (e.g., `List.empty()`).* -An extension type `V` may be used in an object pattern -*(e.g., `case V(): ...` where `V` is an extension type)*. -Exhaustiveness analysis will treat such patterns as if they had been an -object pattern matching the extension type erasure of `V`. +An extension type `V` (which may include actual type arguments) can be used +in an object pattern *(e.g., `case V(): ...` where `V` is an extension +type)*. Exhaustiveness analysis will treat such patterns as if they had +been an object pattern matching the extension type erasure of `V`. *In other words, we make no attempt to hide the representation type during the exhaustiveness analysis. The usage of such patterns is very similar to From 924d136d837cec4e538cd35754f923d7889c6173 Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Fri, 14 Jul 2023 17:21:30 +0200 Subject: [PATCH 26/29] Typo --- .../extension-types/feature-specification.md | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/accepted/future-releases/extension-types/feature-specification.md b/accepted/future-releases/extension-types/feature-specification.md index 78be8845c8..c8cac3ecd7 100644 --- a/accepted/future-releases/extension-types/feature-specification.md +++ b/accepted/future-releases/extension-types/feature-specification.md @@ -688,8 +688,17 @@ extension type V(T id) ... { ``` It is then allowed to use +V\1, .. Tk> as a type. +A compile-time error occurs if the type V\1, .. Tk> -as a type. +provides a wrong number of type arguments to `V` (when `k` is different +from `s`), and if it is not regular-bounded. + +*In other words, such types cannot be super-bounded. The reason for this +restriction is that it is unsound to execute code in the body of `V` in +the case where the values of the type variables do not satisfy their +declared bounds, and those values will be obtained directly from the static +type of the receiver in each member invocation on `V`.* *Such types can occur as the declared type of a variable or parameter, as the return type of a function or getter, as a type argument in a type, @@ -703,7 +712,8 @@ where one or more extension types occur as type arguments (e.g., An extension type `V` (which may include actual type arguments) can be used in an object pattern *(e.g., `case V(): ...` where `V` is an extension type)*. Exhaustiveness analysis will treat such patterns as if they had -been an object pattern matching the extension type erasure of `V`. +been an object pattern matching the extension type erasure of `V` (defined +below). *In other words, we make no attempt to hide the representation type during the exhaustiveness analysis. The usage of such patterns is very similar to @@ -712,17 +722,6 @@ type `V` to an object without running any constructors of `V`. We will rely on lints in order to inform developers about such situations, similarly to the treatment of expressions like `e as V`.* -A compile-time error occurs if the type -V\1, .. Tk> -provides a wrong number of type arguments to `V` (when `k` is different -from `s`), and if it is not regular-bounded. - -*In other words, such types cannot be super-bounded. The reason for this -restriction is that it is unsound to execute code in the body of `V` in -the case where the values of the type variables do not satisfy their -declared bounds, and those values will be obtained directly from the static -type of the receiver in each member invocation on `V`.* - A compile-time error occurs if a type parameter of an extension type declaration occurs in a non-covariant position in the representation type. From 6496c9757bbd964cf34e4944cbe77d97dcdc9330 Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Fri, 14 Jul 2023 22:11:03 +0200 Subject: [PATCH 27/29] Many small improvements --- .../extension-types/feature-specification.md | 98 ++++++++++--------- 1 file changed, 52 insertions(+), 46 deletions(-) diff --git a/accepted/future-releases/extension-types/feature-specification.md b/accepted/future-releases/extension-types/feature-specification.md index c8cac3ecd7..647b514279 100644 --- a/accepted/future-releases/extension-types/feature-specification.md +++ b/accepted/future-releases/extension-types/feature-specification.md @@ -447,10 +447,10 @@ In the case where `m` is a method, the invocation could be an extension type member property extraction (*a getter invocation or a tear-off*) or a method invocation, and in the latter case there will also be an ``, optionally passing some actual type arguments, and -(non-optionally) passing an actual argument list. Finally, a property -extraction may be a property extraction followed by a term derived from -``, in which case it denotes a generic function -instantiation of the function object yielded by the property extraction. +(non-optionally) passing an actual argument list. Finally, it may be a +property extraction followed by a term derived from ``, in +which case it denotes a generic function instantiation of the function +object yielded by the property extraction. *These constructs are standard, but they have different semantics when `m` is an extension type member, so we need to state their static analysis and @@ -480,6 +480,12 @@ subsequent sections will specify all the elements ("an invocation of the extension type member `m` ...") based on specific syntax, and then rely an this section to specify the static analysis of the given situation. +*Note that some compile-time errors may seem relevant in this section, but +they are actually specified in the section about the static analysis of +extension types. This is because said errors are errors in an extension +type declaration, but this section is concerned with invocations of +extension type members.* + We need to introduce a concept that is similar to existing concepts for regular classes. @@ -509,6 +515,9 @@ all non-extension type members named `n` that _DV_ has, again using a repeated computation of the instantiated type of each superinterface on the path to the given non-extension type superinterface. +*In this section it is assumed that _DV_ does not have any compile-time +errors, which ensures that this combined member signature exists.* + Now we are ready to specify the invocation itself. Consider an invocation of the extension type member `m` on the receiver @@ -534,8 +543,8 @@ ensured by normal static analysis of subexpressions like `e`.* If the name of `m` is a name in the interface of `Object` (that is, `toString`, `==`, `hashCode`, `runtimeType`, or `noSuchMethod`), the static analysis of the invocation is treated as an ordinary instance member -invocation on a receiver of type `Object?` and with the same `args`, if -any. +invocation on a receiver of type `Object?` and with the same `args` or +`typeArgs`, if any. Otherwise, a compile-time error occurs if `V` does not have a member named `m`. @@ -603,8 +612,11 @@ member signature. *For instance, a dynamic type check according to this member signature may be applied to an actual argument whose static type is `dynamic`.* -Otherwise, `m` is an extension type member. Let _Dm_ be the declaration -named `m` that `V` has. +Otherwise, `m` is an extension type member. Let _Dm_ be the unique +declaration named `m` that `V` has. + +*The declaration is known to be unique because the program would otherwise +have a compile-time error.* Evaluation of this invocation proceeds by evaluating `e` to an object `o`. @@ -632,11 +644,11 @@ from `Object`, there are no special exceptions. Note that we can tear off the same method from the same extension type with the same representation object twice, and still get different behavior, because the extension type had different actual type arguments. Hence, we can not consider two -extension type method tear-offs equal just because they have the same -receiver. Optimizations whereby separately torn-off methods are represented -by the same object are allowed, as long as they behave as specified, so -there is no guarantee that two torn-off methods are unequal, unless they -must behave differently.* +extension type method tear-offs of the same method equal just because they +have the same receiver. Optimizations whereby separately torn-off methods +are represented by the same object are allowed, as long as they behave as +specified. So there is no guarantee that two torn-off methods are unequal, +unless they must behave differently.* The closurization is subject to generic function instantiation in the case where `args` is omitted and `typeArgs` provided, using the same semantics @@ -819,7 +831,7 @@ with the actual type arguments T1, ..., Ts and no actual argument part. Similarly, `e.m` is treated the same, but omits -`args`, includes ``. +`args`, and includes ``. *Setter invocations are treated as invocations of methods with a single argument. Similarly, operator invocations are treated as method invocations @@ -902,16 +914,17 @@ non-nullable then `V` is a proper subtype of `Object`, and `V` is non-nullable. Otherwise, `V` is a proper subtype of `Object?`, and `V` is potentially nullable. -*That is, an expression of an extension type can be assigned to a top -type (like all other expressions), and if the representation type is -non-nullable then it can also be assigned to `Object`. Non-extension -types (except bottom types) cannot be assigned to extension types without -a cast. Similarly, null cannot be assigned to an extension type without a -cast, even in the case where the representation type is nullable (even -better: don't use a cast, call a constructor instead). Another consequence of -the fact that the extension type is potentially non-nullable is that it -is an error to have an instance variable whose type is an extension type, -and then relying on implicit initialization to null.* +*That is, an expression of an extension type can be assigned to a top type +(like all other expressions), and if the representation type is +non-nullable then it can also be assigned to `Object`. Non-extension types +(except bottom types and `dynamic`) cannot be assigned to extension types +without an explicit cast. Similarly, null cannot be assigned to an +extension type without an explicit cast, even in the case where the +representation type is nullable (even better: don't use a cast, call a +constructor instead). Another consequence of the fact that the extension +type is potentially non-nullable is that it is an error to have an instance +variable whose type is an extension type, and then relying on implicit +initialization to null.* In the body of a member of an extension type declaration _DV_ named `Name` and declaring the type parameters @@ -949,7 +962,7 @@ declarations.* Let X1 extends B1, .. Xs extends Bs be a declaration of the type parameters of a generic entity (*it could -be a generic class, extension type, or mixin, or typedef, or function*). +be a generic class, extension type, mixin, typedef, or function*). Let BBj be the extension type erasure of Bj, for _j_ in _1 .. s_. It is a compile-time error if @@ -957,11 +970,12 @@ It is a compile-time error if has any compile-time errors. *For example, the extension type erasure could map -X extends C, Y extends X to +X extends C\, Y extends X to X extends Y, Y extends X, which is an error.* -#### Extension type constructors and their static analysis + +#### Extension Type Constructors and Their Static Analysis An extension type declaration _DV_ named `Name` may declare one or more constructors. A constructor which is declared in an extension type @@ -1020,7 +1034,7 @@ shows, any occurrence of the keywords `abstract`, `final`, `base`, is a syntax error.* -### Composing Extension Types +### Declaring Superinterfaces of an Extension Type This section describes the effect of including a clause derived from `` in an extension type declaration. We use the phrase @@ -1118,19 +1132,6 @@ for all T1, .. Ts. of `V`. This is true for extension type superinterfaces as well as non-extension type superinterfaces.* -A compile-time error occurs if an extension type declaration _DV_ has two -extension type superinterfaces `V1` and `V2`, where both `V1` and `V2` have -an extension type member named _m_, and the two declarations of _m_ are -distinct declarations, and _DV_ does not declare a member named _m_. - -*In other words, if two different declarations of _m_ are inherited from -two extension type superinterfaces then the subinterface must resolve the -conflict. The so-called diamond inheritance pattern can create the case -where two superinterfaces have an _m_, but they are both declared by the -same declaration (so `V` is a subinterface of `V1` and `V2`, and both `V1` -and `V2` are subinterfaces of `V3`, and only `V3` declares _m_, in which -case there is no conflict in `V`).* - *Assume that _DV_ is an extension type declaration named `Name`, and the type `V1`, declared by _DV1_, is a superinterface of _DV_ (`V1` could be an extension type or a non-extension type). Let `m` be the name of a @@ -1172,11 +1173,10 @@ type would then match `T2`, but not `T1`). In this case there is also no override relationship between `T1.foo` and `T2.foo`, they are just independent member signatures.* -*The effect of having an extension type declaration _DV_ with -superinterfaces `V1, .. Vk` is that the members declared by _DV_ as -well as all members of `V1, .. Vk` that are not redeclared by a -declaration in _DV_ can be invoked on a receiver of the type -introduced by _DV_.* +*In summary, the effect of having an extension type declaration _DV_ with +superinterfaces `V1, .. Vk` is that the members declared by _DV_ as well as +all members of `V1, .. Vk` that are not redeclared by a declaration in _DV_ +can be invoked on a receiver of the type introduced by _DV_.* ## Dynamic Semantics of Extension Types @@ -1231,6 +1231,12 @@ A type test, `o is U` or `o is! U`, and a type cast, `o as U`, where test and type cast on the run-time representation of the extension type as described above. +*These type casts and type tests where the target type is an extension type +may be considered to "bypass" the constructors of the extension type. This +proposal makes no attempt to prevent this. However, support for detecting +and thus avoiding that this occurs may be provided via lints or similar +mechanisms.* + An extension type `V` used as an expression (*a type literal*) evaluates to the value of the extension type erasure of the representation type used as an expression *(also known as the ultimate representation type)*. From 0b38d8b05e5aba682e753c85d98b4778f10557c0 Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Fri, 14 Jul 2023 22:12:30 +0200 Subject: [PATCH 28/29] Typo --- .../future-releases/extension-types/feature-specification.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/accepted/future-releases/extension-types/feature-specification.md b/accepted/future-releases/extension-types/feature-specification.md index 647b514279..c5495dfc9d 100644 --- a/accepted/future-releases/extension-types/feature-specification.md +++ b/accepted/future-releases/extension-types/feature-specification.md @@ -1268,7 +1268,7 @@ This section mentions a few topics that have given rise to discussions. -### Is the primary constructor constant? +### Is the Primary Constructor Constant? This proposal specifies that the primary constructor is always constant. The point is that this is possible, because a primary constructor @@ -1318,7 +1318,7 @@ extension type V(S it) { ``` -### Allow implementing a final extension type? +### Allow Implementing a `final` Extension Type? Note that this section is about the use of a `final` modifier on an extension type declaration (similar to a class modifier), but that From b3f18d66208b8e8f62c3397a81dad57629e0f024 Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Wed, 19 Jul 2023 19:08:34 +0200 Subject: [PATCH 29/29] Adjust the grammar and primary constructor rule to say that it is constant if there is an explicit `const`, otherwise not --- .../extension-types/feature-specification.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/accepted/future-releases/extension-types/feature-specification.md b/accepted/future-releases/extension-types/feature-specification.md index c5495dfc9d..4f3c08bfb2 100644 --- a/accepted/future-releases/extension-types/feature-specification.md +++ b/accepted/future-releases/extension-types/feature-specification.md @@ -402,7 +402,7 @@ with some rules for elements used in extension type declarations: ```ebnf ::= - 'extension' 'type' ? + 'extension' 'type' 'const'? ? ? '{' ( )* @@ -993,10 +993,10 @@ The `` works as a constructor. The optional `('.' )` in the grammar is used to declare this constructor with a name of the form ` '.' ` *(at times described as a "named constructor")*, or ` '.' 'new'`. It -is a constant constructor: If `e` is a constant expression and `V(e)` is -not an error, then `V(e)` is a constant expression. Other constructors may -be declared `const` or not, following the normal rules for constant -constructors. +is a constant constructor if and only if the reserved word `const` occurs +just after `extension type` in the header of the declaration. Other +constructors may be declared `const` or not, following the normal rules for +constant constructors. A compile-time error occurs if an extension type constructor includes a superinitializer. *That is, a term of the form `super(...)` or @@ -1200,7 +1200,7 @@ constructor execution is the value of `this`. The dynamic semantics of an instance creation that references the `` follows the semantics of primary -constructors: Consider the representation declaration as a constant primary +constructors: Consider the representation declaration as a primary constructor, then consider the corresponding non-primary constructor _k_. The execution of the representation declaration as a constructor has the same semantics as an execution of _k_. @@ -1288,6 +1288,10 @@ In any case, every non-primary constructor in an extension type would potentially have properties that would make `const` an error, so they must specify `const` explicitly as usual. +Update: As of July 19 2023, the reserved word `const` must be specified +explicitly, which will make the primary constructor a constant +constructor. + ### Support "private inheritance"?