Skip to content

Constructor tear-off of forwarding factory constructors is under-defined. #3427

Open
@lrhn

Description

@lrhn

The constructor tear-off specifiction defines the behavior of a tear-off as "equivalent to" tearing off a corresponding static function, which has the same type parameters as the class, the same parameter list as the constructor, and a return type which is the class-type instantiated with those type arguments.

That definition doesn't work for all redirecting factory constructors with optional parameters, because those cannot have default values, which makes the corresponding static function also not have default values, even if the optional parameter's type is non-nullable. That is, the "corresponding static function" is not a valid Dart function.

(And this is, again, why we shouldn't define things in terms of desugaring.)

The current approach cannot be saved by saying that we should use the default value of the (transitive) redirect target's parameter, which must also be optional and theregore surely have one. That parameter can have a wider type, and a default value which isn't valid for the redirecting factory's parameter.

Example:

class C { 
  factory C([int x]) = D; 
}
class D implements C {
  C([int? x]) { assert(x == null); }
}
void main() {
  C Function([int]) f = C.new;
  assert(f.runtimeType == typeof<C Function([int])>);
  f();
}
typedef typeof<T> = T;

There is no valid desugaring which can introduce a Dart function for C.new which behaves like calling C.new should
(that is, calling that function works just like calling C.new with the same arguments would).

I suggest we rewrite the places in the specification where we introduce a wrapper which copies default values, into forwarding argument lists.

Constructor tear-off:

The result of tearing off a constructor D.C of a class declaration D with type parameters TP and parameter list P
is a function value with signature D<TP> Function(P).
When that function is invoked with type arguments TS and argument list A, it returns the result of invoking the constructor D.C on the instantiated type D<TS> with argument list A.

The result of tearing off the same constructor from an instantiated type, tearing off D<TS>.C, is a function with
signature D<TS> Function(P') where P' is P with type arguments TS substituted for type parameters TP.
Invoking that function with argument list A returns the result of invoking D<TS>.C with argument list A.

(or something to that effect.)

Basically accept that not all function signatures have default values, even where user-declared functions must have them.

The same principle should be applied to generic function instantiation, implicit or explicit. It introduces a new function with a signature which has no type arguments and where the parameter and return types replaces the type parameters with the instantiating type arguments. Calling that function invokes the original function with those type arguments and the same argument list. Again, without any attempt to copy default values, because there are functions which do not have default values - even if it's only redirecting factory constructor tear-offs, for now.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugThere is a mistake in the language specification or in an active document

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions