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

Optional Chaining not working as expected in combination with nullish coalescing operator #54825

Closed
s137 opened this issue Jun 29, 2023 · 9 comments
Labels
Duplicate An existing issue was already created

Comments

@s137
Copy link

s137 commented Jun 29, 2023

Bug Report

🔎 Search Terms

object is possibly null, optional chaining, nullish coalescing

🕗 Version & Regression Information

  • This was tested in typescript 4.9.5 and also on the newest available version of typescript on the playground
  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about 'nullish coalescing', 'optional chaining' and '?'

⏯ Playground Link

Playground Link

💻 Code

function returnStringArrayOrNull(): RegExpExecArray | null {
    // this is only an example check
    return /testexpression/.exec(navigator.userAgent);
}

const matches: RegExpExecArray | null = returnStringArrayOrNull();
const res = ((matches?.length ?? 0) > 1) ? matches[1] === 'true' : false;

const matches2: RegExpExecArray | null = returnStringArrayOrNull();
const res2 = (matches2 && (matches2.length > 1)) ? matches2[1] === 'true' : false;

🙁 Actual behavior

The code fails to compile with the following error message: TS18047: 'matches' is possibly 'null', which makes no sense to me.
The second example with the const matches2 works completely fine.
The same thing happens if I use the || operator instead of ??.

🙂 Expected behavior

From my understanding this should work perfectly fine, because if matches is null, the use of the ? operator should make sure, that the expression matches?.length is evaluated to undefined and then undefined ?? 0 should get evaluated to 0 wich is less than 1, hence matches should not be able to be null in any scenario. (see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html)

@MartinJohns
Copy link
Contributor

This is a design limitation. See this comment: #48536 (comment)

@jcalz
Copy link
Contributor

jcalz commented Jun 29, 2023

Also, the compiler would need to "know" that 0 < 1 is always true, which it doesn't. That part is #26382.

@s137
Copy link
Author

s137 commented Jun 29, 2023

But why would you make such a design limitation, I really don't see the benefit of it. For a developer like me coming from a C# background (where such a thing would work perfectly fine) it's just very confusing.

Also @jcalz why wouldn't the compiler "know" 0 < 1 is always true? I looked at your link but I don't really get how this applies here. I dont't have literal number comparision typings here. As an example, I can do the following:

function returnStringArrayOrNull(): RegExpExecArray | null {
    // this is only an example check
    return /testexpression/.exec(navigator.userAgent);
}

const a = returnStringArrayOrNull()?.length ?? 0;
const b = a < 1;
if (b) {
  // do something
  console.log('Test');
}

which works fine, if returnStringArrayOrNull returns null, so I'd say the compiler does know that 0 is less than 1.

But maybe I'm just not understanding it correctly, so feel free to let me know if I'm completely wrong here and if so, could you maybe explain it?

@RyanCavanaugh
Copy link
Member

But why would you make such a design limitation

We don't put design limitations in place with intention. A design limitation is a thing that occurs as a result of a trade-off to get some other desirable thing.

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Jun 29, 2023
@s137
Copy link
Author

s137 commented Jun 29, 2023

But why would you make such a design limitation

We don't put design limitations in place with intention. A design limitation is a thing that occurs as a result of a trade-off to get some other desirable thing.

Ah ok now that makes sense. I still don't know what the downside of having the above behaviour would be (i.e. which desireable behaviour that works because of it would disappear, if it were changed), but I get what you mean. Thanks for the clarification.

Also as @MartinJohns mentioned and as you just tagged it, this is a duplicate, so I'm going to close it as such.

@s137 s137 closed this as not planned Won't fix, can't repro, duplicate, stale Jun 29, 2023
@jcalz
Copy link
Contributor

jcalz commented Jun 29, 2023

which works fine

All code should "work fine" at runtime, but I'm talking about what the type checker can verify. If you test 0 < 1 the compiler sees that as boolean and not true. If you write if (true) { } else { console.log("oops") } there will be an unreachability error in the else clause, but if (0 < 1) { } else { console.log("oops") } does not have such an error.

@MartinJohns
Copy link
Contributor

But why would you make such a design limitation, I really don't see the benefit of it. For a developer like me coming from a C# background (where such a thing would work perfectly fine) it's just very confusing.

In TypeScript you can pass an object that's structurally the same in place for another type, but in C# I can't do this. Why would they make such a design limitation, I really don't see the benefit of it. For a developer like me coming from a TypeScript background (where such a thing would work perfectly fine) it's just very confusing.

Sorry, I could not resist.

@s137
Copy link
Author

s137 commented Jun 29, 2023

which works fine

All code should "work fine" at runtime, but I'm talking about what the type checker can verify. If you test 0 < 1 the compiler sees that as boolean and not true. If you write if (true) { } else { console.log("oops") } there will be an unreachability error in the else clause, but if (0 < 1) { } else { console.log("oops") } does not have such an error.

Ah ok now I get what you meant, the compiler doesn't "know" at compile time that 0 < 1, it evaluates it only at runtime, which I nevertheless still find odd, because it's basic mathematics, so the compiler should and could know (in C# for example I get an unreachability warning for both your examples).

But ok, I guess the lesson here for me is that obviously every programming language does things differently, and just because you are used to something working a certain way, it doesn't mean its always the best way there is. Just as @MartinJohns kind of showed me with his comment 😄

@jcalz
Copy link
Contributor

jcalz commented Jun 29, 2023

basic mathematics

looks meaningfully at #26382

And maybe #15645

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

4 participants