-
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
Null Safety: Sound non-nullable types with incremental migration (NNBD) #110
Comments
I've added a draft of the roadmap for this proposal, linked from the header comment. |
@jmesserly I'm not sure how to constructively move the NNBD discussion from #125 here, because the my suggestion is all about linking the two issues. But I'll try: I suggest that, as a first step, we introduce sound non-nullable types as part of immutable types (#125). This has several advantages:
|
IMO, linking the two proposals would be counterproductive. We are working hard on NNBD, there is a lot of design & planning work going on. There aren't any major open questions on syntax or desired semantics. Linking the two would prevent us from pushing forward on NNBD, because immutable types has a lot of open design questions, and we'd have to delay work on NNBD until that's resolved. (Also it could be confusing if different parts of the language treat |
That's fine too, it was just an idea. I don't have enough context to know how best to proceed, so if you believe that NNBD and immutables should proceed completely independently, then they should :) |
Makes sense :). Yeah, we seem to be gathering a lot of momentum behind Leaf's proposal. It's really exciting! I think we'll be able to apply the many lessons of Strong Mode to NNBD, to make the migration as easy & rewarding as possible. (e.g. ideally it's non-breaking for code that isn't migrated. Until you turn on NNBD, you don't break. Once you turn it on, you have to fix some errors, but you gain some degree of null safety, even if everything else isn't migrated yet. Once everything in an app is migrated, then it has sound null safety. It's pretty neat.) |
... which in this CL will always null. This is the first step when adding NNDB support as outlined in dart-lang/language#110 Change-Id: If3810bcaf1b73e70924f09d619e2a84e7d5ba8d6 Reviewed-on: https://dart-review.googlesource.com/c/86860 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Dan Rubel <[email protected]>
This adds code to ignore trailing `?` in `as` and `is` expressions because the trailing `?` may be part of the larger expression. At the moment, this change is a no-op, but will become active when a subsequent CL lands support for nullable types as part of dart-lang/language#110 Change-Id: I829d6aee0f11957ca9b5e143000005031649449f Reviewed-on: https://dart-review.googlesource.com/c/86960 Reviewed-by: Brian Wilkerson <[email protected]>
... as part of adding NNBD as outlined in dart-lang/language#110 This only supports parsing simple nullable types such as int? and List<int>? while subsequent CLs will add support for parsing more complex types. Change-Id: I3cc5c65d20bf3732a39cab0e823b2f7332342866 Reviewed-on: https://dart-review.googlesource.com/c/86961 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Dan Rubel <[email protected]>
... which in this CL will always null. This is another step when adding NNDB support as outlined in dart-lang/language#110 Change-Id: I3974fcb885c63be4af9d1007258383f3f8191ae0 Reviewed-on: https://dart-review.googlesource.com/c/87080 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Dan Rubel <[email protected]>
…tion type This only supports nullable return values of the form <identifier> '?' 'Function' '(' ... This is an increment CL in the ongoing effort to add nullable type support as outlined in dart-lang/language#110 Change-Id: I42febae9f88f7e4d8b05907988deab97c7a7425c Reviewed-on: https://dart-review.googlesource.com/c/87081 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Dan Rubel <[email protected]>
This adds support for nullable types of the form <identifier> '.' <identifier> '?' and <identifier> '.' <identifier> '?' 'Function' '(' ... This is an increment CL in the ongoing effort to add nullable type support as outlined in dart-lang/language#110 Change-Id: I526aecbe64bacbd442cea0b4c52d36ff23b0443b Reviewed-on: https://dart-review.googlesource.com/c/87083 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Dan Rubel <[email protected]>
This reverts commit 7720689. Reason for revert: Breaks parsing less common conditionals (e.g. b ? c = true : g();) Original change's description: > Add support for prefixed nullable type > > This adds support for nullable types of the form > > <identifier> '.' <identifier> '?' > > and > > <identifier> '.' <identifier> '?' 'Function' '(' ... > > This is an increment CL in the ongoing effort to add nullable type support > as outlined in dart-lang/language#110 > > Change-Id: I526aecbe64bacbd442cea0b4c52d36ff23b0443b > Reviewed-on: https://dart-review.googlesource.com/c/87083 > Reviewed-by: Brian Wilkerson <[email protected]> > Commit-Queue: Dan Rubel <[email protected]> [email protected],[email protected] Change-Id: Ib5e74b4aad239f561a33eae9d95dffa2693037f7 No-Presubmit: true No-Tree-Checks: true No-Try: true Reviewed-on: https://dart-review.googlesource.com/c/87282 Reviewed-by: Dan Rubel <[email protected]> Commit-Queue: Dan Rubel <[email protected]>
…unction type This reverts commit 11d081d Reason for revert: Breaks parsing less common conditionals (e.g. b ? c = true : g();) Original change's description: > Add support for simple nullable type return value in generalized function type > > This only supports nullable return values of the form > > <identifier> '?' 'Function' '(' ... > > This is an increment CL in the ongoing effort to add nullable type support > as outlined in dart-lang/language#110 Change-Id: I99bce29619d4e448193e3c81fa86a982791b1f77 Reviewed-on: https://dart-review.googlesource.com/c/87283 Reviewed-by: Dan Rubel <[email protected]>
This reverts commit bc8c7cf Reason for revert: Breaks parsing less common conditionals (e.g. b ? c = true : g();) Original change's description: > Add support for parsing simple nullable types > > ... as part of adding NNBD as outlined in > dart-lang/language#110 > > This only supports parsing simple nullable types > such as int? and List<int>? while subsequent CLs > will add support for parsing more complex types. Change-Id: I49a21a85dca19241e3b23ed5c9fb6084e70f2000 Reviewed-on: https://dart-review.googlesource.com/c/87284 Reviewed-by: Dan Rubel <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Dan Rubel <[email protected]>
I've updated the header with some sub-issues for discussing specific topics. |
... as part of adding NNBD as outlined in dart-lang/language#110 This only supports parsing simple nullable types such as int? and List<int>? while subsequent CLs will add support for parsing more complex types. Change-Id: I144858946cb115755af437299899c2631105bf8c Reviewed-on: https://dart-review.googlesource.com/c/87501 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Dan Rubel <[email protected]>
This reverts commit c5fd11b. Reason for revert: Conditional expression lookahead may modify the token stream Original change's description: > Add support for parsing simple nullable types > > ... as part of adding NNBD as outlined in > dart-lang/language#110 > > This only supports parsing simple nullable types > such as int? and List<int>? while subsequent CLs > will add support for parsing more complex types. > > Change-Id: I144858946cb115755af437299899c2631105bf8c > Reviewed-on: https://dart-review.googlesource.com/c/87501 > Reviewed-by: Brian Wilkerson <[email protected]> > Commit-Queue: Dan Rubel <[email protected]> [email protected],[email protected] Change-Id: Ie1f47e7384ff51159aa2ebeb21561455b8e6287f No-Presubmit: true No-Tree-Checks: true No-Try: true Reviewed-on: https://dart-review.googlesource.com/c/87620 Reviewed-by: Dan Rubel <[email protected]> Commit-Queue: Dan Rubel <[email protected]>
... as part of adding NNBD as outlined in dart-lang/language#110 This CL updates the parser to recognize two specialized comments at the beginning of a library of the form '//@NNBD' and '//@NNBD*' for use by the analyzer to aid developers when converting their libraries to be non-nullable by default. In addition, the AstBuilder now populates the generated analyzer AST with nullable type information. Change-Id: I80d221dd138973aa32f05bde631245d9ac6ee10f Reviewed-on: https://dart-review.googlesource.com/c/87540 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Dan Rubel <[email protected]>
... as part of adding NNBD as outlined in dart-lang/language#110 This only supports parsing simple nullable types such as int? and List<int>? while subsequent CLs will add support for parsing more complex types. Due to current parser limitations, it also only supports nullable types in a limited number of statements such as T? t; if (x is String?) {} In addition, address comments in * https://dart-review.googlesource.com/c/sdk/+/87880 * https://dart-review.googlesource.com/c/sdk/+/87881 Change-Id: Ife4afadc0b72906fc0c4e9b1977ad838041c2a84 Reviewed-on: https://dart-review.googlesource.com/c/87920 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Dan Rubel <[email protected]>
Added links to two new discussion issues in the header comment. |
I don't think it entirely solves the problem. How do you use if-vars for code like this?
|
The author of that proposal included a "negative if-var", which covers exactly that case. It would look exactly like you wrote: if (var field == null) {
throw "Error, not allowed to be null";
}
var x = field.foo; The gist is that when checking to see if an if-var is null (or when using |
Honestly, I think the language should be designed in a way to avoid using the If devs are forced into using This is why I wrote a linter for a bigger TypeScript project that disallowed the usage of |
Yeah that is confusing. And
I'd say you wasted some time writing a linter! In any case this isn't a problem in Typescript because Typescript's null checking isn't sound. They just ignore this problem. For example the following code type checks fine but will throw a type error at runtime. let i = 1;
class Foo {
public get maybeString(): string | null {
return ++i % 2 === 0 ? "Dave" : null;
}
}
function maybeStringLen(f: Foo): number {
if (f.maybeString === null) {
return 0;
}
return f.maybeString.length;
}
let x = maybeStringLen(new Foo()); |
I really think that this is a case for a new local variable: final notNullField = field;
if (notNullField == null) {
throw ...
}
var x = notNullField.foo; I am also in favor for simple type and non-null scoped var: // not null scoped var
if (field != null use notNullField) {
var x = notNullField.foo;
}
// type scoped var
if (field is SomeClass use fieldAsSomeClass) {
print(fieldAsSomeClass.foo);
} the semantics don't bother me as long as I have some simple and intuitive as above |
But that is what we have to do at the moment. The point of this thread is to find better solutions.
Another case if-vars don't solve is multiple chained conditions. E.g. in Typescript you can easily do this: if (field !== null && field.subfield !== null && field.subfield.subsubfield !== null) {
doStuff(field.subfield.subsubfield);
} Would you advocate this? if (field != null use a && a.subfield != null use b && b.subsubfield != null use c) {
doStuff(c);
} Surely we can do better than that! |
TS is a great language and a very good evolution of JS stuff, but I don't think we should try to strictly follow it. your example could be simple as: if (field?.subfield?.subsubfield != null use c) {
doStuff(c);
} |
And with if-vars (positive if-vars -- the thread seems to agree that negative if-vars are too messy for the moment), that would be: if (var field?.subfield?.subfield != null) {
doStuff(subfield);
} Also,
I just noticed that all 14 tasks at the top of this issue are complete. (Well, one is not checked off yet but the issue it's linked to is closed.) Does that mean this issue is ready to be closed? Oh how far it's come. |
At least, for private final fields, it should be sound to propagate the null-safety: class Foo {
...
final Bar? _bar;
...
void baz() {
if (_bar != null) {
_bar.somemethod(); <-- is always safe here
}
}
} |
and if not overridden in any other class in the same file/part |
I know, it's not perfect. But, I am currently migrating a bigger Flutter project with 150k Dart LOC. The most annoying piece are exactly these constructs where you have final nullable fields. Typical pattern is a widget wrapping some arguments: class Bla extends StatelessWidget {
Bla({this.text});
final String? text;
@override
Widget build(BuildContext) => text == null ? SizedBox.shrink() : Text(text);
} |
I agree. |
Sealed classes or structures would make this a lot less painful The problem exists because of inheritance. By being able to store state in an inheritance-free manner, the type-inference could be much better In the end it isn't just about null-testing here. All kinds of testing suffer from the same issue, such as: final Object something
...
if (something is MyClass) {
(something as MyClass).doSomething();
} |
While migrating a 150k project, I run into the following problem: ///
void foo<T>(T bar) {
switch (T) {
case String:
break;
case double:
break;
default:
throw Exception('Unsupported type $T');
}
} If |
You can do: Type _typeOf<T>() => T;
if (T == _typeOf<String?>()) That will require to not use a switch-case though |
@derolf You cannot add nullable types to the switch. Only constants can be used as switch case expressions, and there is no syntax for creating constant nullable type literals. You'll have to use a non-constant approach instead. Type typeOf<T>() => T; // Helper function.
final Type _nullableString = typeOf<String?>();
final Type _nullableDouble = typeOf<String?>();
void foo<T>(T bar) {
var type = T;
if (type == String || type == _nullableString || type == double || type == _nullableDouble) return;
throw Exception("Unsupported type $type");
} This will allow you to check against any type. I'd probably rewrite it to avoid the bool _isSubType<S, T>() => <S>[] is List<T>;
void foo<T>(T bar) {
if ((_isSubtype<T, String?>() || _isSubtype<T, double?>()) &&
!(_isSubtype<T, Null>()) {
return;
}
throw ....
} |
@rrousselGit @lrhn thanks for the hints, I am using So, I am fixing the tests now. Does anyone has an idea how to run tests from Visual Code while passing Options:
|
|
Was there talk about having something like |
Hello, when is this feature being released? |
This feature has been released in the beta channel and should land in stable shortly. |
@leafpetersen What is shortly? Few days? weeks? months? I am looking to start a new web service project in Dart. |
@olfek I suspect that the PMs would be sad if I stole their thunder, so I think I'll leave it at shortly. :) The feature has been released in a beta build, and the general cadence of stable releases is public info, so you can draw your own conclusions. That said, I would discourage you from starting a new project with null safety enabled unless you're fairly confident that you will not subsequently need to take on a dependency which is not yet null safe. We are strongly encouraging folks to migrate in "waterfall" order (dependencies first). Starting with a null safe app and then adding a non-null safe dependency will require you to jump through some hoops and switch back to unsound mode. So if you're expecting to not have many package dependencies (or you can validate that all of your expected dependencies already have null safe releases), then by all means, grab a beta-channel SDK and go for it, but otherwise I might suggest waiting until the package ecosystem has a bit more time to migrate. |
Please see this blog post for our latest update on migrating to null safety: https://medium.com/dartlang/preparing-the-dart-and-flutter-ecosystem-for-null-safety-e550ce72c010 |
As of Dart 2.12 -- launched to the stable channel today -- null safety is now available! |
What a nice thread, 2+ years of hard work! I'm enjoying Dart Null Safety, it's the future of modern languages. ;-) |
This is a solution proposal for #71. The proposal covers the intended final language design, and the language, tooling, and ecosystem support required for the migration.
Topic specific discussion issues for resolution:
!
.Object
nullable?T!
?late
await
vs!
!
binds more tightly.late
variablesThe text was updated successfully, but these errors were encountered: