Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve comparable relation of template literal types to detect more impossible situations #57872

Open
hitsthings opened this issue Mar 21, 2024 · 5 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@hitsthings
Copy link

🔎 Search Terms

string condition always return false
2367 template

🕗 Version & Regression Information

This is the behavior in every version after 4.4.4 (including nightly), and I reviewed the FAQ for entries about "template" or "string"

This issue claims to fix a related problem in 4.5, but 4.5.5 is the first issue that fails for me. Maybe no relation though.
#45201

In 4.4.4 it it fails correctly though, so maybe that fix is the regression of this issue?! https://www.typescriptlang.org/play?ts=4.4.4#code/C4TwDgpgBAglC8UAGBDAXGgJAbwHYFcBbAIwgCcBfJAKFEigCEFliMcCTyrrbxoANZkgAebPEVKUadaAE0h7CVxrUAxgHtcAZ2BR0sIeizYAjNw3bdrRkNbGzKizqiiogxCLEO1m5yDRQ8h443tQAlgBmUAAUwgjwiCAAlFDY1FBQTuoANhAAdNnqAObRAOTAABZhWlBaFer42QAmUBUoAG7QEShhuU2lSdQUPJExKPGIxClpGVm5BcVl3b01GmRkEKrA2SADQ9RAA

⏯ Playground Link

https://www.typescriptlang.org/play?#code/C4TwDgpgBAglC8UAGBDAXGgJAbwHYFcBbAIwgCcBfJAKFEigCEFliMcCTyrrbxoANZkgAebPEVKUadaAE0h7CVxrUAxgHtcAZ2BR0sIeizYAjNw3bdrRkNbGzKizqiiogxCLEO1m5yDRQ8h443tQAlgBmUAAUwgjwiCAAlFDY1FBQTuoANhAAdNnqAObRAOTAABZhWlBaFer42QAmUBUoAG7QEShhuU2lSdQUPJExKPGIxClpGVm5BcVl3b01GmRkEKrA2SADQ9RAA

💻 Code

type A = `a::${number}`
type B = `b::${number}`

type X = `x::${number}`
type Y = `${number}`

const a: A = `a::${1}`
const b: B = `b::${1}`

const x: X = `x::${1}`
const y: Y = `${1}`

if (x === y) {
  console.log('this should have failed')
}

if (a === b) {
  console.log('fails correctly')
}

🙁 Actual behavior

Comparing x and y generates no complaints from TS for "x::1" and "1", but does for "a::1" and "b::1". There is no way for a number to evaluate to "x:: so there is no overlap between these template types.

🙂 Expected behavior

I would expect that Typescript shows that error consistently whenever types don't match.

There is no overlap between a string that must start with "x::" and one that must start with a number.

Additional information about the issue

No response

@RyanCavanaugh
Copy link
Member

Bisects to #46137

@RyanCavanaugh
Copy link
Member

That PR adds this code

                        if (relation === comparableRelation) {
                            return templateLiteralTypesDefinitelyUnrelated(source as TemplateLiteralType, target as TemplateLiteralType) ? Ternary.False : Ternary.True;
                        }

where templateLiteralTypesDefinitelyUnrelated is currently conservatively defined as:

    function templateLiteralTypesDefinitelyUnrelated(source: TemplateLiteralType, target: TemplateLiteralType) {
        // Two template literal types with diffences in their starting or ending text spans are definitely unrelated.
        const sourceStart = source.texts[0];
        const targetStart = target.texts[0];
        const sourceEnd = source.texts[source.texts.length - 1];
        const targetEnd = target.texts[target.texts.length - 1];
        const startLen = Math.min(sourceStart.length, targetStart.length);
        const endLen = Math.min(sourceEnd.length, targetEnd.length);
        return sourceStart.slice(0, startLen) !== targetStart.slice(0, startLen) ||
            sourceEnd.slice(sourceEnd.length - endLen) !== targetEnd.slice(targetEnd.length - endLen);
    }

I'm not sure how much further we want to take this logic; allowing more things than is technically possible in the comparable relation isn't a very big defect, and it's somewhat hard to put exact bounds on what the toString of any number might be -- if we tighten up to say the first digit must be numeric or something, then there's still a "bug" where sequences like "00" aren't detected as being not-possible outputs of toString.

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature labels Mar 22, 2024
@RyanCavanaugh RyanCavanaugh changed the title Template types involving number don't fail when compared without overlap (x::${number} versus ${number}) 2367 Improve comparable relation of template literal types to detect more impossible situations Mar 22, 2024
@hitsthings
Copy link
Author

Is this area very performance-bound? I've been hashing it out and I think I could write something to relatively accurately compare two template strings for overlap, but it'd necessarily require checking a lot of different options. It would presumably take a lot more time than checking if the first and last parts are the same.
Is it worth the time for me to explore that?

This caused me to ship a bug so I'd like to fix it. Otherwise I have to use branded strings to distinguish my number strings from other strings which is kinda annoying.

@RyanCavanaugh
Copy link
Member

Overall codesize and complexity is a budget we have to manage. By far the most common complaint we get in this area is that "I should be able to compare anything"; we get very few "I was not allowed to compare two things that in reality didn't overlap". So I'd really need to see more feedback from other people on this before committing to consider any approach.

@hitsthings
Copy link
Author

Ok fair enough. Thanks for the explanation!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

2 participants