You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Optional properties in inferred mapped type causes auto-completion to break.ย #49547
typeA1<Textendsobject>={[Kin(keyofT|'autoCompleteProp')]:
Kextends'autoCompleteProp'
? boolean
: KextendskeyofT
? T[K]extendsobject
? A1<T[K]>
: object
: never;}typeA2<Textendsobject>={[Kin(keyofT|'autoCompleteProp')]?: // <---- this is the only difference compared to A1Kextends'autoCompleteProp'
? boolean
: KextendskeyofT
? T[K]extendsobject
? A2<T[K]>
: object
: never;}functionmakeA1<TextendsA1<T>>(t: T){returnt;}functionmakeA2<TextendsA2<T>>(t: T){returnt;}makeA1({autoCompleteProp: true,// <-- you CAN autoComplete 'autoCompleteProp' herebla: {autoCompleteProp: true,// <-- you CAN autoComplete 'autoCompleteProp' hereblubb: {autoCompleteProp: false,// <-- you CAN autoComplete 'autoCompleteProp' herebleh: {// <-- you CAN autoComplete 'autoCompleteProp' here}},},});makeA2({autoCompleteProp: true,// <-- you CAN autoComplete 'autoCompleteProp' herebla: {// <-- you CAN'T autoComplete 'autoCompleteProp' hereblubb: {// <-- you CAN'T autoComplete 'autoCompleteProp' herebleh: {// <-- you CAN'T autoComplete 'autoCompleteProp' here},},},});
๐ Actual behavior
No auto-completion offered by editor or IDE in marked lines when pressing CTRL + SPACE or a
Edit: You can enter autoCompleteProp: '', in the lines where auto-completion fails. The compiler will complain that autoCompleteProp must be a boolean. Thus, the information about the optional property is there, it's just that it never appears in the auto-completion results.
๐ Expected behavior
Auto-complete suggestion for "autoCompleteProp" in every line marked with <--
I think this has bitten me in the past and it would be lovely to get this fixed.
So far this is what I've gathered:
the origin of the problem can be narrowed down to those lines
with required properties the inferredType (eg: { autoCompleteProp: true; someProp: {}; }) doesn't pass the compareTypes check and thus the instantiatedConstraint (A1<{ autoCompleteProp: true; someProp: {}; }>) gets assigned to inference.inferredType
with optional properties the same inferredType ({ autoCompleteProp: true; someProp: {}; }) passes the compareTypes check and thus inferredType stays assigned to inference.inferredType as-is.
the compareTypes check is simply using compareTypesAssignable under the hood so the logic here makes sense - with optional members the inferredType is assignable to the instantiatedConstraint but with required ones it isn't.
getCompletionsAtPosition ends up looking up properties of this inferredType and it fails to see autoCompleteProp there because the inference "locked" the type param to an object without this property
Part of the question here is - should this autoCompleteProp?: boolean be part of the inferred type? If we assume that makeA1/makeA2 are really identity functions - should this property be available on the returned object? I guess the choice here is somewhat arbitrary and there is no good/bad answer here. A decision had to be made about this and it's not part of that type.
That raises an issue with the autocomplete though. I knew about some other, somewhat similar, issues with "locked" inferences - eg. this PR by Andrew Branch (@andrewbranch). So I've thought that maybe a similar approach could be taken here. We could avoid "locking" the inference when resolving those types for the completions "request" .
I've experimented with this a little bit and, while I broke some stuff, I think I also got somewhat promising results here. I learned that it's definitely not as easy as just using instantiatedConstraint over inferredType ๐
However, the problem I see with this is that the mentioned compareTypes is concerned with type params/arguments but what I would truly like to do is to avoid locking the inference for the object at a given position, a mapped type like this can be nested somewhere in the parameter and doesn't have to appear "at the top-level" of the parameter. Does anyone have any ideas how to approach this?
Activity
Andarist commentedon Jun 27, 2022
I think this has bitten me in the past and it would be lovely to get this fixed.
So far this is what I've gathered:
inferredType(eg:{ autoCompleteProp: true; someProp: {}; }) doesn't pass thecompareTypescheck and thus theinstantiatedConstraint(A1<{ autoCompleteProp: true; someProp: {}; }>) gets assigned toinference.inferredTypeinferredType({ autoCompleteProp: true; someProp: {}; }) passes thecompareTypescheck and thusinferredTypestays assigned toinference.inferredTypeas-is.compareTypescheck is simply usingcompareTypesAssignableunder the hood so the logic here makes sense - with optional members theinferredTypeis assignable to theinstantiatedConstraintbut with required ones it isn't.getCompletionsAtPositionends up looking up properties of thisinferredTypeand it fails to seeautoCompletePropthere because the inference "locked" the type param to an object without this propertyPart of the question here is - should this
autoCompleteProp?: booleanbe part of the inferred type? If we assume thatmakeA1/makeA2are really identity functions - should this property be available on the returned object? I guess the choice here is somewhat arbitrary and there is no good/bad answer here. A decision had to be made about this and it's not part of that type.That raises an issue with the autocomplete though. I knew about some other, somewhat similar, issues with "locked" inferences - eg. this PR by Andrew Branch (@andrewbranch). So I've thought that maybe a similar approach could be taken here. We could avoid "locking" the inference when resolving those types for the completions "request" .
I've experimented with this a little bit and, while I broke some stuff, I think I also got somewhat promising results here. I learned that it's definitely not as easy as just using
instantiatedConstraintoverinferredType๐However, the problem I see with this is that the mentioned
compareTypesis concerned with type params/arguments but what I would truly like to do is to avoid locking the inference for the object at a given position, a mapped type like this can be nested somewhere in the parameter and doesn't have to appear "at the top-level" of the parameter. Does anyone have any ideas how to approach this?barak007 commentedon Dec 17, 2023
I hope this gets fixed soon.
I have a simple case similar to this and I think the offender here is the combination of extending
Partialandkeyof T.it also sounds similar to #45617
Playground
victorgarciaesgi commentedon Apr 1, 2025
Hey! In Typescript
5.8.xthis is still happening, despite the #48410 that was merged.Here is a reproduction
RyanCavanaugh commentedon Dec 3, 2025
Closing language service bugs related to the 6.0 implementation. For more information, see #62827