Skip to content
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

Should null aware operators chain when we move to NNBD? #155

Closed
Tracked by #110
leafpetersen opened this issue Dec 22, 2018 · 8 comments
Closed
Tracked by #110

Should null aware operators chain when we move to NNBD? #155

leafpetersen opened this issue Dec 22, 2018 · 8 comments
Assignees
Labels
nnbd NNBD related issues

Comments

@leafpetersen
Copy link
Member

With NNBD, calling methods on the result of a null aware operator whatsIt?.frobIt().fiddleSticks() will always be a static error unless fiddleSticks is a method on Object.

Should we then just say that the ?. propagates? That is, that an unguarded method call on the result of a guarded method call is treated as guarded?

Arguments for:

  • It's always going to be an error otherwise, so why not just do the right thing?

Arguments against:

  • It's not very compositional: changing the code to var a = whatsIt?.frobIt(); a.fiddleSticks(); will fail.

Other issues/arguments?

@leafpetersen
Copy link
Member Author

cc @lrhn @munificent @eernstg

@munificent
Copy link
Member

I poked around a couple of languages:

  • C# and Swift short-circuit the entire method chain.

  • Groovy and CoffeeScript do not. I don't think Ruby or Kotlin do either.

Personally, I feel we should short-circuit. It is a fairly common mistake to forget to do ?. on the rest of the method chain, requiring it is never useful, and it's more verbose.

@lrhn
Copy link
Member

lrhn commented Jan 3, 2019

I want to fix that wart whether or not we get non-nullable types, but it's obviously a good time to do it since you will likely be touching null-aware code anyway.

It's not always going to be an error otherwise:

var text = x?.foo().toString();  // foo-toString or "null".

It's not safe under refactoring, but that's probably unavoidable. Most short-circuiting operations are not.
A ?. chain is similar to a cascade, it's not a general expression. You can't move things out of a cascade either.

Short-circuiting is probably even required to make non-nullable types pleasant to work with.
Take foo().bar().baz().

If foo() returns a nullable type, you need to write either foo()?.bar()... or foo()!.bar()... to be allowed to run the program. So you do.
If bar() returns a nullable type, then you will need foo()?.bar()?.baz() anyway.
If bar() returns a non-nullable type, it would be great if you could write foo()?.bar().baz(). The rule for whether you need ? is "compositional" then, it depends only on local information, not context information. Well, it is in either case, it just differs in what it considers its context.

If you want baz() to always be called, you can write it as (foo()?.bar()).baz(), so that option is still there. It will be one more places where parentheses are not just grouping.

@pschiffmann
Copy link

pschiffmann commented Jan 4, 2019

final v = RegExp('Hello, (.+)!')
    .matchAsPrefix(input)
    ?.group(1)
    ?.split(RegExp(r'\s+'))
    ?.map(capitalize)
    ?.toList()
  ?? [];

Here, only the first two methods can return actually null; once we know that the group(1) call returns a non-null result, the next two accesses are safe. So with short-circuiting would I write

final v = RegExp('Hello, (.+)!')
    .matchAsPrefix(input)
    ?.group(1)
    ?.split(RegExp(r'\s+'))
    .map(capitalize)
    .toList()
  ?? [];

or

final v = RegExp('Hello, (.+)!')
    .matchAsPrefix(input)
    ?.group(1)
    .split(RegExp(r'\s+'))
    .map(capitalize)
    .toList()
  ?? [];

@lrhn
Copy link
Member

lrhn commented Jan 4, 2019

@pschiffmann
Yes, with short-circuiting you would use the former of the last two examples. Since group(1) can return null if the first capture group is not participating, you will need a ?. after that as well, but split and map cannot return null (and with non-nullable types they will return a non-nullable type to make that clear).

Short-circuiting is a good idea independently of non-nullable types.
With non-nullable types, you will need to use ?. on any expression with a static type that is nullable, unlike now where you can omit it and just get a run-time error if the value happens to be null.

@leafpetersen
Copy link
Member Author

Decided: we will do this.

@munificent
Copy link
Member

Woo!

@leafpetersen
Copy link
Member Author

Closing this, since we've made a decision.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
nnbd NNBD related issues
Projects
None yet
Development

No branches or pull requests

4 participants