-
Notifications
You must be signed in to change notification settings - Fork 205
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
Infer most specific generic type for methods #1557
Comments
This works for me: class Clazz<T> {}
extension ClazzExt<R> on Clazz<R> {
Clazz<R> join<S>(Clazz<S> other) => Clazz<R>();
}
void main() {
final a = Clazz<int>();
final b = Clazz<double>();
print(a.join(b));
} |
This ignores the type of the argument and returns the type of the receiver: It prints |
Yes, this is how it works. In the first case, the compiler will try to find a common supertype between the two arguments, as they both use If you try something like: extension ClazzExt<T, R extends T, S extends T> on Clazz<R> {
Clazz<T> join<S>(Clazz<S> other) => Clazz<T>();
} The compiler will infer Why the compiler will infer the more generic supertype ( |
Yeah, I tried that too, but I need the most specific type. I can work around it by writing |
@renggli I haven't thought comprehensibly through this, but I suspect that there isn't a clean way to do this. There's an unexpected asymmetry underlying this: I like to be able to think about |
In the example code I provided: extension ClazzExt<T, R extends T, S extends T> on Clazz<R> {
Clazz<T> join(Clazz<S> other) => Clazz<T>();
} The compiler knows that the returned type |
@mateusfccp I think there's some binding scope confusion there in your code. Note that you have two different type variables named extension ClazzExt<T, R extends T, S extends T> on Clazz<R> {
Clazz<T> join<P>(Clazz<P> other) => Clazz<T>();
} If I now do First, extension method resolution matches Second, after extension method resolution is done, inference for the generic method kicks in, and matches |
@leafpetersen I fixed what I meant with my code by removing This is what I meant: extension ClazzExt<T, R extends T, S extends T> on Clazz<R> {
Clazz<T> join(Clazz<S> other) => Clazz<T>();
} What I was thinking was something like this:
Consider Valid matches are |
@mateusfccp I think then for your example there's really no need for the extra type parameters. The key point is that for what you want to work, extension method resolution needs to consider not just the receiver when resolving the generic arguments to the extension, but also the arguments to the method call. This is not what is currently done: instead, extension method resolution is done WRT the receiver only, which fixes the generic parameters to the extension, and then resolution is done on the method itself. If we did consider the arguments to the method as part of resolution, then extension ClazzExt<T,> on Clazz<T> {
Clazz<T> join(Clazz<T> other) => Clazz<T>();
} would work just fine, since at the point of extension method resolution, we would consider both the receiver of To be honest - I don't remember the design discussion for why we went this way and what the tradeoffs were. I know that I raised this question in the design discussion, but I don't off the top of my head remember why we went the other way, and I can't quickly find a discussion issue for it. It may have had to do with the desire @lrhn had to model this around implicit wrapper classes? Not sure. @lrhn may have a better recollection. |
There were several reasons, with "not introducing method overloading" being the strongest. Dart does not have method overloading. Each class can only have one method with each name. If we allowed extension methods to be chosen depending on the method arguments, you would effectively get overloading for extension methods (we'd have to ignore a two-parameter extension method when you're calling with three arguments and vice versa), which might entice people to write their APIs entirely in extension methods instead of normal method. So, we went for a design where extension methods mirrors instance methods as far as possible. For this example, if it had been an instance method, you'd have gotten the same errors: class Clazz<T> {
Clazz<T> join(Clazz<T> other) => Clazz<T>();
}
void main() {
final a = Clazz<int>();
final b = Clazz<double>();
print(a.join(b)); // The argument type 'Clazz<double>' can't be assigned to the
// parameter type 'Clazz<int>_ 👎
} There is nothing new and special about doing this with extensions, they mirror instance methods precisely here too. What you really-really want is: Clazz<R> join<R super T>(Clazz<R> other) => ...; but Dart sadly does not have super-bounded type variables. |
Due to dart-lang/language#1557 the parser constructed by `ChoiceParserExtension.or` is not typed correctly. Runtime-type errors are avoided by construction `ChoiceParser`s directly.
The following example works as expected:
The problem I am facing is that I cannot figure out a way to write an (operator) method within
Clazz
that works and that doesn't yieldClazz<dynamic>
as a result.I would have expected that the following static extension method is very similar to the function above, yet the compiler is unable to infer
R
asnum
in this case and yieldsThe argument type 'Clazz<double>' can't be assigned to the parameter type 'Clazz<int>'
:Context: https://stackoverflow.com/questions/66851159/getting-a-more-specific-generic-type-than-dynamic
The text was updated successfully, but these errors were encountered: