-
Notifications
You must be signed in to change notification settings - Fork 208
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
Inconsistent anonymous function type checking #2055
Comments
You would naturally want contravariance (or invariance) in order to have an instance variable where a type variable declared by the enclosing class occurs in the type in a contravariant position: // Needs declaration-site variance support.
// Experimental support: `--enable-experiment=variance`. NB: Not released, not complete.
class SomeClass<in TGeneric> { // Or `inout TGeneric`, for invariance.
void Function(TGeneric) someProperty;
SomeClass(this.someProperty);
void someMethod(TGeneric a) {}
} Cf. #524 for more information about declaration-site variance. With this declaration you'd get the traditional strict type checking: void main() async {
// `dynamic <: String` required, but does not hold.
void Function(dynamic) foo = (String a) {}; // Error.
foo('hello');
// `SomeClass` means `SomeClass<dynamic>`, and `dynamic <: String` does not hold.
SomeClass bar = SomeClass<String>((a) {}); // Inferred as `(String a) {}`. // Error
// Allowed, based on the declared type of `bar`.
bar.someMethod('hello');
// Allowed, based on the declared type of `someProperty`.
bar.someProperty('hello');
// You'd probably want this instead.
var baz = SomeClass<String>((a) {}); // Inferred type of `baz` is `SomeClass<String>`.
baz.someMethod(''); baz.someProperty(''); // OK.
} Today (where declaration-site variance has not yet been added to the language), and since the language was initiated about a decade ago, Dart uses dynamically checked covariance for all class type variables. This is exactly the way Java and C# have always treated arrays, and they also perform dynamic checks (such as This means that every type variable is considered to be covariant, and subtype relationships like void main() {
List<num> xs = <int>[];
xs.add(1); // OK statically, dynamic check `1 as int`, succeeds.
xs.add(1.5); // OK statically, but the dynamic check `1.5 as int` fails, and the invocation throws.
} In the case where an instance variable has a type where a type variable occurs in a non-covariant position, we may get a dynamic error when the instance variable is evaluated, because it does not have the statically known type: class A<X> {
void Function(X) f;
A(this.f);
}
void main() {
A<num> a = A<int>((i) {});
print(a.f); // Dynamic error: `a.f` has static type `void Function(num)`, actual type is `void Function(int)`.
} Cf. #297 as well, for comments/references on "contravariant members". In Dart of today, hence, you simply don't want to have an instance variable whose type contains a type variable from the enclosing class in a non-covariant position. |
Given that Dart currently uses dynamically checked covariance for all type variables, what can you do today? abstract class SomeClass<X> {
void someMethod(X a);
}
// Create subclasses and override `someMethod` as needed. Maybe you'll be able to use the above declaration, if you need to have With that declaration of Alternatively, you may be able to use the following approach, if you really need to have an instance variable whose type is a function type: class SomeClass<X extends void Function(Never)> {
X someProperty;
SomeClass(this.someProperty);
}
void main() {
var x = SomeClass((String s) {}); // `x` has type `SomeClass<void Function(String)>`.
x.someProperty('Hello'); // OK, statically and dynamically.
x.someProperty = (dynamic d) {}; // OK.
x.someProperty('world!'); // OK, statically and dynamically.
// The value of `x.someProperty` does actually accept any argument,
// and we could use a dynamic invocation to pass non-string arguments.
(x.someProperty as dynamic)(true); // OK, statically and dynamically.
} In this case Do note that you will then, of course, get an error in case the statically known type involves void main() {
SomeClass<void Function(dynamic)> x = SomeClass<void Function(String)>((s) {}); // Error.
// .. but if the declaration of `x` had not had an error in the initializing expression ..
x.someProperty(true); // OK, based on the declared type of `x`.
} |
Basically, this issue reports consequences of very old language design choices that we would handle by adding sound declaration-site variance (plus possibly something like use-site invariance), and those things are already the topic of existing language repo issues (#524 and #297, plus a few others). In that sense this issue is a duplicate of those language repo issues, and it actually isn't reporting on any behavior that isn't working as intended, so I'll close the issue. @tschai-yim, please follow those other issues and comment there if needed, or create a new issue proposing a different take on variance if the existing proposals do not fit your ideas at all. |
Hello, I found an inconsistency with the type checking of lambdas. I'm guessing this has more to do with how Dart's type system works and not a bug in the implementation so I'll file the issue here.
Problem
When assigning a value of type
Function(type)
(type
can be any type other thandynamic
) to a variable of typeFunction(dynamic)
it fails.But when you wrap the above lambda in a class the assignment works but you are unable to use it. Furthermore, when it is a method instead of a lambda property the usage works again.
Possible solutions
Allow Function(type) to Function(dynamic) assignment
TypeScript does it this way:
Disallow Function(type) to Function(dynamic) assignment and properly check the class properties
Scala does it this way:
The text was updated successfully, but these errors were encountered: