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
Inferring a key within a string inconsistently yields too wide a union #57126
This is a somewhat confusing behavior but it works as expected.
The algorithm first infers something - a single character: 'a'. And only after that it checks if this satisfies the constraint. It doesn't satisfy the constraint here so the inferred type gets replaced by the constraint and that is used for your K. So you essentially end up with this once we substitute all of your type arguments etc:
the constraint of type parameters, including those introduced by infer, doesn't change the matching algorithm
Is this true just for string matching? Or is it a general rule? I remember I landed a pull request that introduced an implied arity based on those constraints for some variadic elements when inferring from tuples. This has been a while ago - but should such a change be reverted? Or is the context there sufficiently different that this implied arity is OK there? Note that I realized later that this change wasn't perfect - it only handles some cases (it doesn't make other cases worse though).
Mateusz Burzyński (@Andarist): I was about to get even more confused imagining the option you mentioned would work the way I'd been expecting. Fortunately (I guess?), it doesn't. K is still inferred to be the first character available, and the type evaluates to false. There might have been another misunderstanding somewhere...
Good news is that this provides a way to parse a string one character at a time.
I would also be curious to know what happens when matching the empty string against `${infer Ch}${string}`, since it fails (while technically, both parameters could be instantiated with the empty string).
Ah yes - sorry. Sorry for the confusion - as you mentioned, the proposed version is not good since infer consumes a single character.
Good news is that this provides a way to parse a string one character at a time.
I recommend doing this most of the time.
I would also be curious to know what happens when matching the empty string against ${infer Ch}${string}, since it fails ( while technically, both parameters could technically be instantiated with the empty string).
Like here?
typeTest<Textendsstring>=Textends `${infer Char}${string}` ? Char : never;typeResult=Test<"">;// never
Or did you mean something closer to this?
typeD2={"": "empty";};typeTest<Data,T>=Textends `${infer KextendskeyofData&string}${string}`
? K
: false;typeT2=Test<D2,"">;
I had the first example in my mind.
That said, I would guess it's the same logic behind both, since the second only introduces a constraint on the type to be inferred (extends ''), which is not to influence the matching process.
Type inference supports inferring from a string literal type to a template literal type. For inference to succeed the starting and ending literal character spans (if any) of the target must exactly match the starting and ending spans of the source. Inference proceeds by matching each placeholder to a substring in the source from left to right: A placeholder followed by a literal character span is matched by inferring zero or more characters from the source until the first occurrence of that literal character span in the source. A placeholder immediately followed by another placeholder is matched by inferring a single character from the source.
You have 2 placeholders here so the first one tries to consume a single character - but there is none when you instantiate this T with an empty string so the whole thing doesn't get matched. I think this makes sense.
One extra quirk related to empty strings that I know of is this one: #49839 (comment) . It's quite similar to your problem here - the inferred Lastdoesn't match the constraint, so it's replaced by it. Then we are left with a string that contains 5 placeholders and the last placeholder is not followed by another placeholder... so it just consumes the rest of the string and the conditional type gets satisfied.
I was trying to investigate about the behavior we can observe in the example you linked.
But there was another problem in the way...
typeExactly3<Textendsstring>=Textends `${any}${any}${any}${infer Lastextends''}` ?
T :
never;typeExactly3_Infer<Textendsstring>=Textends `${infer A}${infer B}${infer C}${infer Lastextends''}` ?
[A,B,C,Last] :
never;typeT=Exactly3<'abcd'>;// 'abcd' ie. the original problemtypeT_Infer=Exactly3_Infer<'abcd'>;// never ie. you shall never know what was going on
Is it because having found fitting types for A, B and C on the "first run", there is no going back in the back-anchored attempt for Last? Assuming it even takes place since with that in mind, it could fail immediately...
The only thing I can say for sure is that I'm still confused.
Activity
Andarist commentedon Jan 22, 2024
This is a somewhat confusing behavior but it works as expected.
The algorithm first infers something - a single character:
'a'. And only after that it checks if this satisfies the constraint. It doesn't satisfy the constraint here so the inferred type gets replaced by the constraint and that is used for yourK. So you essentially end up with this once we substitute all of your type arguments etc:This is quite confusing because it makes the more concise/intuitive variant worse than the verbose one (one that behaves like you have expected):
RyanCavanaugh commentedon Jan 22, 2024
This is effectively the same as #49839 - the constraint of type parameters, including those introduced by
infer, doesn't change the matching algorithmAndarist commentedon Jan 23, 2024
Is this true just for string matching? Or is it a general rule? I remember I landed a pull request that introduced an implied arity based on those constraints for some variadic elements when inferring from tuples. This has been a while ago - but should such a change be reverted? Or is the context there sufficiently different that this implied arity is OK there? Note that I realized later that this change wasn't perfect - it only handles some cases (it doesn't make other cases worse though).
ghostinpeace commentedon Jan 23, 2024
Thank you for explaining.
Mateusz Burzyński (@Andarist): I was about to get even more confused imagining the option you mentioned would work the way I'd been expecting. Fortunately (I guess?), it doesn't.
Kis still inferred to be the first character available, and the type evaluates tofalse. There might have been another misunderstanding somewhere...Good news is that this provides a way to parse a string one character at a time.
I would also be curious to know what happens when matching the empty string against
`${infer Ch}${string}`, since it fails (while technically, both parameters could be instantiated with the empty string).Andarist commentedon Jan 23, 2024
Ah yes - sorry. Sorry for the confusion - as you mentioned, the proposed version is not good since
inferconsumes a single character.I recommend doing this most of the time.
Like here?
Or did you mean something closer to this?
ghostinpeace commentedon Jan 23, 2024
I had the first example in my mind.
That said, I would guess it's the same logic behind both, since the second only introduces a constraint on the type to be inferred (
extends ''), which is not to influence the matching process.Andarist commentedon Jan 24, 2024
Yeah, this isn't that different. From #40336 :
You have 2 placeholders here so the first one tries to consume a single character - but there is none when you instantiate this
Twith an empty string so the whole thing doesn't get matched. I think this makes sense.One extra quirk related to empty strings that I know of is this one: #49839 (comment) . It's quite similar to your problem here - the inferred
Lastdoesn't match the constraint, so it's replaced by it. Then we are left with a string that contains 5 placeholders and the last placeholder is not followed by another placeholder... so it just consumes the rest of the string and the conditional type gets satisfied.ghostinpeace commentedon Jan 24, 2024
I was trying to investigate about the behavior we can observe in the example you linked.
But there was another problem in the way...
Is it because having found fitting types for A, B and C on the "first run", there is no going back in the back-anchored attempt for Last? Assuming it even takes place since with that in mind, it could fail immediately...
The only thing I can say for sure is that I'm still confused.
Andarist commentedon Jan 24, 2024
Not quite. I think it's just because this version "removes" the placeholders - and thus it removes the behavior associated with them.
"a"(A),"b"(B),"c"(C),"d"(Last)Lastcan't be accepted - it doesn't match the constraint, so it's replaced with the constraint ("")"abc"(no placeholders!)"abcd") is checked against that - it doesn't extend"abc"so the falsy branch gets returnedtypescript-bot commentedon Jan 27, 2024
This issue has been marked as "Duplicate" and has seen no recent activity. It has been automatically closed for house-keeping purposes.