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

Reference Thread: Correctness Improvement in Unconstrained Type Parameters #49489

Closed
RyanCavanaugh opened this issue Jun 11, 2022 · 1 comment
Labels
Discussion Issues which may not have code impact

Comments

@RyanCavanaugh
Copy link
Member

What's Happening?

In TypeScript 4.8, for projects with strictNullChecks enabled, TypeScript will now correctly issue an error when an unconstrained type parameter is used in a position where null or undefined are not legal values.

The simplest example is:

// Unconstrained type parameter T...
function foo<T>(x: T) {
  // ... passed to bar's 'value' parameter
  bar(x);
}

// Accepts any non-null non-undefined value
function bar(value: {}) {
  Object.keys(value); // <- throws on null/undefined
}

bar(undefined); // Correct error: this throws

foo(undefined); // Not an error (4.7), bad. Correctly errors in 4.8

Why?

As demonstrated above, code like this has a potential bug - the values null and undefined can be indirectly passed through these unconstrained type parameters to code that is not supposed to observe those values.

Other Manifestations

This behavior will also be visible in type positions. One example would be:

interface Foo<T> {
  x: Bar<T>;
}

interface Bar<T extends { }> { }

How do I fix this?

The exact fix you should do depends on your code, but potential options are:

Add a Constraint

In the first example, we can add a constraint:

function foo<T extends {}>(x: T) {
//             ++++++++++

This will ensure that foo is not called with a value of null or undefined. The best constraint to write depends on the intent of the function:

  • To accept only objects (i.e. strings, numbers, and booleans are not allowed), write T extends object
  • To accept any value that is not null or undefined, write T extends { }
  • If your lint rules say you shouldn't write those types, disable that lint rule

Note that adding a constraint to foo might cause new errors to appear in callers of foo. You may have to repeat this process a few times to fix all call chains in your program.

Test for Null/Undefined

If your intent was still to allow null / undefined values to be passed to foo but didn't intend to crash bar, you can check the value before calling it:

function foo<T>(x: T) {
  if (x != null) {
    bar(x); // OK
  }
}

Non-null assertion operator / postfix !

If you know for other reasons that a value can't be null or undefined, you can use ! after an expression to indicate this:

function foo<T>(x: T) {
  /* Magic 8 Ball tells me that x is actually never null/undefined */
  bar(x!); // OK
}

Fixes in .d.ts files

This check can cause errors to appear even in declaration files:

interface Foo<T> {
  // Error because potential instantiation of Foo<undefined>
  // illegally produces an undefined property value at Foo.x.v
  x: Bar<T>;
}

interface Bar<T extends {}> {
  v: T;
}

In this case, the correct fix is either to add a constraint either matching or subsuming the other type's constraint (interface Foo<T extends {}>), or use intersection:

interface Foo<T> {
  x: Bar<T & {}>;
}

interface Bar<T extends {}> {
  v: T;
}

declare const m: Foo<undefined>;
m.x; // <- of type 'never'

What to Expect

In addition to fixing all impacts of this on DefinitelyTyped, we're also running an extended test run on top TypeScript projects on GitHub to look for instances of this problem, and will be sending PRs to fix these problems where possible. All potential fixes here are fully backward-compatible with TypeScript 4.7, so you can expect to be able to safely merge them.

@jcalz
Copy link
Contributor

jcalz commented Jun 11, 2022

  • If your lint rules say you shouldn't write those types, disable that lint rule

cries in #21732

sandersn added a commit to sandersn/mongoose that referenced this issue Jun 22, 2022
Fixes 16 errors in models.d.ts on Typescript 4.8 which is currently in
beta. Typescript 4.8 forbids assignment of an unconstrained type
parameter to one that disallows `null` or `undefined`:
microsoft/TypeScript#49489

The alternative fix is to constrain *lots* of types in models.d.ts to
`Record<string | number, any>` -- that is, to forbid `null | undefined`,
but I'm not sure it's worth the effort.
darrachequesne pushed a commit to socketio/socket.io that referenced this issue Sep 1, 2022
trevor-scheer added a commit to apollographql/apollo-server that referenced this issue Sep 23, 2022
…rs (#6940)

Similar to ardatan/graphql-tools#4382, but these
didn't turn up in the 4.7 upgrade.
Likely related to microsoft/TypeScript#49489.

Solution is to constrain our generics appropriately.
<!--
First, 🌠 thank you 🌠 for taking the time to consider a contribution to
Apollo!

Here are some important details to follow:

* ⏰ Your time is important
To save your precious time, if the contribution you are making will take
more
than an hour, please make sure it has been discussed in an issue first.
          This is especially true for feature requests!
* 💡 Features
Feature requests can be created and discussed within a GitHub Issue. Be
sure to search for existing feature requests (and related issues!) prior
to
opening a new request. If an existing issue covers the need, please
upvote
that issue by using the 👍 emote, rather than opening a new issue.
* 🔌 Integrations
Apollo Server has many web-framework integrations including Express,
Koa,
Hapi and more. When adding a new feature, or fixing a bug, please take a
peak and see if other integrations are also affected. In most cases, the
fix can be applied to the other frameworks as well. Please note that,
since new web-frameworks have a high maintenance cost, pull-requests for
new web-frameworks should be discussed with a project maintainer first.
* 🕷 Bug fixes
These can be created and discussed in this repository. When fixing a
bug,
please _try_ to add a test which verifies the fix. If you cannot, you
should
still submit the PR but we may still ask you (and help you!) to create a
test.
* 📖 Contribution guidelines
Follow
https://github.com/apollographql/apollo-server/blob/main/CONTRIBUTING.md
when submitting a pull request. Make sure existing tests still pass, and
add
          tests for all new behavior.
* ✏️ Explain your pull request
Describe the big picture of your changes here to communicate to what
your
pull request is meant to accomplish. Provide 🔗 links 🔗 to associated
issues!

We hope you will find this to be a positive experience! Open source
contribution can be intimidating and we hope to alleviate that pain as
much as possible. Without following these guidelines, you may be missing
context that can help you succeed with your contribution, which is why
we encourage discussion first. Ultimately, there is no guarantee that we
will be able to merge your pull-request, but by following these
guidelines we can try to avoid disappointment.
-->
dzad pushed a commit to dzad/socket.io that referenced this issue May 29, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Discussion Issues which may not have code impact
Projects
None yet
Development

No branches or pull requests

2 participants