-
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
Analyzer performs incorrect type substitution when resolving call to generic method in a generic extension #52117
Comments
I am more surprised about the error at I'm not sure why inference isn't setting |
I believe that too. I wasn't sure how to reference that error for me to name this issue, but at least I figured it was a problem with the lint. |
Yeah, type inference is weird in this invocation. solo_test_X() async {
await assertErrorsInCode(r'''
void f<T extends Object?>(T object) {
if (object is int?) {
object?.apply(g);
}
}
extension E<T2 extends Object> on T2 {
void apply<R>(R Function(T2 self) transform) {}
}
double g(int a) => 0.0;
''', [
error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 80, 1),
]);
final node = findNode.singleMethodInvocation;
assertResolvedNodeText(node, r'''
MethodInvocation
target: SimpleIdentifier
token: object
staticElement: self::@function::f::@parameter::object
staticType: T & int?
operator: ?.
methodName: SimpleIdentifier
token: apply
staticElement: MethodMember
base: self::@extension::E::@method::apply
substitution: {T2: T, R: R}
staticType: void Function<R>(R Function(T))
argumentList: ArgumentList
leftParenthesis: (
arguments
SimpleIdentifier
token: g
parameter: ParameterMember
base: root::@parameter::transform
substitution: {R: dynamic}
staticElement: self::@function::g
staticType: double Function(int)
rightParenthesis: )
staticInvokeType: void Function(dynamic Function(T))
staticType: void
typeArgumentTypes
dynamic
''');
} |
Looks like the same bug as: #35799 |
Please give a look at this comment on dart-lang/sdk#52077 |
Moving this one to the SDK tracker since |
Has this been fixed? Asking because of #53828 (comment) |
It's possible. I'm not sure. I haven't specifically reproduced this one. |
To help a little, I've tested it myself in the latest master, and the |
Thanks for retesting in the latest master, @FMorschel! I'm glad that the issue isn't triggering for you anymore. I'm sorry to say that sometimes this sort of thing happens; we don't get around to fully investigating an issue before something else changes and the issue stops occurring. When this happens, it can be difficult to dig through the project history to figure out whether the bug was fixed on purpose (say, because someone else ran into it in a different form, and their issue got addressed), or whether it just kind of "went into hiding" because something else changed. Sometimes I go ahead and dig anyhow because I'm curious, or because I'm worried that maybe there's a deeper problem that still hasn't been solved. But we're all busy, so more often than not what happens at this point is that we close the issue and move on to other things. In this particular case, I think it would be worth doing a bit more investigation, in particular into @scheglov's comment saying that we don't infer If I'm understanding that comment correctly, @scheglov is referring to this line from his repro of the issue:
Which suggests that when applying the extension void f<T extends Object?>(T object) {
if (object is int?) {
object?.apply(g);
}
} That seems like a problem that could potentially underlie a lot of bugs. And just because this @scheglov, do you think you could repeat your experiment and see if |
The test in #52117 (comment) still produces the same resolution, with |
If you then think renaming this issue to make sure we are clear about solving this substitution problem would be better (since the |
Sorry for the ping, @stereotype441, but I just wanted to know if you ever saw my last comment about renaming this. Or we could even open a new issue to fix that substitution problem and differ from this. I've not tested it again so that might be a good thing to do just to make sure it is still happening. |
unnecessary_cast
false positive
Good point, thanks! I've re-titled the issue. |
I think the core of this issue is when and how an intersection type is erased to a reifiable type. As noted in #56028, actual type arguments for an extension in an extension member invocation (that is, for the extension itself, not for the member) are computed in the same way as the actual type arguments of a constructor invocation with a corresponding class. So we should get the same outcome (in particular, the same error) in the original example here as we do with the following example: import 'package:intl/intl.dart';
class Apply<T extends Object> {
final T t;
Apply(this.t);
R apply<R>(R Function(T self) transform) => transform(t);
}
void func<T extends Object?>(T object) {
if (object is DateTime?) {
final formatter = DateFormat('yyyy/MM/dd');
if (object != null) {
// Type of `object` is `T & DateTime`, same as receiver type with `object?.apply(...)`.
Apply(object).apply(formatter.format); // Error.
}
}
} This example yields the following error message at
which is exactly the same error message that we get with the extension member invocation in the original example. However, I would in both cases have expected the error message to acknowledge that the type inference step does not produce a binding for the given type variable that satisfies the constraints. Just like this example: void f<X extends num>(X x) {}
void main() => f('Hello'); // Error: "Couldn't infer type parameter 'X'". Considering that the developer did not choose the given value for the type variable For the It is surprising that the parameter type in the This seems to imply that the So, no matter what's the ultimate resolution of this issue, I think it should not be possible for any static analysis steps to consider the receiver type of (Of course, the intersection type could also be erased to |
tl;dr It should say "isn't defined for the type" rd;lt Coming back to this issue, I can see that it may not be obvious how to characterize the observed behavior, or what to do next. So here's a shorter version of what I tried to say in the previous comment. ;-) Here is the core of the example program again, simplified a bit and modified to invoke the extension method with extension Apply<T extends Object> on T {
R apply<R>(R Function(T self) transform) => transform(this);
}
Object? f(DateTime _) => null;
void func<T extends Object?>(T object) {
if (object is DateTime?) {
object?.apply(f); // ... type can't be assigned ...
if (object != null) {
object.apply(f); // ... type can't be assigned ...
}
}
} The observed behavior of the analyzer is understandable in the sense that However, we should never have reached that point because I think the fix could be to erase the receiver type from Fun fact: If we force that outcome by using a different member name then we get the following:
Shouldn't that be a specific type rather than '<unknown>'? |
Describe the issue
I have a function with a generic type
T extends Object?
. Somewhere in the middle of my function, I have a test to see if my variableT object
isDateTime?
. I'm not sure how to describe this better than showing you the code (it can be tested with dartpad.dev):To Reproduce
*Here is where I found it
Expected behavior
Either not triggering
unnecessary_cast
or not triggering the error with the line above as all the other code before works just fine.The text was updated successfully, but these errors were encountered: