-
Notifications
You must be signed in to change notification settings - Fork 207
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
Be more strict on inferred types with no constraints? #4197
Comments
(Why is the greatest closure of a type variable with a bound not its bound? Is it because that doesn't work for F-bounded types, or because we want to allow super-bounded types?) This would be one way to avoid an implicit and un-asked-for Might be better to combine it with other "no implicit (I'd also make the context type of an expressionStatement's expression be |
Exactly.
Sounds good! We should avoid breaking existing code whenever possible, and there might be some kinds of code where the current approach (where a number of expressions have type So it would be great if we could learn about specific projects or types of coding where this kind of breakage is unwanted and inconvenient. In those situations, it might be possible to introduce the type @Wdestroier, do you have some situations in mind where you'd prefer to get the type |
I always interpret the code as ommiting the type means dynamic. I can try to compile this project https://github.com/wdestroier/asimox (for example) with this feature enabled. The application is more prone to erros, but maybe they're simple to fix, or the library can be updated. Inferring Generics are another source of complexity, is the cast at the end necessary? Maybe it makes sense in the real code, but not in the example and I'm overcomplicating. Or is the cast syntax ugly and people are trying to write // Strange cast at the end.
X method<X>(Object? o) {
// ...
return o as X;
}
// No generics and no dynamic.
Object? method(Object? o) {
// ...
return o;
}
method(o)..arglebargle // Error.
(method(o) as X)..arglebargle; // Ok. |
This is interesting! I don't hear this perspective on @Wdestroier wrote:
When it comes to declarations, type inference is actually only used with local function declarations and function literals (lambdas). This means that top-level functions and static and instance methods and getters would still have the return type class A {
// The return type is `dynamic` in all cases.
static staticMethod() {}
static get staticGetter => "Hello, I'm a String!";
method() { return 1; }
get getter => "Hello!";
}
// The return type of `main` is `dynamic`, too.
main() {
A.staticMethod().arglebargle;
A.staticGetter.arglebargle;
A().method().arglebargle;
A().getter.arglebargle;
if (1 > 2) main().arglebargle;
} Similarly for formal parameter declarations. I'm not sure how well this covers the use cases that you care about, but it seems likely that it would cover much of it. About the generic functions: X method<X>(Object? o) {
// ...
return o as X;
} The purpose of this declaration could be that every call site is allowed to request a result of an arbitrary type ("I want an List<X> method2<X>() => <X>[];
void main() {
List<Iterable<Symbol>> xs = method2(); // OK!
Iterable<int> ys = method2(); // Sure, no problem!
} When the return type is So why wouldn't you just use this approach, which seems to give you the same type safety properties in a simpler manner?: method(Object? o) {
// ...
return o;
} This means that the cast to the context type will occur at the call site rather than in the return statement, but the only difference between those two behaviors is that the former has a stack trace which is one line shorter. I don't think there is any other way to see the difference. The next variant uses the type Object? method(Object? o) {
// ...
return o;
} My assumption is that you don't want this because your application context makes This proposal would make type inference slightly more strict, but I haven't seen anything in your examples that would stop working. Perhaps it doesn't actually create any difficulties for you? |
Thanks, @eernstg! It's only my perspective, because I was looking for a language like Java in the past and with dynamic features and Dart (possibly by accident) has a great combination of both.
These test cases aren't very complete, sorry, I took a real example and tried to wipe as much as possible. "button" function with generic types and the "onClick" parameter: // button is one of all the generated HTML elements.
DomElement<Element, Event> button<Element, Event>({
DomEventConsumer<Element, Event>? onClick,
...
}) {...} "DomEventConsumer" definition: import 'package:domino/domino.dart' as domino show DomEvent, DomLifecycleEvent;
// domino has effectively the same typedef, except it doesn't tell the return type.
// This typedef exists to ensure domino is only part of the internal implementation.
typedef DomEventConsumer<Element, Event> = FutureOr<void> Function(
domino.DomEvent<Element, Event> event,
); Usage: class MyButton extends DomNode {
@override
DomNode render() {
return button(
type: 'button',
text: 'Click me',
onClick: (event) {
print('Clicked!');
event.event // <-- Currently dynamic, would be an error.
..stopPropagation()
..preventDefault();
},
),
}
} Others packages use domino too if I recall correctly, such as jaspr and deact. |
The specification of type inference in inference.md says about the greatest closure of a type in the section Type variable elimination that
which in the context means that the greatest closure of a type parameter which is in the target set
L
isObject?
.The section Type schema elimination says that
_
is treated as a type parameter in the target set.Next, the section Grounded constraint solution for a type variable says that
which in the context means that when a type variable
X
has the combined constraint_ <: X <: _
then the solution is the greatest closure of_
, that is,Object?
.Nevertheless, the implemented behavior chooses
dynamic
, at least in some cases:The fact that the analyzer does not report a compile-time error at the invocation of
arglebargle
shows that the type inferred forgetContextType(1)
isdynamic
rather thanObject?
. The common front end confirms this by also not reporting a compile-time error, and further by printingdynamic
at run time.It is a breaking change, but I'd recommend that type inference is adjusted to infer
Object?
rather thandynamic
in such cases. We could at least experiment with this outcome and see how severe the breakage is. The change would be language versioned such that developers can opt in to the more strict analysis when they are ready.@dart-lang/language-team, WDYT?
The text was updated successfully, but these errors were encountered: