Skip to content

Allow type guards to be type checked #47468

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

Closed
5 tasks done
Pzixel opened this issue Jan 16, 2022 · 4 comments
Closed
5 tasks done

Allow type guards to be type checked #47468

Pzixel opened this issue Jan 16, 2022 · 4 comments

Comments

@Pzixel
Copy link

Pzixel commented Jan 16, 2022

Suggestion

Allow type guards to be type checked

🔍 Search Terms

Type guards narrowing

✅ Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

Type guards are sometimes used when TS has no knowledge what's happening here and we need some kind of believeMe to enforce some invariants we can guarantee as developers. But there are plenty of trivial cases that can be inferred by compiler itself. And I strongly believe that if it can do it it should since it helps us with human mistakes.

It can be implemented in both breaking and non-breaking manner. I do believe that breaking manner would make future more safe but since I know how people love backward compatibility even between major versions I think an infer keyword may be used to fix the gap. It could look like

const isTen = (value: number): infer value is 10 => {
    if (value === 10) {
      const result = value; // infered to be 10
      return false; // illegal: '10' is assignable to type '10'.
    }
    return true; // illegal: 'number' is not assignable to type '10'.
};

📃 Motivating Example

There are multiple questions with hundreds of upvotes on SO that asks for different narrowings and they all repeat a similar pattern like

// some guard function
function notEmpty<TValue>(value: TValue | null | undefined): value is TValue { // checking some invariant
  if (value === null || value === undefined) return false;
  const testDummy: TValue = value; // compiler already infered all needed information here
  return true; // but it doesn't check it in any way even if user input clearly contradicts with what it infered
}

The problem is compiler doesn't check it at all and we can always rewrite it as:

function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {
  return true; // totally legal
}

or

function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {
  if (value === null || value === undefined) return false;
  const testDummy: TValue = value;
  return false; // absolutely legal
}

💻 Use Cases

  • all sorts of type guards will benefit from additional compiler check which is lightyears better than code review
  • special cases include all kinds of filters like const notNulls: string[] = ['foo', 'bar', null, 'zoo', null].filter(notEmpty)
@MartinJohns
Copy link
Contributor

MartinJohns commented Jan 16, 2022

Duplicate of #29980.

how people love backward compatibility even between major versions

TypeScript does not follow SemVer.

@Pzixel
Copy link
Author

Pzixel commented Jan 16, 2022

Right, sorry I didn't find it first. Yet I think this one has several differences:

  1. I propose using an infer keyword to keep backward compatibility
  2. I think "this is not needed" isn't true since I pretty much struggle with Array.filter keeping invariants. Checking for nulls is all too common
  3. I don't think it will be hard to implement it. It already reuses all existing compiler checks

@MartinJohns
Copy link
Contributor

MartinJohns commented Jan 16, 2022

Different competing syntaxes don't warrant new issues. A different syntax proposal can be commented on existing issues. The TypeScript team mentioned this several times (not bothering to look it up now).

Checking for nulls is common, and trivially solved with a simple function. And for type guards you should just write more unit tests.

The TypeScript team disagrees (in the other issue). The very point of used-defined type guards is to support the compiler with type checks that the compiler itself can't verify. This is true for the majority of cases, except for a few very simply type guards. And I personally would doubt that this narrow use-case would justify the additional complexity in the compiler.

@Pzixel Pzixel closed this as completed Jan 16, 2022
@fatcerberus
Copy link

If type predicates could be automatically verified, we wouldn’t need them. TypeScript already does control flow-based narrowing, so nine times out of ten you’re only writing a type predicate because there’s a type check the compiler couldn’t help you with.

It isn’t like encryption where finding the key is harder than verifying it; verifying through control flow that a type has been narrowed properly is exactly the same problem as doing the narrowing directly.

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

No branches or pull requests

3 participants