-
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
Should we provide a dynamically checked function type? #295
Comments
It's partially dynamic: The callee is already known to be a function object that accepts invocations with exactly one positional argument (assuming the example type With a separate entry point where all actual arguments are checked against the parameter types at the beginning of the function body, it should not be more expensive than an invocation of an instance method with covariant parameters. It may or may not be worthwhile to allow the checked entry point to check just some actual arguments (perhaps using an extra parameter which is a bitmask), rather than all of them, to avoid spending time on checks that are guaranteed to succeed. This could make every closure a bit bigger, but it would also allow compilers to avoid generating any code at call sites concerned with downcasts on actual arguments, they'll just call the checked entry point. |
I think I (and anyone else reading along, I guess) are missing some context here. What would a deliberate use of |
The main purpose of a function type with some or all parameter types specified as the bottom type is that a variable/parameter of that type can hold many different types of function (because it's a supertype of all function types where those occurrences of // The type system will never be able to understand that `args[j]` is always
// a suitable argument for `funs[j]`, but let's say that we keep them consistent
// based on the business rules of the application and some manual thought.
var funs = <void Function(Null)>[(num i) {...}, (Object o) {...}, (String s) {...}];
var args = [1, true, "Three"];
main() {
for (int index = 0; index < funs.length; ++index) funs[index](args[index]);
} Currently, we have to cast If we ensure during code generation that the body of the callee will check the type of its actual arguments (say, because we generate code to call a "checked" entry point of the function object) then there is no need for any dynamic type checks at all at the call site. Alternatively, we could perform just the argument type check at the call site, treating it as a "slightly dynamic" invocation. We already treat invocations of instance methods with parameters that are covariant-by-class similarly, e.g., Similarly, we can allow meaningful code like We do, somewhat arbitrarily and definitely wastefully, generate static checks based directly on the statically known type arguments at the call site: class A<X> {
void foo(X x) {}
}
class B extends A<int> {
void foo(num n) {}
}
main() {
A<int> a = B();
num n = 3.14;
a.foo(n); // Throws.
} The invocation of It would be a correct optimization to omit these static checks for invocations where it is statically known that the required check is performed by the callee, and not throwing is of course a non-breaking change. If we do this then invocations of a function with static type like |
Thanks for clarifying.
Ah, right. You can't cast Allowing an invocation of a function whose parameter type is Never intuitively feels weird to me. I can sort of see the utility of treating like a strange "contravariant top type" but I think silently allowing that in invocations is more likely to confuse than illuminate. I'd lean towards requiring users to do a cast, even if it throws away from static information, just so to acknowledge that they're doing something odd with the type system. But I don't have a strong opinion or intuition about this. Maybe the simplest answer is to leave it out and see if users miss it. |
That's exactly the idea that made me bring up this topic: There is no way to express that cast, short of adding an 'existential open' construct to the language (cf. #170): main() {
void Function(Null) fun = (num i) {...};
Object arg = 1;
fun(arg as T); // But there is no way to write that `T`!
// .. except if we introduce a way to extract it (which is an 'existential open'):
if (fun is void Function(var T)) {
if (arg is T) fun(arg); // Safe.
}
} So we could make it possible for developers to establish the required context for calling |
Never
be dynamic calls?
Removing this from NNBD and re-titling to reflect discussion. There's still some interest in supporting a type which is more precise than dynamic in that it describes only functions of a specific arity, and possibly imposes additional static checks, but which is dynamically checked when called, allowing for more flexibility in usage. |
Edited Editing this to re-point this to the more general problem of having a type for dynamically checked functions, without being specific as to the mechanism (using
Never
as the marker being one particular strategy).The type
int Function(Never)
is useful only as a top type for unary functions. It could be useful to allow calls to this to be done using dynamic runtime checks, since in practice all deliberate uses of such function types currently require going through a dynamic call.A concern is the possibility of inadvertent uses - are there scenarios this might arise accidentally through inference?
The text was updated successfully, but these errors were encountered: