-
Notifications
You must be signed in to change notification settings - Fork 164
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
Code generation error on nested generic bounded type arguments #658
Comments
Nice catch! Thanks for reporting it. But it looks like an analyzer issue to me. Seems like self-referencing type variables have broken equality, note that in both examples it says @srawlins @scheglov Sam, Konstantin It seems equality is not working as expected for type variables that reference themselves in their bounds, like |
I don't understand your question to me. solo_test_X() async {
await assertNoErrorsInCode(r'''
class Foo<T> {}
abstract class Bar<T> {
Iterable<X> m1<X extends Foo<X>>(X Function(T) f);
}
''');
final X = findElement.typeParameter('X');
final X1 = X.instantiate(nullabilitySuffix: NullabilitySuffix.none);
final X2 = X.instantiate(nullabilitySuffix: NullabilitySuffix.none);
print(X1 == X2);
} prints |
@scheglov My question was "is equality supposed to work for self-referencing type variables, or is it a known limitation?". I guess your answer implies that it is supposed to work. For the exact failing test case, I didn't try it myself, but judging from the error reported in this bug, I think you have to inherit |
My understanding is that type equality is not defined by the language specification. @eernstg I can only see I would say that two I don't know what to advise here without getting into details how you use these types. |
I'm pushing for a definition of type equality which is based on this section. The section is specifying equality for The equality test would then be (ignoring In any case, type parameters would be subject to this structural comparison, so class Foo<X> {}
void Function<X extends Foo<X>>() f = <X extends Foo<X>>() {};
void Function<Y extends Foo<Y>>() g = f; // No error.
typedef F = Function<X extends Foo<X>>();
typedef G = Function<Y extends Foo<Y>>();
void main() {
print(F == G); // 'true'.
} The case where two distinct type parameters have the same name must of course consider them different because the can have different values (except class C<X extends Foo<X>> { // F-bound doesn't matter, but let's just keep it.
late X x1;
void foo<X extends Foo<X>>(X x2) => x1 = x2; // Error.
} The error is "A value of type 'X' can't be assigned to a variable of type 'X'" which sounds confusing, but is correct. |
@eernstg Thanks for the detailed explanation! That matches my intuitive expectations of how it should work. @scheglov Uh, I should have debugged it a bit myself before asking you. I think in general it works as I expected it to work and as Erik describes it. I was wrong to identify the problem to be equality not working for bounded type variables. It turned out, the problem is sometimes in "Sometimes" == if I get an inherited method from a generic class with |
I'm not aware about any error with the correctness of analysis that happens because of this. In the most cases we use subtype operation, and while doing this for I updated the failing test to print instead of checking. solo_test_getMember_fromGenericSuper_method_parameter_bound_substituion() async {
await resolveTestCode('''
class Foo<T> {}
abstract class A<XA> {
TA foo<TA extends Foo<TA>>();
}
abstract class B<XB> extends A<XB> {}
''');
final B = findElement.classOrMixin('B');
var foo0 = manager.getMember2(B, Name(null, 'foo'));
final foo = ExecutableMember.from2(foo0!, Substitution.empty);
final T = foo.typeParameters.single;
final otherT = (T.bound as InterfaceType).typeArguments.single;
// expect(otherT.element, same(T));
print(identical(otherT.element, same(T)));
print(typeSystem.runtimeTypesEqual(foo0.type, foo.type));
} As you can see, |
So, are you claiming this is not a bug? I'm not totally buying this. For the following reasons:
|
Yes, when we do substitution, we need to use the type parameters that are used in the type in which we do the substitution, not from any other type that might have type parameters with the same names. This probably works, otherwise we would have much bigger issues. Yes, when we run |
…tor. See dart-lang/mockito#658 (comment) Change-Id: I5b9e4b1d82ec935bcd2097ec76cf5a8e28c5e29e Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/312205 Commit-Queue: Konstantin Shcheglov <[email protected]> Reviewed-by: Samuel Rawlins <[email protected]>
Hm... everything that you say does make sense to me, but I fail to see how it matches to the example in question. Sure, with the analyzer's representation for type variables, one has to be careful while doing substitution and use the matching type parameters. But I'm using an empty substitution, that should hopefully satisfy the condition. Then, of course, if one starts with two equal Like, if we have two function types |
Ok, I think I figured this out. Turns out This also paves us a path to fix the Mockito codegen: we just have to use So, there is no user visible bug, but a number of suspicious behaviors:
@scheglov Do you want me to file a SDK bug for this, or do you think this is all fine? |
Turns out `FunctionTypedElement.typeParameters` could be inconsistent for `MethodMember`s returned by `InheritanceManager3.getMember2`. `FunctionTypedElement.type.typeFormals` seem to be always good, but we have to also use `type.parameters` and `type.returnType` instead of just `parameters` and `returnType` in this case. Fixes #658 PiperOrigin-RevId: 545613608
Had to revert the fix, as it broke some existing tests, looking. |
and I had to roll it back, since it caused breakages in two ways: 1. Sometimes `ParameterElement` from `type.parameters` had `enclosingElement` set to `null` and we use that in one helper function. That was easy to fix, we could just pass `methodElement` to that function directly. It's probably correct that `ParameterElement` of a `FunctionType` doesn't link back to a `MethodElement`, but it's weird that sometimes it does, so it wasn't caught in the tests. I had to get rid of using `type.parameters` anyway because of the second problem. 2. `type.parameters` don't contain parameters' default values (totally correct, since default values belong to methods, not to types), but that means we can't use them, since we do need default values. So I ended up with a more hacky solution, that uses `type.typeFormals` just to get correct references for method's type parameters, and then uses `typeParameters`, `returnType` and `parameters` for the rest as before. Original commit description: Turns out `FunctionTypedElement.typeParameters` could be inconsistent for `MethodMember`s returned by `InheritanceManager3.getMember2`. `FunctionTypedElement.type.typeFormals` seem to be always good, but we have to also use `type.parameters` and `type.returnType` instead of just `parameters` and `returnType` in this case. Fixes #658 PiperOrigin-RevId: 545934516
First attempt was #671 and I had to roll it back, since it caused breakages in two ways: 1. Sometimes `ParameterElement` from `type.parameters` had `enclosingElement` set to `null` and we use that in one helper function. That was easy to fix, we could just pass `methodElement` to that function directly. It's probably correct that `ParameterElement` of a `FunctionType` doesn't link back to a `MethodElement`, but it's weird that sometimes it does, so it wasn't caught in the tests. I had to get rid of using `type.parameters` anyway because of the second problem. 2. `type.parameters` don't contain parameters' default values (totally correct, since default values belong to methods, not to types), but that means we can't use them, since we do need default values. So I ended up with a more hacky solution, that uses `type.typeFormals` just to get correct references for method's type parameters, and then uses `typeParameters`, `returnType` and `parameters` for the rest as before. Original commit description: Use `FunctionTypedElement.type` while generating method overrides Turns out `FunctionTypedElement.typeParameters` could be inconsistent for `MethodMember`s returned by `InheritanceManager3.getMember2`. `FunctionTypedElement.type.typeFormals` seem to be always good, but we have to also use `type.parameters` and `type.returnType` instead of just `parameters` and `returnType` in this case. Fixes #658 PiperOrigin-RevId: 545934516
First attempt was #671 and I had to roll it back, since it caused breakages in two ways: 1. Sometimes `ParameterElement` from `type.parameters` had `enclosingElement` set to `null` and we use that in one helper function. That was easy to fix, we could just pass `methodElement` to that function directly. It's probably correct that `ParameterElement` of a `FunctionType` doesn't link back to a `MethodElement`, but it's weird that sometimes it does, so it wasn't caught in the tests. I had to get rid of using `type.parameters` anyway because of the second problem. 2. `type.parameters` don't contain parameters' default values (totally correct, since default values belong to methods, not to types), but that means we can't use them, since we do need default values. So I ended up with a more hacky solution, that uses `type.typeFormals` just to get correct references for method's type parameters, and then uses `typeParameters`, `returnType` and `parameters` for the rest as before. Original commit description: Use `FunctionTypedElement.type` while generating method overrides Turns out `FunctionTypedElement.typeParameters` could be inconsistent for `MethodMember`s returned by `InheritanceManager3.getMember2`. `FunctionTypedElement.type.typeFormals` seem to be always good, but we have to also use `type.parameters` and `type.returnType` instead of just `parameters` and `returnType` in this case. Fixes #658 PiperOrigin-RevId: 545934516
First attempt was #671 and I had to roll it back, since it caused breakages in two ways: 1. Sometimes `ParameterElement` from `type.parameters` had `enclosingElement` set to `null` and we use that in one helper function. That was easy to fix, we could just pass `methodElement` to that function directly. It's probably correct that `ParameterElement` of a `FunctionType` doesn't link back to a `MethodElement`, but it's weird that sometimes it does, so it wasn't caught in the tests. I had to get rid of using `type.parameters` anyway because of the second problem. 2. `type.parameters` don't contain parameters' default values (totally correct, since default values belong to methods, not to types), but that means we can't use them, since we do need default values. So I ended up with a more hacky solution, that uses `type.typeFormals` just to get correct references for method's type parameters, and then uses `typeParameters`, `returnType` and `parameters` for the rest as before. Original commit description: Use `FunctionTypedElement.type` while generating method overrides Turns out `FunctionTypedElement.typeParameters` could be inconsistent for `MethodMember`s returned by `InheritanceManager3.getMember2`. `FunctionTypedElement.type.typeFormals` seem to be always good, but we have to also use `type.parameters` and `type.returnType` instead of just `parameters` and `returnType` in this case. Fixes #658 PiperOrigin-RevId: 545934516
I believe https://dart-review.googlesource.com/c/sdk/+/312401 would allow us to revert the hack on Mockito side. |
Turns out `FunctionTypedElement.typeParameters` could be inconsistent for `MethodMember`s returned by `InheritanceManager3.getMember2`. `FunctionTypedElement.type.typeFormals` seem to be always good, but we have to also use `type.parameters` and `type.returnType` instead of just `parameters` and `returnType` in this case. Fixes dart-lang/mockito#658 PiperOrigin-RevId: 545681214
First attempt was dart-lang/mockito#671 and I had to roll it back, since it caused breakages in two ways: 1. Sometimes `ParameterElement` from `type.parameters` had `enclosingElement` set to `null` and we use that in one helper function. That was easy to fix, we could just pass `methodElement` to that function directly. It's probably correct that `ParameterElement` of a `FunctionType` doesn't link back to a `MethodElement`, but it's weird that sometimes it does, so it wasn't caught in the tests. I had to get rid of using `type.parameters` anyway because of the second problem. 2. `type.parameters` don't contain parameters' default values (totally correct, since default values belong to methods, not to types), but that means we can't use them, since we do need default values. So I ended up with a more hacky solution, that uses `type.typeFormals` just to get correct references for method's type parameters, and then uses `typeParameters`, `returnType` and `parameters` for the rest as before. Original commit description: Use `FunctionTypedElement.type` while generating method overrides Turns out `FunctionTypedElement.typeParameters` could be inconsistent for `MethodMember`s returned by `InheritanceManager3.getMember2`. `FunctionTypedElement.type.typeFormals` seem to be always good, but we have to also use `type.parameters` and `type.returnType` instead of just `parameters` and `returnType` in this case. Fixes dart-lang/mockito#658 PiperOrigin-RevId: 547427000
Code generation throws an error when a nested generic bounded type argument is used. The following code reproduces the issue:
example:
or simpler example:
The text was updated successfully, but these errors were encountered: