-
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
Allow if-scoped variable declarations to help address field promotion #1991
Comments
if (var field = myNullableField; field == null) {
return null;
}
// use `field` as non-null here. You can do that for local variables, because they are also in scope after the |
I don't think there's a real problem here: If you want to use the variable after the end of the var field = myNullableField;
if (field == null) return null;
// use `field` as non-null here. This kind of scoping is unsurprising, and the "more global" declaration of I just changed #1210 to restrict the scope of variables introduced by a binding expression to the nearest |
This might be viewed as a benefit: it means that putting the variable declaration inside the if-statement also expresses that the variable is only in scope inside that if-statement. |
I really wish we had something simpler instead of full declaring the variable. if (obj.prop != null) { At this moment I think "I have to declare prop to use it". if (final prop = obj.prop; prop != null) { Instead of: final prop = obj.prop;
if (prop != null) { ? The only place I see myself using this, in the current state, is in List/Map declaration. We need something that makes sense for using the declaration inside the At least our team aways tend to make |
In our team local |
I don't write |
We also didn't like |
The analysis server already produces different highlight region types for IntelliJ. It appears that by default the colors are the same, but users can adjust the color for any region type (Editor / Color Scheme / Dart). For LSP-based clients the analysis server produces a |
On the brevity of variables with a short scope: I often read and write Dart code with single-letter variables in for-each loops, and closures: for (var e in someIterator) { ... }
someIterator.forEach((e) { ... }); but I almost never use single-letter variable names for anything else (other parameters, other local variables, declared in a variable declaration statement, fields). So perhaps developers would lean towards using single-letter if-variables in this way, which would be another benefit to the if-variable over a preceding variable declaration statement. if (var p = obj.propertyNameWhichIsLong; p != null) {
p.methodName(p.prop1, p.prop2);
// more uses of p, without `p!`.
} |
I think single-character variables are underused in Dart. The "avoid abbreviations" style guide makes them hard to argue for, but in many cases, a small scope and a clear declaration makes longer variable names unnecessary. for (var p in properties) {
something(p);
p.other;
} If the body of the loop is short, every reader will remember what I'd probably not allow var x = e1;
var y = e2; instead, it takes two lines, but it also means the variable names are aligned and next to a |
It's an entirely tractable feature, but I just can't convince myself that it adds much value. All it really does is slightly reduce the scope of the local variable. But function bodies tend to be fairly short in Dart anyway, so this is a marginal benefit. If we didn't already have control flow based type promotion, it would be powerful. But as it is... meh. |
for-in loops provide a lot of value because what they desugar to is fairly complex, verbose, and easy to get wrong. C-style for loops are only somewhat marginal... but they are already in the heads of millions of users, which is itself a large benefit. |
C-style for loops actually interferes with my idea to allow Maybe #171's |
Personally I would rather see the variable added to the enclosing scope, because it would allow usage patterns like this: if (var x = obj.x; x == null) {
return;
}
// x is in scope here In fact, this feature could be trivially generalized to allow the form if (x is! int || (var y = x + 1; y < 0)) {
// Note: y is in scope here but it can't be used because it's not definitely assigned
return;
}
// y is in scope here and can be used and would behave equivalently to: int y;
if (x is! int || (y = x + 1) < 0) {
// Note: y is in scope here but it can't be used because it's not definitely assigned
return;
}
// y is in scope here and can be used |
I like the idea of having It's very, very close to allowing Technically, inside the |
Agreed, I don't think re-using definite assignment here does what you want (consider also the case that the type is nullable, and hence not required to be definitely assigned).
Do you mean any sub-expression anywhere (i.e. this proposal) or just inside of
meh. That code is 10x more readable if written as follows if (x is! int) return;
var y = x + 1;
if (y < 0)) return;
// y is in scope here and can be used |
@tatumizer, you have to be more specific—local variables will be promoted just fine in the example as shown: void use(int i) {}
void main() {
int? prop = 1 as dynamic; // Ensure that `prop` has type `int?`.
if (prop != null) {
use(prop); // Promotion - no error
}
} The variables that aren't promoted are non-local ones, and we have spent all of 'field-promotion' on debating how to make promotion of non-local variables available in some way (statically checked or dynamically checked, cached or non-cached, etc). In this case the type of the variable One of the proposals is concerned with an "optimistic" approach, and that one could be said to use a specialized type: When promotion is done optimistically, a promoted instance variable However, we do have a few cases where there's no good type for a promotion. For example, consider a variable Of course, the pragmatic advice in this case would be to promote We did discuss the idea that we'd have a "non-nullable operator" on types, but we decided that the complexity of handling such an operator wasn't worth the small improvement that it would be expected to provide. |
OK, very good! Note that it is a double-edged sword, though, because it looks nice, but it introduces a (potentially large) number of dynamic checks, and most of the recent changes to Dart have been aimed at eliminating dynamic checks. |
@tatumizer wrote:
I think the optimistic approach is a perfectly consistent and usable proposal. It also represents a particular language design trade-off, allowing for a potentially large number of dynamic checks with no visible syntax, and this might cause the proposal to get strong support or ditto opposition. So we could do that, but the debate about it is perhaps at the software engineering level rather than the technical level ("do we want to accept those dynamic checks?"). The approaches that are based on evaluating something (in particular, an instance variable, but it could be any expression) and storing the value in a local variable are safe at the immediate level (there are no implicit dynamic checks). However, these approaches may give rise to subtle bugs because they rely on caching a value, and the behavior of the enclosing application may be such that the cached value is stale. So we could do that as well, and it would even make sense to do both, if it's considered appropriate to have support for so much language mechanism in this topic area. Finally, we have an approach like #1518 where immutability is turned into an interface property (that is, we make it a part of the contract of a particular getter in an interface that it is stable, and it is then enforced by the language that this contract is upheld by all implementations). This approach is properly safe (no dynamic checks, and no values can be stale), but it can only be applied in the case where it is actually an appropriate contract that a specific property should be immutable.
|
Now, we can write |
@Cat-sushi Awesome! However I think the correct syntax is I really need to check all pattern types available😁, I was missing some. |
@Cat-sushi is exactly right, and null check patterns were added in large part to support this idiom. The syntax is a pretty unintuitive but once you learn it, I think it works OK. At least, we never were able to come up with anything better. Swift has something similar, which is where I got it from, and is a signal that it's useful and works. But the fact that the top search result for that syntax is https://fuckingifcaseletsyntax.com/ does not inspire confidence that the users find the syntax intuitive. :) |
Breaking out a proposal from discussion in this issue, we could consider adding the ability to declare a variable in the condition of an
if
statement which is scoped to theif
and theelse
branch:As usual, you can replace
var
withfinal
.Related features
Go has a version of this, as does C#.
Questions
Should you be able to declare multiple variables?
Possible extensions
@lrhn proposed a generalization of this in which we introduce a general
let
syntax usingvar x = e1; e2
wherex
is bound ine2
, and then further specify that in a few specific constructs (if
statements at least) we extrude the scope into the subsequent blocks.Discussion
This is a fairly minimal change. It doesn't really eliminate any verbosity, and requires you to explicitly write the name of the variable twice:
There is nonetheless a general consensus in discussions that moving the variable binding into the
if
statement is materially better than writing it as a standalone statement before hand: the latter feels somehow more ad hoc.This proposal doesn't help with the
guard-let
use case where a negative check is done and the body of theif
throws. Possibly we could allow the variable to be in scope in the continuation even when the failure continuation is the enclosing block to handle this case? Or possibly only when the failure continuation is the enclosing block and the success continuation never completes (i.e. always either returns or throws)?cc @munificent @lrhn @eernstg @jakemac53 @natebosch @stereotype441
The text was updated successfully, but these errors were encountered: