question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Type narrowing with assertions containing truthy literals

See original GitHub issue

TypeScript Version: 4.2.0-dev.20201109

Search Terms:

type narrowing truthy type narrowing assertion conditional throw

Code

function test(foo?: number) {
  if (!foo) throw new Error();

  return foo.toFixed();
}

function testWithLiteralTrue(foo?: number) {
  if (!foo && true) throw new Error();

  return foo.toFixed();
}

function testWithTruthyLiteral(foo?: number) {
  if (!foo && "str") throw new Error();

  return foo.toFixed(); // Object is possibly 'undefined'.
}

Expected behavior:

In all three functions typescript would know foo is a number after the assertion.

Actual behavior:

In the last example, testWithTruthyLiteral, typescript complains foo may be undefined. This is the same behavior for all known truthy values except for literal true.

Playground Link: https://www.typescriptlang.org/play?ts=4.2.0-dev.20201109#code/GYVwdgxgLglg9mABFApgZygRgBTDnAfgC5EwQBbAIxQCcBKRAbwChFEZhFsBCPOBqAAsacAO6kU4gKI0RNbHQDczVohoooIGkj4A6KHABiMAB4oAJguUBfFaEiwEydFABMufMVIVq9Jqo4uXnxEADJQ5BoQFAFhMQlpWTh5JRU2dU1tRD0DYzNLVNtme2h4JFQMAGYPQhIyKloGFjZAnj4wiIAiDBpO2JFxMElEGTkrNLUNLR18fSNTC3GiksdylwAWGq963yaAzjaQ8MRMfvihxLHU1Qzp7NnchYKbO3BSpwqoAFYtup9G-wtA7BOAdJjWM6DYajZLjG5TLI5eb5JbMIA

Related Issues: https://github.com/microsoft/TypeScript/issues/29323

It sounds like from https://github.com/microsoft/TypeScript/issues/29323#issuecomment-454548280 that determining truthiness of expressions could lead to circular dependencies between compiler passes, but that literal true has been special cased. This should be able to be extended to truthy literals without the circular dependency concern.

And for context, like others that have brought up similar issues, this stems from wanting a truthy token that can be used for a minification hook without affecting the types or code execution.

Issue Analytics

  • State:open
  • Created 3 years ago
  • Comments:6 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
mlrawlingscommented, Nov 13, 2020

It’s definitely kind of strange. The goal, like this comment, is to have assertions and other code that can be stripped out in a production build.

Here’s a contrived example:

let list: number[] | undefined;

export function setup() {
  list = [];
}

export function append(value: number) {
  if ("DEBUG_TOKEN" && !list) {
    throw new Error("You must call `setup` before appending values");
  }
  list.push(value);
}

export function cleanup() {
  list = undefined;
}

Then, for a production build, you replace "DEBUG_TOKEN" with false using something like @rollup/plugin-replace so it gets eliminated with dead code removal:

export function append(value) {
  list.push(value);
}

I find myself using this pattern in many cases rather than the non-null assertion operator so that it’s explicit why I expect the value to be non-null, and if for some reason it is null, you get a nice explanative error in development environments.

Besides non-null, we use this pattern for some assertion functions as well. One of them requires a recursive tree walk in a fairly hot codepath. It’s nice that we can provide these errors during development, but not pay the cost of the check when running in production.

It would be really nice if we didn’t have to sprinkle ! all over or do things like this to get the types correct:

if ("DEBUG_TOKEN" && !list) {
    throw new Error("You must call `setup` before appending values");
}
list = list!;

or

if ("DEBUG_TOKEN" && !someExpensiveAssertion(value)) {
  throw new Error("A helpful error");
}
value = value as TypeThatTheAssertionSaidItWas;

Especially because tools like terser don’t remove this redundant assignment.

0reactions
RyanCavanaughcommented, Nov 20, 2020

Wait, this is just

let theName = name || "foo";

since '' is falsy

Read more comments on GitHub >

github_iconTop Results From Across the Web

Documentation - Narrowing - TypeScript
The “true” branch narrows x 's types which have either an optional or required property value , and the “false” branch narrows to...
Read more >
Type-Safe TypeScript with Type Narrowing - Rainer Hahnekamp
Truthiness Narrowing​​ Falsy and Truthy. If we put a falsy value into a condition, it will return false and true for a truthy...
Read more >
narrowing types via type guards and assertion functions - 2ality
An assertion function narrows the type of an existing value. A forced conversion function returns an existing value with a new type –...
Read more >
Type Narrowing in TypeScript - Maina Wycliffe
In this type of narrowing, we check whether a variable is truthy ... In this approach, you create an object, with a literal...
Read more >
Narrowing
When every type in a union contains a common property with literal types, TypeScript considers that to be a discriminated union, and can...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found