-
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
Declared type is never respected using extensions in dart 3. #53481
Comments
This is working as intended, but there are a few steps in this process. Consider this variant: void main() {
final Wrapper<TestColor> theme = (White() as TestColor).transform();
theme.value = Black();
print(theme.value);
}
// Other declarations unchanged. This variant compiles and runs without any errors. The underlying issue here is dynamically checked covariance: In the original program we have the following:
There are many situations where the covariant typing is avoided because type inference in Dart gives priority to the context type. For instance, if we declare However, in this particular case the covariant typing cannot be avoided based on the context type. The reason for this is that the typing of an extension method invocation is based on the receiver, and nothing else. So we inspect the receiver If you wish to support an enhancement of Dart whereby this kind of issue can be detected at compile time then you could vote for dart-lang/language#524. You can also emulate invariance already today. Replace the declaration of typedef _Inv<X> = X Function(X);
typedef Wrapper<X> = _Wrapper<X, _Inv<X>>;
class _Wrapper<T, Invariance> {
T value;
_Wrapper(this.value);
} If you do that then The idea behind this idiom is that we want the real wrapper class to take two type arguments, and they must always be of the form With the original But with the new two-type-parameter version which is called We renamed the real wrapper class to If dart-lang/language#524 or something similar is accepted into the language then we could get the same effect as follows: class Wrapper<inout T> {
T value;
Wrapper(this.value);
} The keyword |
First and foremost, I genuinely appreciate your comprehensive explanation regarding the issue. It’s clear that a significant amount of thought has been invested in the design and implementation of the Dart type system. However, I'd like to address a few concerns further.
To potentially mitigate this issue, it would be invaluable to have a lint warning for code segments that might lead to a runtime |
Could you provide the example which works differently in Dart 2 and Dart 3? The piece of code included in the issue (
Runtime checked covariance is always there (e.g. That's not to say that you should not trust your code - but rather to highlight the fact that even well typed code can throw runtime errors. |
Agreeing with @mraleph (in particular, I don't understand how it could work in Dart 2), I'll just add a couple of extra comments: @jonataslaw wrote:
Oh, but there is a static check! (So you could say that the situation is even worse ;-) The fact that a class uses a type variable in a non-covariant position (e.g., as the type of an instance variable) is known to the compiler, and it will generate the required code in order to check at run time whether or not that situation has caused an actual argument to be passed (in this case: to the setter of the variable So that If we get support for declaration-site variance (dart-lang/language#524) and make |
This piece of code might indeed address the problem. However, after examining it closely, I noticed that while the solution appears to tackle the issue, it does seem to conflict with one of Dart's rules. For clarity, you can refer to this: Dart Diagnostic Messages - Unnecessary Cast. If we don't manually disable this rule, the lint tool will automatically remove the casting upon saving, affecting the execution. I also observed that the error in question is somewhat elusive. At one point, this exception was introduced, possibly because of the Dart code plugin. I just tried to run code that had been in production since last year, and suddenly it stopped working. While I understand your viewpoint, I would like to kindly express my difference in opinion regarding its classification as a compile-time error. The issue emerges when the state of the variable shifts, making it hard to anticipate during compilation. It's accurate to say that unit tests might spot this, but ideally, the lint rule should prevent such constructs: final Wrapper<TestColor> theme = White().transform();
theme.value = Black(); If Ideally, linting and static typing tools should collaborate seamlessly to ensure such consistency. It would undoubtedly be advantageous for developers to trust the language's static typing without needing comprehensive unit tests for these fundamental verifications. |
@jonataslaw wrote, about unnecessary_cast:
Oh, that's unfortunate. That diagnostic probably does not make any attempt to detect whether the cast is actually unnecessary, it just lints every upcast (based on the wrong assumption that they are always unnecessary). You can use
I did not intend to say that this is a compile-time error! The situation where The point is that it is not particularly difficult to get rid of this kind of run-time error: We just need statically checked variance. We do need to recognize that statically checked variance isn't going to happen everywhere in Dart anytime soon. It's too much of a breaking change, and there is a huge amount of code out there which is actually working, and which was probably simpler to write because of dynamically checked covariance. However, if we get something like statically checked declaration-site variance then each class author (who isn't tied on their hands and feet based on existing code) can choose to use statically safe variance, and hence turn this run-time error into a compile-time error. |
Closing this as working as intended. FWIW, this is the expected behavior of covariant generics. It's fundamentally the same as the following Java example, where you'll get no static error, but a runtime Object[] foo = new String[] { "hello", "world" };
foo[1] = 42; There are several open issues around generics here: https//github.com/dart-lang/language/issues It might make sense to open a separate linter issue if we need to ignore a cast here. Another option for your code is to redefine the extension: extension ThemeTransform on TestColor {
Wrapper<T> transform<T extends TestColor>() => Wrapper<T>(this as T);
} A generic method (instead of a generic extension) will infer the type of |
@eernstg Thank you very much for the clarification. English isn't my first language, so this seems clearer to me now. The solution proposed by @vsmenon also works as intended. I will open an issue about the problem with the linter in the appropriate repository. Thank you very much for your time! |
When declaring a type like:
AbstractType foo = ConcreteImplementation();
The
AbstractType
type is expected to be respected. This is a common feature in all existing languages, and the default behavior of dart before version 3.Currently, this behavior is partially respected, within the classes where it is used, but it is definitely never respected using extensions.
We can verify this with the following example. What do we expect to be printed?
'Instance of Black'
What is printed? Nothing, because before executing this code we got an error:
Example:
Here is similar Flutter code, which shows the difference between using types within the class it will be used in, and in extensions. The second button works, the first triggers an error.
The text was updated successfully, but these errors were encountered: