-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Analyser finds extension, but it wont compile #52077
Comments
It looks like a bug in the common front end (or perhaps the backend, but that seems less likely). Here is a simplified version of the example: mixin Filterable {}
class Filter<T extends Filterable?> {
void test(T object) {
if (object is! Filterable) return; // Should promote `object` to `T & Filterable`.
object.when(); // Accepted by analyzer. Error during compilation.
}
}
extension _When<T extends Filterable> on T {
T? when() => null;
}
void main() {} This program causes the following compile-time error:
I'll transfer this issue to the SDK repository because it seems to be a CFE bug. |
@johnniwinther, do you agree that the extension |
Originally, I had this extension with |
Interesting! But the example I gave still causes a compile-time error with |
Maybe it has something to do with dart-lang/sdk#52117? |
It looks like the problem could be that while So, to invoke the extension, the compiler would have to choose either Or maybe it does just look at |
This explanation, at least for me, really seems to fit the dart-lang/sdk#52117 error. |
I think the analyzer has issues too: void main() {
foo<List<int>?>([1]);
}
foo<T extends List<int>?>(T list) {
if (list is List<int>) {
// Promoted type T&List<int>
List<int> l1 = list;
T l2 = list;
list.st<Exactly<T>>(); // When used to solve <T>, it become
// list.st<Exactly<List<int>>>();
list.foo;
list.bar;
list.baz;
list.baz.st<Exactly<T>>(); // Unsound
}
}
extension <S> on S {
void st<X extends Exactly<S>>(){}
}
extension on List<int> {
List<int> get foo {
print("foo: List<int>");
return this;
}
}
extension <S> on S {
S get bar {
print("bar: $S");
return this;
}
}
extension <S extends List<int>> on S {
S get baz {
print("baz: $S");
return this;
}
}
typedef Exactly<T> = T Function(T); (https://dartpad.dev/?id=f2bd890540648e55221aa5d6dbf8e469) The lines: list.st<Exactly<T>>();
// list.st<Exactly<List<int>>>(); show that the actual type argument passed to the extension is However, the front-end says that The analyzer gives no error, and the line list.baz.st<Exactly<T>>(); // Unsound shows (probably, always hard to be absolutely sure) that the type argument actually passed to That is an invalid type argument, since So, analyzer is doing something very wrong here, so it's not obvious that it's the front-end which is being wrong. |
Dart type inference is partially specified in inference.md, but it does not cover the situation where an intersection type is the static type of the receiver of an invocation which might call an extension method. I've create #56028 in order to reach a conclusion about which rules we want. This issue may then be used to handle the implementation of the given approach, possibly with two subissues @lrhn, I'd recommend that you create new issues if this comment shows unexpected behaviors in the analyzer that aren't covered by the intersection-type/extension-method topic which was the starting point for this issue. |
I gave a look at inference.md, and I have another question, that I couldn't understand reading that file. Why does when I do a code like the following, it gives me extension _When<R extends Object, T extends R> on T {
R? when(R Function(T) fn) => null;
}
void main() {
final number = 1; // int
final result = number.when((_) => 1.2); //Object?
} If this type of generics doesn't work, shouldn't it be warned with a lint? |
It is rarely useful for an extension to declare a type parameter whose value is unconstrained by the The reason for this is that all actual type arguments for the extension are inferred (unless provided explicitly) based on the static type of the receiver, with no input from any other source. In particular, the context type is However, the example doesn't provide any justification for the relationship extension _When<T> on T {
R? when<R extends Object>(R Function(T) fn) => null;
} If you do need the relation extension _When<T> on T {
R? when<R extends Object super T>(R Function(T) fn) => null;
} We could also consider an approach where you handle the same computation using a regular function. This means that the receiver will now be the first argument, and remaining arguments are pushed back to position 2, 3, etc. The point is that this makes the type arguments dealing with the "receiver" and the type arguments dealing with the arguments available at the same time, hence allowing us to specify a larger set of relationships: R? when<R extends Object, T extends R>(T self, R Function(T) fn) => null;
void main() {
final number = 1; // int
final result2 = when(number, (_) => 1.2); // Error, inference failed. `<num>` would work.
} There is a special difficulty here, because type inference would need to process the actual arguments of the invocation as a group in order to know that it needs to use The current approach causes the arguments to be split into phases (because the first argument has type However, in this particular case it isn't sufficient, because there is a need to flow information in the other direction: returning See dart-lang/language#3013 where this difficulty is reported. |
Thank you for your explanation. Just so you understand better why I was asking this. I have as you've seen, this extension: extension When<T extends Object> on T {
T? when(bool Function(T self) predicate, {T Function(T self)? otherwise}) {
if (predicate(this)) return this;
return otherwise?.call(this);
}
} This is used by me mostly inside Flutter projects since everything is mostly a one-line code, I sometimes find myself having to test for something (even more useful when is some nested value) so I actually use this extension for those cases. But, some days ago I tried to use this extension to set a Widget on my screen, since it has only one type parameter it was telling me the other widget wasn't of the same type extension WhenWidget<T extends Widget> on T {
T? when(bool Function(T self) predicate, {T Function(T self)? otherwise}) {
if (predicate(this)) return this;
return otherwise?.call(this);
}
} This solves my case because is more specific so it infers that it should be used. Only asked because if I end up having to do more of these extensions, I will have a lot of work having to create one more for every more specific type. |
As commented in #56028, this is an analyzer bug, as things are currently specified (at least based on the extension method feature specification, haven't checked the language spec integration of that). The type inference applied to (The error message is confusing, it should say, |
Hi again! I'm not entirely sure this is the same thing, but the last line with content here: formatter = widget.formatter ?? _formatter; //A value of type 'TextInputFormatter' can't be assigned to a variable of type 'NumberFormatter<T>'. it is showing this error. Here is the complete code for it. class CustomDoubleField<T extends double?> extends StatefulWidget {
const CustomDoubleField({
this.formatter,
super.key,
});
final NumberFormatter<T>? formatter;
@override
State<CustomDoubleField<T>> createState() => _CustomDoubleFieldState<T>();
}
class _CustomDoubleFieldState<T extends double?> extends State<CustomDoubleField<T>> {
static final _formatter = NumberFormatterDecimal<double>(
maxDecimalDigits: 3,
minDecimalDigits: 0,
decimalSeparator: ',',
groupSeparator: '.',
);
late NumberFormatter<T> formatted;
@override
void initState() {
super.initState();
formatter = widget.formatter ?? _formatter; //A value of type 'TextInputFormatter' can't be assigned to a variable of type 'NumberFormatter<T>'.
}
//...
} Even though class NumberFormatterDecimal<T extends num?>
with EquatableMixin
implements NumberFormatter<T> {
//...
}
abstract class NumberFormatter<T extends num?> implements TextInputFormatter {
T parse(String txt);
String format(T numero);
} |
It's not the same. This code is just not type-sound. (I assume I can derive why you get the type that you do, but the bigger underlying problem is that The problem is the (And the reason you get The implemented superinterfaces of those two are:
The least interface those two have in common is It's then not assignable to the context type Even with improved context-type guidance, which I don't know if we have added yet or just talked about, where we'd just give |
I see. Thank you a lot for the explanation! |
Closing this in favor of #53711. This discussion took many tangents and was hard for me to follow to see what our AI is. I think I've summarized and quoted the important parts over there. |
I have a file which looks like the following (you can test this error in dartpad.dev):
It gives the following output:
I'm not sure this is intended behaviour, but for me, it doesn't seem like that mostly because (as said in the title) Analyser tells me nothing is wrong.
The text was updated successfully, but these errors were encountered: