Repository navigation
Type 'x.y.${Name}' does not satisfy the constraint '${string}.${Name}'.(2344) #61990
Description
🔎 Search Terms
Template Literal Types, assignability, generics
🕗 Version & Regression Information
- Between versions 4.3.5 and 4.2.3 more cases like this started being supported (
x.${Name}became assignable to${string}.${Name}for example), however thex.y.${Name}case has not bees assignable in any version since template support was added up to and including current nightly ( 5.9.0-dev.20250702 )
⏯ Playground Link
💻 Code
/**
* Compile time assert that A is assignable to (extends) B.
* To use, simply define a type:
* `type _check = requireAssignableTo<T, Expected>;`
*/
export type requireAssignableTo<_A extends B, B> = true;
type worksSinceTypeScript4_3<Name extends string> = requireAssignableTo<
`x.${Name}`,
`${string}.${Name}`
>;
type fails<Name extends string> = requireAssignableTo<
// Type '`x.y.${Name}`' does not satisfy the constraint '`${string}.${Name}`'.(2344)
`x.y.${Name}`,
`${string}.${Name}`
>;
type nonGenericWorks = requireAssignableTo<
`x.y.z`,
`${string}.z`
>;🙁 Actual behavior
A compile error:
Type '
x.y.${Name}' does not satisfy the constraint '${string}.${Name}'.(2344)
🙂 Expected behavior
No compile error. The .${Name} portion of the type matches exactly, so this can be reduced to checking if "x.y" is assignable to string should be allowed.
Additional information about the issue
I understand that in general not all cases like that are practical to support: perfect assignability checking of template strings is likely infeasible but I decided to report this case as I think this one suggest an algorithm that would be practical to implement and efficient: running a greedy algorithm to remove common constant substrings from the beginning and end of the string, including generic values, might be a viable approach, and would improve handling of this case.
Activity
MartinJohns commentedon Jul 2, 2025
I believe this is working as intended, as per #40336.
Basically the matching is not eager. The
${string}part in${string}.${Name}will only match up to the first., and${Name}will match all the rest. So it's different fromx.y.${Name}, because here the part after the first.will not be matched by${Name}.CraigMacomber commentedon Jul 2, 2025
By that augment, the fact that
x.y.zis assignable to${string}.zseems like a bug then since the string is just supposed to match up to the first "." and.y.zis not equal to.z?Perhaps I misunderstand, but that sounds like it would be a really odd choice, and also isn't how simpler cases behave.
MartinJohns commentedon Jul 2, 2025
In that case it knows that the string will end with
.z, but in the case with${Name}it doesn't know it, and${Name}is matching all the rest (after the first.).CraigMacomber commentedon Jul 2, 2025
You have explained how it works, and why the implementation produces the observed results. Thanks for that.
Perhaps I'm misinterpreting your meaning but from that being the entirety of your response I'm interpreting that as implying that this is not a bug, and is expected behavior. Is that correct?
If this is expected, that's fine, but in that case I think the documentation should better explain this kind of thing, or at least note that there are some limitation.
Either way I still think the behavior I would like here would be safe, desirable, compatible with what is documented about template string literals, and possible to implement. I'm not clear if the above comment is intended to disagree with that implicitly or simply doesn't address it.
To me it seems like the choice to implement it as currently implemented produces very strange behaviors which do not make sense and require understanding the implementation details of the compiler to discover these behaviors.
For example, it's not intuitive to me that if you were to replace the name type parameter with any possible type (
string,any,neverany union of string literals, or even a nested template literal) it is assignable, but it's unassignable when the generic type parameter is used. I understand if thats an implementation limitation, but it seems undesirable and undocumented.It's also unclear to me from my reading of https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html#handbook-content why we get different behavior if we reverse the string. Nothing in the documentation implies that the template string literals check assignability in left to right in a greedy way, which seems to be what's implemented.
Consider this case:
Is there a way a reader of the documentation should come to the conclusion that the direction of strings should impact which cases are allowed?
To me this behavior is surprising, especially since some other things in TypeScript do not work like this. For example doing the same thing with arrays instead of separated strings allows both cases to compile:
It seems odd to me that arrays handle this case fine, but template literal strings behave differently.
I know TypeScript accepts some limitations to which cases compile and which cases do not, and some edge cases (like those around recursive types) are fuzzy and not guaranteed. If there is a similar area of unspecified best effort support for template type literals, I personally think it would be nice to document that somewhere. I checked the handbook page and can't find any indication of such limitations, but maybe there is another place I should look?
Anyway, if this really is expected, feel free to convert this into a feature request (for more cases to be supported and/or improved documentation) instead of a bug report.