Repository navigation
Intersection type in template literal is not reduced to its bare typeΒ #57918
Description
Activity
No, the tsc process didn't crash.
Correct. Updated. π»
Reacted by Joe Calzaretta- addedSuggestionAn idea for TypeScriptAn idea for TypeScriptAwaiting More FeedbackThis means we'd like to hear from more people who would be helped by this featureThis means we'd like to hear from more people who would be helped by this feature
on Mar 25, 2024 RyanCavanaugh commented
on Mar 25, 2024 MemberMore actionsTrying to differentiate template and string literal types seems only possible by relying on weird implementation-detail corner cases and really isn't a great thing to try to offer.
Trying to differentiate template and string literal types seems only possible by relying on weird implementation-detail corner cases and really isn't a great thing to try to offer.
It's not just that. This also make it not possible to check for negative number from interaction type.
Speaking of which, it would be great if TS can provide those type utilities. I understand nowadays TS team don't want to add new type utils, but these are types closely related to the language, and like you said, most of them requires a lot of hackaround ways to get them to work.
RyanCavanaugh commented
on Jan 29, 2025 MemberMore actionsThe
Equalstype there is (intentionally) reliant on internal compiler implementation details and shouldn't be used unless you're actively trying to break something. Its use in type testing libraries is bad and I would discourage its use.RyanCavanaugh commented
on Jan 29, 2025 MemberMore actionsInternal implementation details are subject to change at any time.
Equalsmay evaluate totruebut there are no guarantees on this -- it's dependent on whether or not we choose to intern a given type, which is a performance trade-off that may be situationally-dependent.The origin of this type is that someone went looking for places where type identity might be unintentionally exposed, was successful, and then introduced this into the wild before we noticed and could un-expose it without breaking a ton of people. TS doesn't have any defined semantics of "identical" types, and attempts to reverse-engineer one are not going to be successful as a result.
Update on my above comments: I fixed that slightly tangential issue in #61113, which was merged a few days ago (incidentally, also fixing that particular problem with the
Equalshack).So to re-focus on the issue at hand: personally, I'm ambivalent whether tagged strings should have their tags removed in a template string.
However, the big issue I do see here is that there is no way to remove tags from a literal if you don't know the exact type of the tag(s) in advance.
I.e., it is utterly impossible to write an
Untag<T>type. For any function you come up with, I can give you a literal that can't be untagged. Here's a good test-case:type Evil = 1234 & {a: 0 | 1} & {a: 1 | 2} & {toString: Number['toString']} type YourFailedAttempt = Untag<Evil> // Expected output: 1234
This means that libraries that do conditional string manipulation, etc. will necessarily fail if the user passes in any tagged primitive. It would be really nice to have some way of stripping arbitrary tags from an arbitrary primitive literal.
The very least intrusive fix, I believe, would be to allow the following behavior to work:
type TheTag = Evil extends number & infer TAG ? TAG : unknown
Critically,
TheTagwould need to be everything inEvilexcept the numeric part. Then we could very simply do:OriginalLiteralType = Evil extends TheTag & infer N ? N : neverto retrieve our original literal. (This would have to work forstring,symbol, andbigintas well, of course.)
π Search Terms
template literal, intersection type
π Version & Regression Information
consttemplate literalΒ #54648β― Playground Link
https://www.typescriptlang.org/play?#code/C4TwDgpgBAysBOBLAdgcwCrggHgNJQgA9gJkATAZygoRVQD4oBeKfIk8qmpNKAMigoAZhHhQAqgCgAkAH4oAbVwAaCQF0CxUpUXjVuNTPkBvAL6aOO44oAKg5FAAGAEmO5TjtQC4oAV2QA1sgA9gDuDqZGUADkJAC2YAA2AIYkADKIJPDJidEyPtHcdBlZOXnSBUVo5T7IEABuopKSoJBQAEoAjMywtGiYkNhVDJIA9KNQkwB6si1YHQBMPXA8GFhDfaj8UNbJPt2m9GMT07Nzbe0AzMubAzjRyQBGAMbRR+OTUDPn0O0ALDdVndsA8XtFtrt9lBDsdPt8fh0AKyAujAlzGZC+OKPUQed4nL6zVq-ABsKP663RmOxuMcEKgeygB3xcLOUCAA
π» Code
π Actual behavior
π Expected behavior
Additional information about the issue
I mentioned this in #54648 after it is closed. It is limiting our ability to write the types that works with string literal and template literal and there is no alternative way to workaround that.
I'm suggesting this issue should be fixed and restore the behavior in 5.0.
Here is my original comment:
This behavior is causing a few types in
type-plusto fail (e.g.IsTemplateLiteral,IsStringLiteral,Omit,IsNegative, etc) cyberuni/type-plus#429.In term of soundness, IMO it does make sense that
${string & { a: 1 }}to be reduced to${string}.in JS, it would be:
the reasoning being the
toString(): stringremains unchanged thus the resulting type should be safe to reduce.