-
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
Would like a way to avoid raw generic types #33119
Comments
Related to #32978, also. I'd also love to see |
The combination of type inference and naked types is very unintuitive. It seems like "well I am providing more information to Dart, so it should infer better." |
Java calls them "raw types", which I hear on the Dart team sometimes.
I agree, it should be a compile error. We need to be clear about the several different places a raw type (or a thing that looks like a raw type) can appear. Here's the behavior I'd like: // Error, raw type in annotation:
Set a = null;
// Error, raw type nested in annotation:
List<Set> b = null;
// No error, bare class name has type arguments inferred:
Set<int> c = new Set();
// Error, could not infer type argument for bare class name:
var d = new Set();
// Error, type inference does not fill in "half-complete" type annotations:
List<Set<int>> e = new List<Set>(); As for naming the flag, maybe |
@munificent Thanks. I updated my post/title to use the nomenclature "raw". I also like |
RE: // Does _not_ work as the user intended.
// List<dynamic> x = <dynamic>[1];
List x = [1]; If someone writes that, they can only use The rationale for preferring downwards inference type was cases like this: List<Object> x = [1]; At the time most people wanted that to pick List<dynamic> x = [1]; So That said I do think raw generic types are a problematic area in Dart 2, and it would be nice to find another interpretation for them. BTW, you could use
We could split that up pretty easily, if some of those are easier to enforce/more urgent than others. |
That might "fix" the problem, in that, the user will know they are likely making a mistake, but I don't see any reason to treat I think
An explicit
Definitely. I don't know what process is in place for evolving the language outside of being on the language team at this point, but definitely flags around downcasts, dynamic, and raw types are really desirable. It would be nice to have an idea of where the language is going and try to create flags that guide the user towards that path. |
yeah that's a good question ... IDK either. Hopefully it's on their radar. :) At least technically speaking, it's not hard to add a flag to Analyzer/CFE for this sort of thing, if we want to experiment. |
It's still being sorted out. Right now, basically everyone is just trying to get Dart 2 out the door. Once that's settled down, @leafpetersen and @lrhn will start figuring out what the evolution process looks like. |
We'll be looking at stricter lint flags for things like this. A question for @matanlurey : how do you feel about raw types with bounds? That is, if you have: class A<T extends num> {
}
void main() {
A x;
} then we currently treat |
Oh sure. Just wanted to make it clear to others reading this post that this isn't any sort of formal language proposal, just a bug :)
I don't use them as much as others internally. I guess as long as the bounds is not At the same time I've been debugging issues like the following: class ProtoContainer<T extends GeneratedMessage> {
T data;
}
void main() {
// In reality this isn't what users wanted, because they expect `UserMessage` or something else.
var container = new ProtoContainer();
} ... I don't know if its possible, but maybe a way to indicate that |
Hit another case internally. Accidental omission of type parameters extending/implementing: abstract class HasValue<T> {
T get value;
}
class WizBang extends HasValue {
@override
String get value => 'WizBang';
} This is totally valid code, and nothing is flagged in the analyzer or otherwise. At runtime, trying to use a // OK
class WizBang extends HasValue<String> {}
// OK
class WizBang<T> extends HasValue<T> {}
// Still OK, at least it is explicit
class WizBang extends HasValue<dynamic> {}
// BAD
class WizBang extends HasValue {} |
I suspect that we have two distinct issues here, both arising from the instantiate-to-bound mechanism:
An option It might, however, be a good idea to make the choice of whether to rely on instantiate-to-bound or not a property of each library (or in some other way: to make it part of the source code and scope it), because otherwise we'd just force every single line of code which might ever be reused to avoid using instantiate-to-bound at all (because, if it's a build option, there would always be some important customer out there who wants to compile their programs with |
Another case of a Flutter user hitting this: flutter/flutter#18357 |
Hit a particularly nasty case internally that took 3 of us ~2 hours to resolve: class Manager {
Response<SubContext> getResponse() {
return new Response<SubContext>(new SubContext(), (_) => new Response<SubContext>(_, null));
}
}
typedef Callback<T extends Context> = Response<T> Function(T context);
class Context {}
class SubContext extends Context {}
class Response<T extends Context> {
final T context;
final Callback<T> callback;
Response(this.context, this.callback);
}
void main() {
Manager manager = new Manager();
Response<Context> response = manager.getResponse();
// Response<SubContext>
print(response.runtimeType);
// Uncaught exception:
// TypeError: Closure 'Manager_getResponse_closure':
// type '(SubContext) => Response<SubContext>' is not a subtype of type '(Context) => Response<Context>'
print(response.callback.runtimeType);
}
See if you can spot the source of the error! This is because raw type of Why? Because |
That's a great example. However I'm not sure the problem is so much raw types, as it is |
Definitely. I just mean raw types also are a problem here. Use of |
Just FYI, we have already taken a small step in the direction of a sound typing for expressions like As of bfa8be8, the language specification defines the variance of each formal type parameter of a In other words, we've now established the foundation for knowing that As @jmesserly hinted, we may end up giving it the type With that, however, it would be safe to evaluate In any case, it just doesn't match the actual situation to assume anything more specific than |
I don't quite understand @eernstg - by |
Yes, there is no static type more specific than It would be possible, and might be worth considering, to make typedef Callback<X> = X Function(Object); With this, we would rely on some other check to establish the argument type (for instance, it could be a tear-off of a method with signature |
This was made |
FYI, I recently got back from leave, so I can take a look at this. Most of it is really straightforward except this part: // - Work similar to Java's <?> (wildcard operator). That would mean:
//
// --strict-raw-types
//
// List<int> x = <int>[1];
List x = [1]; That's a change in semantics, so I'd like to get sign-off from the language team first. (I believe @leafpetersen is going to help with specifying the |
No worries! Was just doing a bug scrub. I think we can skip the change in semantics, and make this entirely an optional static check for now. |
Sounds good! Also, do we have another bug tracking the I'm assuming that this bug is only about |
By the way, @srawlins and I would be super happy to help out, we could write some test cases, dogfood the UX messages, or whatever else you think will make rolling this out more successful. |
// List<int> x = <int>[1];
List x = [1]; The introduction of partial type annotation inference (that is, implicitly completing However, linting However, half-done type annotations would still be allowed in source code which is not subject to |
Do we have a preference for implementing this as a linter rule, vs adding a flag to Analyzer? (I asked a similar question on #33823, but now I'm wondering about this one too.) |
I wanted to report on some initial progress: https://dart-review.googlesource.com/c/sdk/+/75629 A note of caution, I'm just starting to test this on real code. That CL is being used to gather data to inform a design document/spec. The rules implemented are subject to change. |
Thanks! A few questions, but this definitely looks like enough to gather data!
What does this mean for us laypeople? :)
I'm guessing this means: class X<T> {
X(T _);
}
void main() {
var x = X(5); // OK
}
I'm not so sure about this one. Part of the reason for this check is to avoid optional type arguments (i.e. train users not to write
That seems correct enough, unless we ever intend to make |
Yeah sorry about that, these rules are still a work in progress. I'd like to send out a design document that walks through some examples. Happy to discuss here too, though.
Basically: List<dynamic> x = [42]; // allowed During inference, the downwards context is a type that an expression is expected to have, based on where it's used. In the example above, (Downwards and upwards refer to the direction type information is flowing through the tree. Given a simplified tree structure like this: VariableDeclaration("x",
InterfaceType("List", "dynamic"),
ListLiteral(IntegerLiteral(42))) ... type information is passed "down" from the VariableDeclaration node to the ListLiteral. But if
Yup that's right. Nice example.
Right now it's only used by the My reason for adding it to the prototype was to give folks an "out", so they can still turn on the flag. If someone is willing to use the flag at all, it seems a lot better than them not using it :). One of the challenges we've had with our opt-in checks is: how do we get people to use them? (We may need to split up
Leaf reminded me of type promotion (doh!), so that rule will need to be removed. |
No worries. Thanks for clarifying!
I agree. I think longer-term we might want to re-think (especially if the language team itself revisits what a raw type annotation means in the future), but for now the low-usage makes me think letting Flutter use this rule is fine.
I could see that (/cc @srawlins), even if only to add a transitional path (it would be unfortunate to have say, 100+ flags after |
Good news: my CL https://dart-review.googlesource.com/c/sdk/+/75629 is updated and sent for review. Thanks to @leafpetersen for clarifications about the desired behavior and @srawlins for the offer to help review and/or take over the CL if it needs significant changes. |
We'll need to add official documentation for this, but for now the instructions are:
analyzer:
language:
strict-inference: true
strict-raw-types: true |
Hi I wonder where can I know all those "stricter" checks? I have searched doc, but cannot find anything about |
I have a long-standing task to document these at https://dart.dev/guides/language/analysis-options. But the list right now is just these two strict modes that you mention. |
@srawlins Thanks! |
Tangentially related to #31410.
While migrating swaths of internal code (probably in the tens-of-thousands-of-lines, myself), one of the most common errors users are running into when migrating to Dart2 are so-called
"naked"raw types(is there a better name?). For example:Here is a more extreme example:
My 🔥 take: In Dart 2 + some flag, "raw" types that are implicitly dynamic should:
<?>
(wildcard operator). That would mean:... if we are accepting name nominations, I nominate
--strict-raw-types
.The text was updated successfully, but these errors were encountered: