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

Call to function returning non-nullable T with contextual type U | undefined should infer T = U #26119

Closed
3 of 4 tasks
mattmccutchen opened this issue Aug 1, 2018 · 6 comments
Assignees
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@mattmccutchen
Copy link
Contributor

mattmccutchen commented Aug 1, 2018

Search Terms

infer undefined constraint null return contextual type strictNullChecks

Conceivably related: #16943

Suggestion

With strictNullChecks, when calling a function whose return type is a type variable T with a non-nullable constraint, and the contextual type includes null and/or undefined, TypeScript should generate an inference for T of the non-nullable part of the contextual type. Currently, I am getting no inference at all.

Minimal example (with strictNullChecks):

function getValue<R extends {}>(): R {
    throw new Error("not implemented");
}

function acceptValue(value?: number) { }
// Currently: Error: Argument of type '{}' is not assignable to parameter of type 'number'.
// With suggestion: No error.
acceptValue(getValue());

Playground

Use Cases

This came up when I attempted to enable strictNullChecks on this code. spanStyles is an optional property of the contextual type, the return type of PA is a type variable with a non-nullable constraint, and it's not possible to infer the return type from the arguments to PA, so I got an error. The error is easily worked around by specifying the type argument to PA.

Examples

I think the suggestion is sufficiently specific that it doesn't need more examples.

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript / JavaScript code (Hopefully not, but one can never be sure with changes to the type system.)
  • 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. new expression-level syntax)
@ghost
Copy link

ghost commented Aug 1, 2018

This happens with any union:

declare function acceptValue(value: number | string): void;
declare function getValue<T extends number | boolean>(): T;
acceptValue(getValue());

getValue's type parameter could be inferred as number since that satisfies both constraints. But instead number | boolean is inferred and there's a compile error.

@RyanCavanaugh
Copy link
Member

ref #16072

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript In Discussion Not yet reached consensus labels Aug 1, 2018
@RyanCavanaugh
Copy link
Member

As Andy identified this doesn't really have to do with null / undefined. But it is pretty weird that expanding the set of values a function can take would cause it to start erroring.

When we have an "outside" inference candidate (in this case, the contextual type that related to the return type), the right thing to do would be do intersect (set intersection, not &) the constraint for that type variable and the candidate type. We could do this (efficiently) for bare union types but synthesizing a proper set-intersection of two arbitrary types isn't something we're set up to do AFAIK.

@RyanCavanaugh RyanCavanaugh added Working as Intended The behavior described is the intended behavior; this is not a bug Design Limitation Constraints of the existing architecture prevent this from being fixed and removed In Discussion Not yet reached consensus Suggestion An idea for TypeScript labels Aug 1, 2018
@RyanCavanaugh
Copy link
Member

Discussed a bunch... TL;DR the declaration function getValue<R extends {}>(): R should really have been illegal from the get-go and the patching we've done to make these declarations not be intrinsically painful are really just patches. So while it's super odd to have a function start failing when its parameter is optional, it's not really observable unless you start with a generic declaration that shouldn't exist in the first place.

Logged #26129

@mattmccutchen
Copy link
Contributor Author

That was just a minimal example. Here's an example where the function is implementable but the variable still can't be inferred from its arguments but only from the return type:

enum Axis { ROW = "row", COL = "col" };

function getMappedType<R extends { [A in Axis]: unknown }>(
  row: R[Axis.ROW], col: R[Axis.COL]): R | Pick<R, Axis> { 
  return { [Axis.ROW]: row, [Axis.COL]: col};
}

function acceptMappedType(mappedType?: {[A in Axis]: number}) { }
acceptMappedType(getMappedType(42, 43));

Playground (remember to enable strictNullChecks)

This is from my spreadsheet project that is pushing mapped types to the limit to keep track of row vs. column IDs (see also #25879).

@RyanCavanaugh RyanCavanaugh self-assigned this Aug 1, 2018
@typescript-bot
Copy link
Collaborator

This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

3 participants