Skip to content

Possible regression of intersection type inference in 5.7.2 compared to 5.6.3 #60864

@spion

Description

@spion

πŸ”Ž Search Terms

inference generic types

πŸ•— Version & Regression Information

⏯ Playground Link

https://www.typescriptlang.org/play/?ts=5.7.2#code/MYGwhgzhAEBqYCcCWYBGICmAeAqgGmgGVoMAPAFwwDsATGCc5KgcwD5oBvAKGmmAHsqDBAFdg5fggAUABxHokwaBABcRAJSdoAXx7QRAQjU4A3F11cAZiKrikg6AFsA1gHFqGZMFwFiZSrT0jEgsrFKqGpx6CBjkIghU0FQYAO5wiCjo2PhEYRDq5lxc5ACeMhjQAKKkYOIAggjMAHJgjhgQWO7JXgAqZRgEAMKCwmAh5OwAvNBdnop95dAAZFG8ANoA0tAh0M4YJfyWMx69-QC6alv+1HS7+4fQw0KMY1Tk0AD8x93z-Ztn0DUyQAbp5CsV+tA6iAQPwUvBkGhMB0elN0oislgegQwFQSuwAD5aTbbRJ7A5HHoXdGZTBY-44vGE6A9f46IqlRY9drkBrMGDTbi8CD8Np8oEiRyoMEWTkVbkMPkQADqSHIAAsEQKoTC4Qjae0sTylawitZbOR7IlyGA9hBZl4sLASBQbjBobD4RkkYaFbzGhBWGEwGpqrV-c1WobYAQ-Sb1GpgfwkDQohZMO9gdBpja7Q7FFIOCKxY01C588ApAAiUhV9TadRmIA

πŸ’» Code

class Variable<U, S extends string> {
  constructor(public s: S) { }
  u!: U;
}

function mkGeneric<U, S extends string>(s: S) {
  return new Variable<U, S>(s)
}

type ExactArgNames<GenericType, Constraint> = GenericType & {
  [K in keyof GenericType]: K extends keyof Constraint ? GenericType[K] : never
}

type AllowVariables<T> = Variable<T, any> | { [K in keyof T]: Variable<T[K], any> | T[K] }

type TestArgs = {
  someArg: number
}

type TestArgsWithVars = AllowVariables<TestArgs>

function takesGeneric<V extends AllowVariables<TestArgs>>(a: ExactArgNames<V, TestArgs>): void {
}

let v = takesGeneric({someArg: mkGeneric("x")});

πŸ™ Actual behavior

VVersion 5.7.2 of the compiler infers the variable type as Variable<uknown, "x"> and subsequently generates an error

πŸ™‚ Expected behavior

VVersion 5.6.3 of the compiler infers the variable type correctly as Variable<number, "x">

Additional information about the issue

This is breaking most code that uses typed-graphql-builder typed-graphql-builder/typed-graphql-builder#85

Activity

Andarist

Andarist commented on Dec 29, 2024

@Andarist
Contributor

I opened a PR with an experiment that could address this: #60876 but, like I mentioned there, I'm not really that confident in the chosen solution.

Your use case is not quite what TS usually allows. Exact types are not a thing, and you are trying to emulate them here. I feel like this is usually "there might be dragons" territory ;p

In general, nested inferences are quite tricky on their own. Just take a look at this comment from the source code:

        // When a call to a generic function is an argument to an outer call to a generic function for which
        // inference is in process, we have a choice to make. If the inner call relies on inferences made from
        // its contextual type to its return type, deferring the inner call processing allows the best possible
        // contextual type to accumulate. But if the outer call relies on inferences made from the return type of
        // the inner call, the inner call should be processed early. There's no sure way to know which choice is
        // right (only a full unification algorithm can determine that), so we resort to the following heuristic:
        // If no type arguments are specified in the inner call and at least one call signature is generic and
        // returns a function type, we choose to defer processing. This narrowly permits function composition
        // operators to flow inferences through return types, but otherwise processes calls right away. We
        // use the resolvingSignature singleton to indicate that we deferred processing. This result will be
        // propagated out and eventually turned into silentNeverType (a type that is assignable to anything and
        // from which we never make inferences).

I hit this myself many times already. In some way, you just lucked out before on some of the contextual types being accidentally ignored and that's why the return type inference was able to flow into your inner inference when the outer one was still in progress.

That being said... this alternative to your types work: TS playground

spion

spion commented on Jan 1, 2025

@spion
Author

Thanks. I did work around this by using a dummy conditional type but using Exclude for filtering keys seems cleaner.

added
Possible ImprovementThe current behavior isn't wrong, but it's possible to see that it might be better in some cases
on Jan 2, 2025
added this to the Backlog milestone on Jan 2, 2025
changed the title [-]Possible regression in 5.7.2 compared to 5.6.3[/-] [+]Possible regression of intersection types in 5.7.2 compared to 5.6.3[/+] on Jan 4, 2025
changed the title [-]Possible regression of intersection types in 5.7.2 compared to 5.6.3[/-] [+]Possible regression of intersection type inference in 5.7.2 compared to 5.6.3[/+] on Jan 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Help WantedYou can do thisPossible ImprovementThe current behavior isn't wrong, but it's possible to see that it might be better in some cases

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

      Participants

      @spion@RyanCavanaugh@Andarist

      Issue actions

        Possible regression of intersection type inference in 5.7.2 compared to 5.6.3 Β· Issue #60864 Β· microsoft/TypeScript