Closed
Description
When trying to define a self type like this class Foo<Self extends Foo<Self>>
it is unclear if the methods of the class that return instances of itself should use Self
or Foo<Self>
as the return type.
- Using
Self
as the return type might cause issues when you need to use the typeFoo
. This will happen when you know only at runtime which subclass ofFoo
is used.
abstract class Foo<Self extends Foo<Self>> {
final bool property;
const Foo(this.property);
Self copyWith(bool property);
Self toggle() {
return copyWith(!property);
}
}
class Bar extends Foo<Bar> {
const Bar(super.property);
@override
Bar copyWith(bool property) {
return Bar(property);
}
}
void main() {
// Let's pretend that there are multiple subclass of `Foo`
// and that you know only at runtime which one you will get
// so that you need to use the type `Foo`
Foo someFoo = const Bar(true); // type: Foo<Foo<dynamic>>
someFoo = someFoo.toggle(); // type: Foo<dynamic>
}
The static analysis fails because when applying the toggle
method the type becomes Foo<dynamic>
instead of Foo<Foo<dynamic>>
.
- Using
Foo<Self>
as the return type doesn't mix well with using the name of the subclass as the type. In this example, it would preferable to be able to use the typeBar
for the variablesomeFoo
but this is not possible as thetoggle
method has a return typeFoo<Bar>
.
abstract class Foo<Self extends Foo<Self>> {
final bool property;
const Foo(this.property);
Foo<Self> copyWith(bool property);
Foo<Self> toggle() {
return copyWith(!property);
}
}
class Bar extends Foo<Bar> {
const Bar(super.property);
@override
Bar copyWith(bool property) {
return Bar(property);
}
}
void main() {
Bar someFoo = const Bar(true); // type: Bar
someFoo = someFoo.toggle(); // type: Foo<Bar>
}
The static analysis fails because someFoo.toggle()
uses the type Foo<Bar>
and the variable someFoo
is defined with type Bar
.
Related to dart-lang/language#3025
Metadata
Metadata
Assignees
Type
Projects
Milestone
Relationships
Development
No branches or pull requests
Activity
dart-github-bot commentedon Feb 10, 2025
Summary: Self-referential type parameters in Dart cause type inference issues. Using
Self
orFoo<Self>
as return types leads to static analysis failures when dealing with runtime subclass selection.eernstg commentedon Feb 10, 2025
This is all working as specified and intended.
Here is the other example, adjusted similarly:
In this case (and in general, actually) you should change the return type of the methods from
Foo<Self>
toSelf
. Note thatSelf
is a subtype ofFoo<Self>
(which is guaranteed because we know that, by soundness, the type variable bounds are enforced at run time for every object). In shortSelf
is a better Self type thanFoo<Self>
.This means that it's just a needless waste of typing precision to specify the return type
Foo<Self>
when you actually intend (and implement) those methods such that they return aSelf
.Finally, note that you're using an F-bounded type variable which is not a complete and faithful replacement for a real Self type. (In particular, the pseudo-variable
this
is not known in the body ofFoo
to have typeSelf
, and it isn't even known to have typeFoo<Self>
.) So you shouldn't expect everything that applies to true Self types to be applicable to an F-bounded type variable as well.julien4215 commentedon Feb 10, 2025
The advantage I found in using
Foo<Self>
instead ofSelf
for the return type is that when you have an object of typeFoo<Foo<dynamic>>
and that you apply the methodtoggle
you still get the typeFoo<Foo<dynamic>>
. WithSelf
as the return type of toggle, you would get the typeFoo<dynamic>
.I don't understand why it would be a needless waste of typing precision if it solves this problem.