SE1 | Enforce the use of type annotations | A strict type hint is generated if a type annotation is omitted anywhere the spec defines one could be accepted. Corollary: a strict type hint is generated anywhere the unknown type dynamic is found during static analysis (Object should be used instead) |
void main() {
var i = blah(); // this should not produce a hint
// var should allow dynamic
print(i);
}
// blah() is missing a return type annotation
// a hint is fine assuming that flow analysis isn't supported // otherwise the type could be inferred from the return type
// the least common denominator would be fine
blah() {
return 4;
}
var i = blah();
print(i);
}
dynamic blah() { // <= added type annotation
return 4;
}
It's great news that you consider adding strict mode!
Can you allow comments in the doc?
I don't think this is the desired behavior (at least not for me)
SE1
Enforce the use of type annotations
A strict type hint is generated if a type annotation is omitted anywhere the spec defines one could be accepted. Corollary: a strict type hint is generated anywhere the unknown type dynamic is found during static analysis (Object should be used instead)
instead a hint should be generated when the type inferred from the right hand side would generate a warning (as when this type had been added explicitly).
Dynamic on the RHS should only be valid when the LHS was declared as dynamic too.void main() {
var i = blah(); // this should not produce a hint
// var should allow dynamic
print(i);
}
// blah() is missing a return type annotation
// a hint is fine assuming that flow analysis isn't supported // otherwise the type could be inferred from the return type
// the least common denominator would be fine
blah() {
return 4;
}
var i = 5;
Object i = 5;
How would this be handled?void main() {var i = blah();
print(i);
}
dynamic blah() { // <= added type annotation
return 4;
}
void main() {Object i = blah();print(i);}Object blah() {return 4;}
package main
import "fmt"
func main() {
i := doIt() // i's type becomes int
s := "test" // s's type becomes string (compile time constant)
s = i // ** cannot use i (type int) as type string in assignment
fmt.Printf("We have i = %v and s = %v\n", i, s)
}
func doIt() int {
return 5
}
void main() {var i = 5;i = new List<int>();print(i);}
void main() {Object i = 5;i = new List<int>();print(i);}
It's great news that you consider adding strict mode!
As I explain in the proposal, the overarching goal behind is to try and help improve code quality and reduce the effort required to achieve higher quality code. Missing off the return type annotation from blah() doesn't help me, as a human, understand the code any better. In fact it makes it worse. Computers might be good at flow analysis; I am not.
SomeType<SomeOtherType> x = new SomeType<SomeOtherType>();I still think the only helpful feature would be that the analyzer produces hints when it can't verify the code with the existing type annotations.
With 'dynamic' I explicitly tell the analyzer that it shouldn't care.
Build tools and continuous integration - dartanalyzer, with modifications as proposed, could be used as part of a continuous integration setup. For example, a Travis build of, say, AngularDart could be made to fail if someone committed code that omitted a return type annotation
Is strict mode only checked for the package currently analyzed but ignored for dependencies?
I guess this would be fine for check-in-hooks when each package is in its own repository
but I wonder how this would work for CI when the application is split into several packages.
I would then need to analyze each package separately instead of just the entry point I guess?
It's great news that you consider adding strict mode!Glad you are interested! Thank you for the feedback.Can you allow comments in the doc?I decided against that; managing discussion here in this thread leaves the document 'clean' for someone reading it for the first time. We can, and of course will, produce revisions as required.I don't think this is the desired behavior (at least not for me)
SE1
Enforce the use of type annotations
A strict type hint is generated if a type annotation is omitted anywhere the spec defines one could be accepted. Corollary: a strict type hint is generated anywhere the unknown type dynamic is found during static analysis (Object should be used instead)
instead a hint should be generated when the type inferred from the right hand side would generate a warning (as when this type had been added explicitly).
Dynamic on the RHS should only be valid when the LHS was declared as dynamic too.void main() {
var i = blah(); // this should not produce a hint
// var should allow dynamic
print(i);
}
// blah() is missing a return type annotation
// a hint is fine assuming that flow analysis isn't supported // otherwise the type could be inferred from the return type
// the least common denominator would be fine
blah() {
return 4;
}
As I explain in the proposal, the overarching goal behind is to try and help improve code quality and reduce the effort required to achieve higher quality code. Missing off the return type annotation from blah() doesn't help me, as a human, understand the code any better. In fact it makes it worse. Computers might be good at flow analysis; I am not.Use of var is equivalent to omitting a type annotation, hence is equivalent to use of dynamic. The proposal (currently) precludes the use of dynamic as a type annotation (see my comments further down). Instead, it is proposed Object be used.
as shorthand for:var i = 5;
However, if we go down that path, I think we'd need to sharply delineate exactly what type inference strict mode will do, and what type inference it won't. I would propose that we only allow 'var' on a variable declaration when the RHS is a literal or an instance creation expression. So for example:int i = 5;
var i = 5; => int i = 5;
var s = 'foo'; => String s = 'foo';
var d = 5.0; => double d = 5.0;
var c = new Cow(); => Cow c = new Cow(); *var l = <int>[]; => List<int> l = <int>[]; *
Note: the three examples I marked with "*" above are my motivation for suggesting this. I find these patterns come up frequently, and it really annoying to have to repeat the type when it appears elsewhere on the same line.var m = <int, String>{}; => Map<int, String> m = <int, String>{}; *
var x = [1, 2, 3]; // type is ambiguous; it could be List<int>, List<num>, or List<Object>.
var x = {'foo': 'bar'}; // also ambiguous; could be Map<String, String>, Map<String, Object>, etc.
var x = 3 + 5; // RHS is neither a literal nor an instance creation expression.var x = blah(); // RHS is neither a literal nor an instance creation expression.
Yes, from the language point of view, var is equivalent to dynamic. But I don't think that necessarily means we have to treat it as equivalent for the purposes of the type checking in strict mode.I would also be willing to consider if, for the purposes of strict mode type checking, we treated:as shorthand for:var i = 5;However, if we go down that path, I think we'd need to sharply delineate exactly what type inference strict mode will do, and what type inference it won't. I would propose that we only allow 'var' on a variable declaration when the RHS is a literal or an instance creation expression. So for example:int i = 5;
var i = 5; => int i = 5;var s = 'foo'; => String s = 'foo';var d = 5.0; => double d = 5.0;var c = new Cow(); => Cow c = new Cow(); *var l = <int>[]; => List<int> l = <int>[]; *Note: the three examples I marked with "*" above are my motivation for suggesting this. I find these patterns come up frequently, and it really annoying to have to repeat the type when it appears elsewhere on the same line.var m = <int, String>{}; => Map<int, String> m = <int, String>{}; *
var m = <int, String>{}; // OR the theoretical
m := <int, String>{}; // Go-like, but not valid Dart
Map<int, String> m = <int, String>{};
var m = <int, String>{}; // [1]m = new List<Dog>(); // [2]
But in the cases below, type inference would not be done, and hence the user would be required to replace "var" with the desired type:var x = [1, 2, 3]; // type is ambiguous; it could be List<int>, List<num>, or List<Object>.
var x = {'foo': 'bar'}; // also ambiguous; could be Map<String, String>, Map<String, Object>, etc.
var x = 3 + 5; // RHS is neither a literal nor an instance creation expression.
var x = blah(); // RHS is neither a literal nor an instance creation expression.
Redundant type information everywhere don't help much either to make the code easier to understand.
Do you plan to use strict mode internally?
Build tools and continuous integration - dartanalyzer, with modifications as proposed, could be used as part of a continuous integration setup. For example, a Travis build of, say, AngularDart could be made to fail if someone committed code that omitted a return type annotation
Is strict mode only checked for the package currently analyzed but ignored for dependencies?
I guess this would be fine for check-in-hooks when each package is in its own repository
but I wonder how this would work for CI when the application is split into several packages.
I would then need to analyze each package separately instead of just the entry point I guess?
class Fruit {Slice() {// ...}}class Apple extends Fruit {}CreateNewFruit() {return new Apple();}CutUpFruit(f) {f.Slice();}
class Fruit {Slice() {// ...}}class Apple extends Fruit {}CreateNewFruit() {return new Apple();}
CutUpFruit(f) {f.Slice();}void main() {Apple a = CreateNewFruit() as Apple;a.Slice(); // ORCutUpFruit(a);}
Object o = CreateNewFruit();
Redundant type information everywhere don't help much either to make the code easier to understand.Please see my response to PaulB. I'm minded to agree.Do you plan to use strict mode internally?Not sure what you mean by internally?
On Thursday, August 14, 2014 4:26:56 PM UTC+2, Paul Jolly wrote:Redundant type information everywhere don't help much either to make the code easier to understand.Please see my response to PaulB. I'm minded to agree.Do you plan to use strict mode internally?Not sure what you mean by internally?This was in relation to the topic mentioned below (CI/dependencies).Would the packages shipped by the Dart team be updated to comply with strict mode.
Yes, from the language point of view, var is equivalent to dynamic. But I don't think that necessarily means we have to treat it as equivalent for the purposes of the type checking in strict mode.I would also be willing to consider if, for the purposes of strict mode type checking, we treated:as shorthand for:var i = 5;However, if we go down that path, I think we'd need to sharply delineate exactly what type inference strict mode will do, and what type inference it won't. I would propose that we only allow 'var' on a variable declaration when the RHS is a literal or an instance creation expression. So for example:int i = 5;
var i = 5; => int i = 5;var s = 'foo'; => String s = 'foo';var d = 5.0; => double d = 5.0;var c = new Cow(); => Cow c = new Cow(); *var l = <int>[]; => List<int> l = <int>[]; *var m = <int, String>{}; => Map<int, String> m = <int, String>{}; *
Note: the three examples I marked with "*" above are my motivation for suggesting this. I find these patterns come up frequently, and it really annoying to have to repeat the type when it appears elsewhere on the same line.
But in the cases below, type inference would not be done, and hence the user would be required to replace "var" with the desired type:var x = [1, 2, 3]; // type is ambiguous; it could be List<int>, List<num>, or List<Object>.var x = {'foo': 'bar'}; // also ambiguous; could be Map<String, String>, Map<String, Object>, etc.
var x = 3 + 5; // RHS is neither a literal nor an instance creation expression.
var x = blah(); // RHS is neither a literal nor an instance creation expression.
But according to the spec, use of var is equivalent to dynamic. So someone unaware of this special 'strict' analysis rule (but yet subject to it, perhaps as part of a team decision) would perhaps be understandably confused.Don't get me wrong, I would much prefer the shorter versions you all propose. But, acting as devil's advocate, are we getting too close to the spec here?
But in the cases below, type inference would not be done, and hence the user would be required to replace "var" with the desired type:var x = [1, 2, 3]; // type is ambiguous; it could be List<int>, List<num>, or List<Object>.Indeed. According to the proposal this would generate a strict type hint because a type annotation is missing on the RHS.var x = {'foo': 'bar'}; // also ambiguous; could be Map<String, String>, Map<String, Object>, etc.Ditto.var x = 3 + 5; // RHS is neither a literal nor an instance creation expression.Is the RHS not a compile time constant here?
var x = blah(); // RHS is neither a literal nor an instance creation expression.But if blah() has a return type annotation this is surely permissible?
is indeed much cleaner to write and read than:Map<int, String> m = <int, String>{};However does this not introduce some ambiguity with respect to the spec? For example if I were allowed to write:var m = <int, String>{}; // [1]m = new List<Dog>(); // [2]
with strict analysis of [1] essentially attributing the type Map<int, String> to m, line [2] would generate a strict type hint. I think we're agreed on that point.But according to the spec, use of var is equivalent to dynamic. So someone unaware of this special 'strict' analysis rule (but yet subject to it, perhaps as part of a team decision) would perhaps be understandably confused.
Don't get me wrong, I would much prefer the shorter versions you all propose. But, acting as devil's advocate, are we getting too close to the spec here?But in the cases below, type inference would not be done, and hence the user would be required to replace "var" with the desired type:var x = [1, 2, 3]; // type is ambiguous; it could be List<int>, List<num>, or List<Object>.
Indeed. According to the proposal this would generate a strict type hint because a type annotation is missing on the RHS.var x = {'foo': 'bar'}; // also ambiguous; could be Map<String, String>, Map<String, Object>, etc.
Yes, from the language point of view, var is equivalent to dynamic. But I don't think that necessarily means we have to treat it as equivalent for the purposes of the type checking in strict mode.I would also be willing to consider if, for the purposes of strict mode type checking, we treated:as shorthand for:var i = 5;However, if we go down that path, I think we'd need to sharply delineate exactly what type inference strict mode will do, and what type inference it won't. I would propose that we only allow 'var' on a variable declaration when the RHS is a literal or an instance creation expression. So for example:int i = 5;
var i = 5; => int i = 5;var s = 'foo'; => String s = 'foo';var d = 5.0; => double d = 5.0;var c = new Cow(); => Cow c = new Cow(); *var l = <int>[]; => List<int> l = <int>[]; *var m = <int, String>{}; => Map<int, String> m = <int, String>{}; *
I would add function/method calls that have a type annotation for the return typeNote: the three examples I marked with "*" above are my motivation for suggesting this. I find these patterns come up frequently, and it really annoying to have to repeat the type when it appears elsewhere on the same line.
But in the cases below, type inference would not be done, and hence the user would be required to replace "var" with the desired type:var x = [1, 2, 3]; // type is ambiguous; it could be List<int>, List<num>, or List<Object>.var x = {'foo': 'bar'}; // also ambiguous; could be Map<String, String>, Map<String, Object>, etc.for these two at least `List` and `Map` could be inferred.
When all elements have the same type I would also infer the generic typeif the developer wants a more general type he can change to <num>[1, 2, 3] or <dynamic>[1,2,3]similar for map
var x = [];var x = {};
var x = 3 + 5; // RHS is neither a literal nor an instance creation expression.as already mentioned this is a constant expression, these should always be inferred.var x = blah(); // RHS is neither a literal nor an instance creation expression.when blah() has a type annotation for the return type it should be inferredThat's my two cents, anyway.PaulB
--
is indeed much cleaner to write and read than:Map<int, String> m = <int, String>{};However does this not introduce some ambiguity with respect to the spec? For example if I were allowed to write:var m = <int, String>{}; // [1]m = new List<Dog>(); // [2]This is where I expect to get a hint.I than have to explicitly state that I want a more generic type.var m = <int, String>{} as dynamic;dynamic m = <int, String>{};with strict analysis of [1] essentially attributing the type Map<int, String> to m, line [2] would generate a strict type hint. I think we're agreed on that point.But according to the spec, use of var is equivalent to dynamic. So someone unaware of this special 'strict' analysis rule (but yet subject to it, perhaps as part of a team decision) would perhaps be understandably confused.but it would work when used in non-strict mode without any hint/warning as required.Don't get me wrong, I would much prefer the shorter versions you all propose. But, acting as devil's advocate, are we getting too close to the spec here?But in the cases below, type inference would not be done, and hence the user would be required to replace "var" with the desired type:var x = [1, 2, 3]; // type is ambiguous; it could be List<int>, List<num>, or List<Object>.either replace- var with a type- or specify the type for the RHS <num>[1,2,3];- or as mentioned above infer the type when all elements have the same type (this can be tricky when the values are not primitives (what if it is a list of comparable types or other values that implement more than one interface)Indeed. According to the proposal this would generate a strict type hint because a type annotation is missing on the RHS.var x = {'foo': 'bar'}; // also ambiguous; could be Map<String, String>, Map<String, Object>, etc.a map without generic type parameters could be treated as Map<String, dynamic>. I think this is what you get with `new Map();`
Ditto.var x = 3 + 5; // RHS is neither a literal nor an instance creation expression.Is the RHS not a compile time constant here?var x = blah(); // RHS is neither a literal nor an instance creation expression.But if blah() has a return type annotation this is surely permissible?
--
On 14 August 2014 07:49, Günter Zöchbauer <gzo...@gmail.com> wrote:Yes, from the language point of view, var is equivalent to dynamic. But I don't think that necessarily means we have to treat it as equivalent for the purposes of the type checking in strict mode.I would also be willing to consider if, for the purposes of strict mode type checking, we treated:as shorthand for:var i = 5;However, if we go down that path, I think we'd need to sharply delineate exactly what type inference strict mode will do, and what type inference it won't. I would propose that we only allow 'var' on a variable declaration when the RHS is a literal or an instance creation expression. So for example:int i = 5;
var i = 5; => int i = 5;var s = 'foo'; => String s = 'foo';var d = 5.0; => double d = 5.0;var c = new Cow(); => Cow c = new Cow(); *var l = <int>[]; => List<int> l = <int>[]; *var m = <int, String>{}; => Map<int, String> m = <int, String>{}; *
I would add function/method calls that have a type annotation for the return typeNote: the three examples I marked with "*" above are my motivation for suggesting this. I find these patterns come up frequently, and it really annoying to have to repeat the type when it appears elsewhere on the same line.
But in the cases below, type inference would not be done, and hence the user would be required to replace "var" with the desired type:var x = [1, 2, 3]; // type is ambiguous; it could be List<int>, List<num>, or List<Object>.var x = {'foo': 'bar'}; // also ambiguous; could be Map<String, String>, Map<String, Object>, etc.for these two at least `List` and `Map` could be inferred.
Except that List is short for List<dynamic> and Map is short for Map<dynamic, dynamic>. I thought we were trying to disallow dynamic everywhere, so automatically inferring a type that includes dynamic as a type parameter seems problematic.
I haven't decided how I feel about this. One of the benefits, IMHO, of strict mode is that it allows me to change a method in my code and be confident that warnings and hints will lead me to all of the other code that's potentially affected by that change. For example, let's say I change the return type of blah(). Under Paul Jolly's definition of strict mode, nearly* all uses of blah() would immediately be flagged, and then I could go through them one by one to see if they merely needed to have their type annotation changed or if they needed to be more extensively reworked. However, if we allow "var x = blah();" to do type inference based on the return type of blah(), then I'd lose that benefit; I would only get a warning/hint if some later code happened to use x in a way that conflicts with the new type. Which means I would miss out on some important opportunities to avoid making mistakes. Also, any warnings/hints I did get would be located at the site where x was used rather than at the site of the call to blah(), so it would require more mental effort to figure out what needed to be changed.*Of course, some uses of blah() would still not be flagged, e.g. 'blah().foo' would only be flagged if the new type lacked a member called 'foo'.This sort of process (make a change and then chase down warnings/hints to make sure I've addressed all affected code) was a really useful part of my workflow back when I was programming in C++ and C#, and I've been frustrated by the fact that I can't do this in Dart. So that's the reason I didn't want to do type inference on 'var x = blah();'--I was looking for a way to reduce the amount of redundant typing I'd have to do in strict mode without weakening that workflow.
PaulB
But according to the spec, use of var is equivalent to dynamic. So someone unaware of this special 'strict' analysis rule (but yet subject to it, perhaps as part of a team decision) would perhaps be understandably confused.
Don't get me wrong, I would much prefer the shorter versions you all propose. But, acting as devil's advocate, are we getting too close to the spec here?I'll grant you that someone might certainly find this confusing. In fact I'm glad you brought that up because I think it's a good way to think about the tradeoffs involved in my proposal: I'm proposing a change that allows the user to reduce annoying redundancy at the expense of some additional risk of confusion. IMHO it's worth it, but I certainly understand if someone else feels otherwise :)
But in the cases below, type inference would not be done, and hence the user would be required to replace "var" with the desired type:var x = [1, 2, 3]; // type is ambiguous; it could be List<int>, List<num>, or List<Object>.Indeed. According to the proposal this would generate a strict type hint because a type annotation is missing on the RHS.var x = {'foo': 'bar'}; // also ambiguous; could be Map<String, String>, Map<String, Object>, etc.Ditto.var x = 3 + 5; // RHS is neither a literal nor an instance creation expression.Is the RHS not a compile time constant here?Correct. But my proposal was to do type inference only for literals and instance creation expressions, not necessarily for all compile time constants.You might be able to talk me into allowing type inference for compile time constants as well, but the downside is that it forces more complexity on analysis (there are some odd corner cases in the evaluation of compile time constants in the analyzer today, and I worry that allowing types to be inferred from compile time constants would make those corner cases even worse). My bet is that this sort of declaration occurs rarely enough in practice that it wouldn't really benefit enough people to be worth the added complexity.
var x = blah(); // RHS is neither a literal nor an instance creation expression.But if blah() has a return type annotation this is surely permissible?I haven't decided how I feel about this. One of the benefits, IMHO, of strict mode is that it allows me to change a method in my code and be confident that warnings and hints will lead me to all of the other code that's potentially affected by that change. For example, let's say I change the return type of blah(). Under Paul Jolly's definition of strict mode, nearly* all uses of blah() would immediately be flagged, and then I could go through them one by one to see if they merely needed to have their type annotation changed or if they needed to be more extensively reworked. However, if we allow "var x = blah();" to do type inference based on the return type of blah(), then I'd lose that benefit; I would only get a warning/hint if some later code happened to use x in a way that conflicts with the new type. Which means I would miss out on some important opportunities to avoid making mistakes. Also, any warnings/hints I did get would be located at the site where x was used rather than at the site of the call to blah(), so it would require more mental effort to figure out what needed to be changed.*Of course, some uses of blah() would still not be flagged, e.g. 'blah().foo' would only be flagged if the new type lacked a member called 'foo'.
This sort of process (make a change and then chase down warnings/hints to make sure I've addressed all affected code) was a really useful part of my workflow back when I was programming in C++ and C#, and I've been frustrated by the fact that I can't do this in Dart. So that's the reason I didn't want to do type inference on 'var x = blah();'--I was looking for a way to reduce the amount of redundant typing I'd have to do in strict mode without weakening that workflow.
Note: the three examples I marked with "*" above are my motivation for suggesting this. I find these patterns come up frequently, and it really annoying to have to repeat the type when it appears elsewhere on the same line.
But in the cases below, type inference would not be done, and hence the user would be required to replace "var" with the desired type:var x = [1, 2, 3]; // type is ambiguous; it could be List<int>, List<num>, or List<Object>.var x = {'foo': 'bar'}; // also ambiguous; could be Map<String, String>, Map<String, Object>, etc.for these two at least `List` and `Map` could be inferred.
Except that List is short for List<dynamic> and Map is short for Map<dynamic, dynamic>. I thought we were trying to disallow dynamic everywhere, so automatically inferring a type that includes dynamic as a type parameter seems problematic.I wouldn't disallow dynamic.
I would only produce a hint when a value is used in a way that can not be verified using the provided type annotations or inferred types.forvar x = {'foo': 'bar'};
I would expect a hint when I dox["foo"].moo();because it's not known if x["foo"] provides this method
Whether x["foo"] should already produce a hint depends on what type exactly should be derived from {'foo': 'bar'}
Maybe strict should require generic type annotations for instance creation.
int x=0;always equivalent in strict mode to1. var x=0;How about keeping things simple, and relying on inferred type whenever variable is initialized?That is,
List<dynamic> x=[1,2,3]2. var x=[1,2,3];is equivalent to
Etc.Rule SE1 should be fixed by excluding these cases.
BTW, at least to my taste, this is better than golang's special syntax with :=
> I'm proposing that we do not permit dynamic for strict analysis; Object should be used where required
I would agree with that if instead of Object you require *explicit* dynamic.
That isvar x=[1,2,3]; // produces a warningvar y=<dynamic>[1,2,3]; // no warning
I have a feeling that Object as universal substitute is persona not grata in dart, so your proposal will have greater chance to succeed if you don't insist on Object.
I have been wondering about this too.On 14 August 2014 10:15, Alex Tatumizer <tatu...@gmail.com> wrote:
> I'm proposing that we do not permit dynamic for strict analysis; Object should be used where required
I would agree with that if instead of Object you require *explicit* dynamic.
That isvar x=[1,2,3]; // produces a warningvar y=<dynamic>[1,2,3]; // no warningI have a feeling that Object as universal substitute is persona not grata in dart, so your proposal will have greater chance to succeed if you don't insist on Object.
Concerning "dynamic" vs "Object" - the official position is something like this: Object tells you that it can be ANY type.
Dynamic doesn't carry this "any" meaning. It's often used when you mean it can be either Foo or Bar, and nothing else. This is not equivalent to saying that it can be just anything. There's a subtle distinction.
I have been wondering about this too.On 14 August 2014 10:15, Alex Tatumizer <tatu...@gmail.com> wrote:
> I'm proposing that we do not permit dynamic for strict analysis; Object should be used where required
I would agree with that if instead of Object you require *explicit* dynamic.
That isvar x=[1,2,3]; // produces a warningvar y=<dynamic>[1,2,3]; // no warningI have a feeling that Object as universal substitute is persona not grata in dart, so your proposal will have greater chance to succeed if you don't insist on Object.Certainly happy to defer on this point - I am relatively new to Dart.Also if you feel things might be more readily adopted/understand by leaving it in then all the better.
Critically (off the top of my head), I don't think allowing dynamic as a type annotation changes the semantics of what's proposed, i.e. a dynamic type annotation can appear anywhere that Object can appear. Unless I'm missing a corner case?
So very happy to modify the proposal to reflect this and the optionality of the LHS type annotations for variable declarations.
--
Certainly happy to defer on this point - I am relatively new to Dart.Also if you feel things might be more readily adopted/understand by leaving it in then all the better.
Critically (off the top of my head), I don't think allowing dynamic as a type annotation changes the semantics of what's proposed, i.e. a dynamic type annotation can appear anywhere that Object can appear. Unless I'm missing a corner case?Object means that the only thing you know is that it's an Object - the tools warn on any call that's not defined in Object.dynamic means that what you know isn't describable by the type system - the tools will not warn about any calls.
What's the distinction between the grey and yellow listings in the 'Problems' pane?Given your comments Justin, I'm guessing the yellow entry is a static warning per the spec but the grey entry is the result of some additional, non-spec specified analysis and hence is a hint or some such?
Would the packages shipped by the Dart team be updated to comply with strict mode.We would need someone from Google/Dart team to comment on that: I don't fall into either category!
I don't see much value here for myself either. The analyzer already does type inference to offer help in places where this document asserts that the tools can't - it gives hints, suggests code completions, and performs refactoring based on inferred types. Also, some of the code in the document doesn't give a static warning, but doesn't give a runtime error either. The soundness of the type system was relaxed (as I understand it) exactly because that happens and we don't want to force casts.What I personally want out of analysis tools seems to be a bit different than what I've seen suggested so far: mainly enforcing or materializing what type inferencing already determines. I want the analyzer to tell me when it's having problems inferring types, so that I know to either refactor my code or help out by adding select type annotations (or file a bug on the analyzer!). I specifically don't want to add types everywhere.
> I want the analyzer to tell me when it's having problems inferring typesAny example of situation where analyzer has difficulty inferring types right now (excluding cases of uninitialized var)?
The Dart style guide explicitly discourages type annotating local variables, so it's unlikely that we'd be changing that.I don't personally see any value in adding more type annotations to my code. As far as I'm concerned a smarter type checker should do more work for me, not make me do more work for it.- bob
The Dart style guide explicitly discourages type annotating local variables, so it's unlikely that we'd be changing that.
I don't see much value here for myself either. The analyzer already does type inference to offer help in places where this document asserts that the tools can't - it gives hints, suggests code completions, and performs refactoring based on inferred types. Also, some of the code in the document doesn't give a static warning, but doesn't give a runtime error either. The soundness of the type system was relaxed (as I understand it) exactly because that happens and we don't want to force casts.What I personally want out of analysis tools seems to be a bit different than what I've seen suggested so far: mainly enforcing or materializing what type inferencing already determines. I want the analyzer to tell me when it's having problems inferring types, so that I know to either refactor my code or help out by adding select type annotations (or file a bug on the analyzer!). I specifically don't want to add types everywhere.
> This was in relation to the topic mentioned below (CI/dependencies).I don't speak for the Dart team, but personally I'd not use strict
> Would the packages shipped by the Dart team be updated to comply with strict
> mode.
mode as defined here, and I would argue against making it policy. It's
too strict.
I personally tend to add type annotations everywhere, even on local
variables, because I rely on language warnings to catch my typos (but
a go-like "x:=foo;" declaration would be great!).
What tools does Google use for checking/linting Dart code against the style guide? Not necessarily enforcing, but at least checking to see what 'violations' exist.
class A {String doA() => "test";}class B extends A {int doB() => 5;}void main() {var a = new A();var b = new B();b.doB();b = a;b.doB(); // [1]}No static warning/hint is given at the moment.
On Fri, Aug 15, 2014 at 3:20 PM, Paul Jolly <pa...@myitcv.org.uk> wrote:No. Missing annotations kind of stand out to me, to the point where I
>> I personally tend to add type annotations everywhere, even on local
>> variables, because I rely on language warnings to catch my typos (but
>> a go-like "x:=foo;" declaration would be great!).
>
>
> I do the same. Hence my kicking off this proposal.
>
> Do you have a tool to help check for the absence of type annotations?
preferred writing "dynamic" to not writing a return type (but that was
too far from the style guide, I've promised not to do that again :)
I'm probably uncharacteristic here because I write mainly in the core
> How do you handle situations where you use packages that don't use type
> annotations?
libraries, which can't use any packages, or on packages built just on
top of the core library.
If I use a package without type annotations, I'll 1) curse a lot, and
2) assign return values to variables with types, so I can see what I'm
doing. Most of the time it isn't really a problem - the interface
between my code and another package's code is likely to be small and
clearly abstracted. Meaning that I also 3) abstract any foreign code
into as few of my own functions as possible, with appropriate names
from my domain, and with types :)
/L
--
Lasse R.H. Nielsen - l...@google.com
'Faith without judgement merely degrades the spirit divine'
Google Denmark ApS - Frederiksborggade 20B, 1 sal - 1360 København K -
Denmark - CVR nr. 28 86 69 84
I haven't. Explicitly declaring the return type, even as void or dynamic, enables the editor to be nice and tell you when you forgot to return a value. I think the style guide needs to evolve with the tools here.
Hints in the analyzer have come a long way. If you haven't relied on them in a while, you may be surprised how often "var" works just as well for this.I personally tend to write lots of mistakes in my code and the analyzer does a great job of helping me find them even when I don't annotate locals.
I haven't. Explicitly declaring the return type, even as void or dynamic, enables the editor to be nice and tell you when you forgot to return a value. I think the style guide needs to evolve with the tools here.
One of the things analyzer can't do is tell the difference between a dynamic variable that is expected to change its type and one that is not. Similar to the previous example that was given:main() {var a = 'One';a = 2; // Is this intentional?}Analyzer can't know whether whether the commented line is a problem. Providing a type annotation would solve that, but at a cost.
This example shows that type inference cannot be trusted. This is a major drawback.
Bob, when you see the line var a = 'One', what is your best guess: is this a String, or we don't know?
That'd be better than some ugly @suppresswarnings syntax.
It could be like the current int division, var a = 'bla';
a = 1; // nok
a ~= 1; // ok
var obj='123';1+obj; // ok - why?obj+1; // ok - why?
obj.foo; // hint (barely visible) "there's no such getter 'foo' in String"
'123'.foo; // warning: "there's no such getter 'foo' in String"
obj as int; // ok - why?'123' as int; // ok, too.
Because of various subtleties, it's hard to know whether something is a bug in analyzer or not.
I know, warnings are defined in the spec, and hints are not. The problem, however, is that no one (except maybe Gilad) knows spec by heart, and things are not very intuitive to guess what should be there. and whether something is a bug in analyzer, or bug in spec, or in the program, or in the head.
But: if var o="abc" is CERTAINLY a String, then o.foo is CERTAINLY a bug. What difference does it make from user's viewpoint that something is defined in the spec, and something is not?
Maybe spec should provide a loophole for analyzer allowing warnings in places where bugs are obvious?
But the root cause IMO is this: spec pays not enough attention to type inference, which could be promoted much higher. The problems above are due to the fact that type inference is not trusted by the spec, so var o='foo', though obviously String, is treated rather as kind of "maybe String".
The language tries to walk a fine line between two sets of users. On the one hand, there are users who want the tools to be able to use all of the power of a type system to help catch bugs during development. On the other hand, there are users who want to be able to write code quickly and don't want the tools to impose unnecessary restrictions or require them to write unnecessary code (for example, see the discussions about having to write a type annotation on the left-hand-side when the variable initializer consists of an instance creation expression).
... On the other hand, there are users who want to be able to write code quickly and don't want the tools to impose unnecessary restrictions or require them to write unnecessary code ......
Do you really think that there are people out there which are offended by hints produced by the static analyzer and accept only runtime errors as indication that their code doesn't work?
int f(Object x) {if (x is String) {return x.length; // warning: There is no such getter 'length' in 'Object'}return 0;}
int f(Object x) {if (x is! String) {return 0;}return x.length; // warning: There is no such getter 'length' in 'Object'}
In my opinion more in-depth analysis and more hints would improve the experience for everyone.
I just want to mention one particular issue that didn't receive enough attention: refactoring.
Not sure how refactoring algo works, but it looks like it doesn't fully utilize inferred types either. Which brings it into confrontation with style guide.
...
This particular example works fine with current refactoring implementation, but I had ones that didn't (can't remember particular scenarios).
Filing a bug would be easier if refactoring tool printed an explanation as to WHY it is in doubt and wants a dialog. Then I could say exactly whose fault it is.
Additionally, a good experiment might be to take pub (written reportedly in accordance with style guide) and try to randomly rename just everything and see whether it works and where it breaks.
final s = 'One';
s = 2; final Future<int> foo = new Future.value("One"); // no hint :-(
final Iterable<int> fum = [1].map((i) => '$i'); // no hint :-(
final Stream<int> bar = new Stream.fromIterable(['s']); // no hint :-(
> Do you have a tool to help check for the absence of type annotations?No. Missing annotations kind of stand out to me, to the point where I
preferred writing "dynamic" to not writing a return type (but that was
too far from the style guide, I've promised not to do that again :)
> How do you handle situations where you use packages that don't use typeI'm probably uncharacteristic here because I write mainly in the core
> annotations?
libraries, which can't use any packages, or on packages built just on
top of the core library.
If I use a package without type annotations, I'll 1) curse a lot, and
2) assign return values to variables with types, so I can see what I'm
doing. Most of the time it isn't really a problem - the interface
between my code and another package's code is likely to be small and
clearly abstracted. Meaning that I also 3) abstract any foreign code
into as few of my own functions as possible, with appropriate names
from my domain, and with types :)
Personally, I think static analysis could be better here. It's possible in many cases to do simple flow analysis and handle variables changing their type and would handle this.class A {String doA() => "test";}class B extends A {int doB() => 5;}void main() {var a = new A();var b = new B();b.doB();b = a;b.doB(); // [1]}No static warning/hint is given at the moment.
In practice, though, users rarely change the type of value assigned to a variable like you do here.
I haven't. Explicitly declaring the return type, even as void or dynamic, enables the editor to be nice and tell you when you forgot to return a value. I think the style guide needs to evolve with the tools here.
+1The same for type annotations on local variables.When the analyzer can't infer the type the annotation should be allowed.
I personally tend to write lots of mistakes in my code and the analyzer does a great job of helping me find them even when I don't annotate locals.
One of the things analyzer can't do is tell the difference between a dynamic variable that is expected to change its type and one that is not. Similar to the previous example that was given:main() {var a = 'One';a = 2; // Is this intentional?}Analyzer can't know whether whether the commented line is a problem. Providing a type annotation would solve that, but at a cost.
Since Dart is a dynamically typed language and variables can change type, I think the analyzer pretty much has to allow the above code. Instead, I really like what it does now:One of the things analyzer can't do is tell the difference between a dynamic variable that is expected to change its type and one that is not. Similar to the previous example that was given:main() {var a = 'One';a = 2; // Is this intentional?}Analyzer can't know whether whether the commented line is a problem. Providing a type annotation would solve that, but at a cost.var a = 'One';a = 2;a.length; // Warning, int has no length getter.
This example shows that type inference cannot be trusted. This is a major drawback.
Bob, when you see the line var a = 'One', what is your best guess: is this a String, or we don't know? In 99.9% cases, it's a String, and never be assigned anything else. However, remaining 0.1% is treated as default by a language. There should a way to utilize type inference, without the need to explicitly declare String a = 'One'
(replying off list because I would be repeating myself too much :)
But the analyzer, rightly enough, must adhere to the specification. So sometimes, as unhelpful as it might be to our users, the only thing I can tell you is why he tool is doing things the way it is.The language tries to walk a fine line between two sets of users. On the one hand, there are users who want the tools to be able to use all of the power of a type system to help catch bugs during development. On the other hand, there are users who want to be able to write code quickly and don't want the tools to impose unnecessary restrictions or require them to write unnecessary code (for example, see the discussions about having to write a type annotation on the left-hand-side when the variable initializer consists of an instance creation expression).
But that opens up a hard question (one of the questions being wrestling with on this thread): what should happen when you mix those two styles? The language generally takes the approach that there shouldn't be an error or warning unless it's always going to be a real problem. That implies that some code containing problems will not produce errors or warnings.
I haven't. Explicitly declaring the return type, even as void or dynamic, enables the editor to be nice and tell you when you forgot to return a value. I think the style guide needs to evolve with the tools here.+1The same for type annotations on local variables.When the analyzer can't infer the type the annotation should be allowed.I might be missing something here, but when can the analyzer know that it has been unsuccessful in inferring a type?
Surely the spec precludes there being such a situation?And we have to include dynamic here because the spec is specific on when dynamic is inferred. For example if I write var i = 2 then the analyzer can't tell me that's wrong (even with a hint) because I could well have intended the type of i to be dynamic.
What examples do you have in mind?
I don't think anyone is offended when the tools provide valid feedback related to problems in the code, but there are plenty of people who are offended when the tools are not as smart as they expect them to be.
The same for type annotations on local variables.When the analyzer can't infer the type the annotation should be allowed.I might be missing something here, but when can the analyzer know that it has been unsuccessful in inferring a type?When dynamic is inferred then there is no specific type information.
Surely the spec precludes there being such a situation?And we have to include dynamic here because the spec is specific on when dynamic is inferred. For example if I write var i = 2 then the analyzer can't tell me that's wrong (even with a hint) because I could well have intended the type of i to be dynamic.with var i = 2 is nothing wrong. It should be treated as integer.
What examples do you have in mind?someMethod(x) {if(x) {return "foo";} else {return 12.34;}}var i = someMethod(y);
The same for type annotations on local variables.When the analyzer can't infer the type the annotation should be allowed.I might be missing something here, but when can the analyzer know that it has been unsuccessful in inferring a type?When dynamic is inferred then there is no specific type information.But the spec allows for dynamic to be inferred. How can you distinguish between a situation when I intended this and one where I didn't?
And what about if I deliberately use dynamic?This is why I proposed the use of Object in the original version of the 'strict' proposal, to help distinguish.Surely the spec precludes there being such a situation?And we have to include dynamic here because the spec is specific on when dynamic is inferred. For example if I write var i = 2 then the analyzer can't tell me that's wrong (even with a hint) because I could well have intended the type of i to be dynamic.with var i = 2 is nothing wrong. It should be treated as integer.Per the relaxed version of SE1 it would be treated as an integer so I agree in the context of the 'strict' proposal.What examples do you have in mind?someMethod(x) {if(x) {return "foo";} else {return 12.34;}}var i = someMethod(y);What is the return type of this method? I would suggest it is Object.
The language tries to walk a fine line between two sets of users. On the one hand, there are users who want the tools to be able to use all of the power of a type system to help catch bugs during development. On the other hand, there are users who want to be able to write code quickly and don't want the tools to impose unnecessary restrictions or require them to write unnecessary code (for example, see the discussions about having to write a type annotation on the left-hand-side when the variable initializer consists of an instance creation expression).