-
Notifications
You must be signed in to change notification settings - Fork 13
Bad errors on generic bounding chains following fix for #84, strong mode incompatible #91
Comments
@jmesserly @leafpetersen Who might know better about types in analyzer. |
…art-archive#86 In the future, maybe there's a way to know the directive's parameters based on how its used in the template. For now, use the upper bounds because those are the lowest types and therefore can be relied upon in the template, and avoids the 'T is not subtype of String' errors. Refactoring: changed event typechecking to go off of a new type field on InputElement so that it could be deparameterized consistently at collection time rather than deparameterized everywhere its used. Same for outputs, but those already had an eventType field that was used everywhere. Opening new issue dart-archive#91 for a bug that's arguably in substitute2; when there's a chain of bounded type parameters, the substitutions aren't done recursively, leaving parameterized types in at the end. However, these types have the proper lower bound set, so the typing behavior should work the same, its just that the errors will be not as clear as they should be.
This is correct. If
I don't know what this means, can you give me a concrete example of what you expect to happen vs what is happening? |
Yeah I want to add on to Leaf's question ... could you link to the code where you're implementing this? Are you trying to see if the string value "thisShouldWork" can be assigned to the "stringBound" field? |
The example I gave where 'A extends B, B extends C' I'm accessing a member of the class, and getting its type. That type is So I call Is there a better way to do this? I think the template is roughly equivalent to MyComponent /* without arguments */ comp = new MyComponent /* no arguments */();
comp.stringBound = "blah"; //correct
comp.stringBound = 5; // int not assignable to string Instead I'm getting the error The plot thickens as well, looks like it doesn't work without weak mode in my code, but I had a hack left in place
I took the wrong conclusion while trying to figure out why THAT test was passing. Edit: note too that that hack won't work for |
instantiateToBounds on TypeSystem (which should be an instance of StrongTypeSystemImpl in strong mode) will do what your first line does, and get the instantiated type. I think the answer in strong mode was supposed to be |
it does not currently work in strong mode: class MyComponent<A extends B, B extends C, C extends String> {
A stringBound;
}
main() {
var x = new MyComponent();
x.stringBound = 'hello';
print(x.stringBound);
}
this is definitely a strong mode bug, whatever we do it shouldn't end up as B. I'll see if we have one filed already, I believe we do. There was assumption that bounds on type parameters would only refer to earlier ones in the list, not later ones like this example does. |
Update: if I reverse the order it works in strong mode: class MyComponent<C extends String, B extends C, A extends B> {
A stringBound;
}
main() {
var x = new MyComponent();
x.stringBound = 'hello';
print(x.stringBound);
} |
this is covered by dart-lang/sdk#27072 |
Woot, glad I was trying so hard to break my own analysis code that I broke strong mode! I'm going to grab myself a cookie. So I should be able to call |
yeah it's a great test case! 👍 I think that's right... you'll want to typeSystem.instantiateToBounds on the BTW feel free to leave a note if that bug is blocking you, we can increase the priority. |
Hmm, that sounds great and not great. I really wanted there to be lower bounds type checking for generic components, just from the perspective of trying to make useful angular errors. Given that the typing of templates isn't truly specified anywhere in the angular spec or in the dart spec...and we are generating warnings rather than compile errors...I'd like to still use lower bounds rather than On the other hand, Also worth noting, we are traversing the ast of the class to build up a model of a directive when this happens, so I am going from a I guess as long as strong mode is turned on it will do the right thing so that's great. On the other hand I can leave it as it is now until the bug in the sdk is fixed..The difference between the two right now is that when using |
if you're traversing the class AST, could you get the ClassElement from the ClassDeclaration node? |
yeah I think you definitely will want to use instantiateToBounds over substitute2. The latter is for something very specific, it's best thought of as a low level primitive that we use to implement other things (such as TypeSystem.instantiateToBounds and FunctionType/InterfaceType instantiate). |
I think I solved it without using DartType _deparameterizeType(
ClassElement classElement, DartType parameterizedType) {
if (parameterizedType == null) {
return null;
}
// See #91 for discussion about bugs related to bounds
var getBound = (p) {
var type = p.bound;
while (type != null && type is TypeParameterType) {
type = type.bound;
}
return type ?? typeProvider.dynamicType;
};
var getType = (p) => p.type;
var bounds = new List<DartType>()
..addAll(classElement.typeParameters.map(getBound));
var parameters = new List<DartType>()
..addAll(classElement.typeParameters.map(getType));
return parameterizedType.substitute2(bounds, parameters);
} Maybe that is useful to you. Happy to take discussion about whether I'm doing too much or not. |
review suggestion ... your "getBound" is just |
another review suggestion: instead of but yeah I would definitely recommend instantateToBounds, if you don't want non-strong mode behavior, you could always create a StrongTypeSystemImpl to use it for instantateToBounds, that way you get consistent results and you'll pick up any bug fixes we do as well :) |
…art-archive#86 In the future, maybe there's a way to know the directive's parameters based on how its used in the template. For now, use the upper bounds because those are the lowest types and therefore can be relied upon in the template, and avoids the 'T is not subtype of String' errors. Refactoring: changed event typechecking to go off of a new type field on InputElement so that it could be deparameterized consistently at collection time rather than deparameterized everywhere its used. Same for outputs, but those already had an eventType field that was used everywhere. Opening new issue dart-archive#91 for some tricky subtleties in generic chains. Looks like this code should some day use `instatiateToBounds` but that will not do much typing without strong mode turned on, and has a bug that is open in their repo.
Ah great tips all three! Thanks! |
OK leaving this ticket open until dart-lang/sdk#27072 is solved, to use |
(#95) * Best current solution to handle all types of generic directives refs #86 In the future, maybe there's a way to know the directive's parameters based on how its used in the template. For now, use the upper bounds because those are the lowest types and therefore can be relied upon in the template, and avoids the 'T is not subtype of String' errors. Refactoring: changed event typechecking to go off of a new type field on InputElement so that it could be deparameterized consistently at collection time rather than deparameterized everywhere its used. Same for outputs, but those already had an eventType field that was used everywhere. Opening new issue #91 for some tricky subtleties in generic chains. Looks like this code should some day use `instatiateToBounds` but that will not do much typing without strong mode turned on, and has a bug that is open in their repo. * Review feedback from jmesserly
Move to a class-instantiation paradigm; instantiate the type to bounds before building the component (still using custom instantiation logic while issue dart-archive#91 is blocked), and use that to resolve getters/setters up the inheritance tree. No change to the @input() and @output() workflows because those don't involve inheritance.
* Solve issue #103 generic inherited types still unresolved Move to a class-instantiation paradigm; instantiate the type to bounds before building the component (still using custom instantiation logic while issue #91 is blocked), and use that to resolve getters/setters up the inheritance tree. No change to the @input() and @output() workflows because those don't involve inheritance. * no longer pass classElement everywhere
In the branch with fix for #84, the type checks are working properly for no strong mode, but the errors are not fully clear/resolved.
When you use this directive in a template, we don't have type parameters but we do have the lower bounds.
With the fix for #86, pass in a string, without strong mode, you get no errors. So that works. Pass in an integer, you get an error, so that works. Even in strong mode, I believe outputs work when they work.
However, the errors you get will say "int is not assignable to B" because
stringBound
is of type A, and A extends B and that's as far as we resolved it. The typing works because we replaced "A" on the type which had no "bound" set, to B from the ClassElement which did, and that bound makes it more or less act like its own bound when not in strong mode.However, when in strong mode, String is not assignable to T extends String, even though the reverse is true.
The problem comes when calling substitute2, and it doesn't substitute recursively. Not sure if the fix for this should be in substitute2 in the analyzer repo?
There are tests for this in
tasks_test.dart
, referencing this ticket number. Uncomment them to do the work.The text was updated successfully, but these errors were encountered: