microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.19k stars 12.38k forks source link

Smarter type narrowing #51348

Closed samchan0221 closed 1 year ago

samchan0221 commented 1 year ago

⭐ Suggestion

interface MessedUp { isMessedUp: true; }

function laughAt(messedUp: MessedUp){}

const myLife: Life = null as any;

if (myLife.isMessedUp) { // Argument of type 'Life' is not assignable to parameter of type 'MessedUp'. // type of myLife is still Life, but it could've been { // isMessedUp: false; // difficulty: "hard" | "medium" | "easy"; // } laughAt(myLife); }


## 💻 Use Cases
- Having this possible will help saving many lines of code of strange type and type guard
- Currently in this case I'm using type guard (which incur runtime cost) or union type (which makes code unmaintainable)
- Union type alternative (it is obviously not scalable)
```ts
type LifeUnion = {
    isMessedUp: true,
    difficulty: "hard",
} | {
    isMessedUp: true,
    difficulty: "medium",
} | {
    isMessedUp: true,
    difficulty: "easy",
} | {
    isMessedUp: false,
    difficulty: "hard",
} | {
    isMessedUp: false,
    difficulty: "medium",
} | {
    isMessedUp: false,
    difficulty: "easy",
}

interface MessedUp {
    isMessedUp: true;
}

function laughAt(messedUp: MessedUp){}

const myLifeUnion: LifeUnion = null as any;

if (myLifeUnion.isMessedUp) {
    laughAt(myLifeUnion);
}

type LifeExperimental = Experimental<"difficulty", ["hard","medium","easy"], Experimental<"isMessedUp", [true, false]>>;

interface MessedUp { isMessedUp: true; }

function laughAt(messedUp: MessedUp){}

const myLifeExperimental: LifeExperimental = null as any;

if (myLifeExperimental.isMessedUp) { laughAt(myLifeExperimental); }

MartinJohns commented 1 year ago

Your suggestion would be unsound, because object types are not sealed. This would be a valid MessedUp type with isMessedUp set to true, but not a valid Life.

const obj = {
    isMessedUp: true,
    difficulty: 1
} as const;
const asType: MessedUp = obj;
jcalz commented 1 year ago

Duplicate of #42384

DanielRosenwasser commented 1 year ago

I would say there's a few aspects of this.

First, trying to associate a value with an appropriate named type based on narrowing logic would be prohibitively expensive (and probably undecidable), and has some pitfalls that @MartinJohns kind of alludes to.

Closer to what you might want is a way to say the type of myLife is equivalent to something like Life & MessedUp. That seems more likely, and we've brainstormed through it, but has issues with how control flow analysis re-joins types when we encounter unions of intersections. If that's more of the request, I would agree that this is a duplicate of #4238.

fatcerberus commented 1 year ago

I don’t really think this is a duplicate of #4238. 🚎

jcalz commented 1 year ago

That's just an unfortunate short hash collision, if you want to be sure that it works, you should use the full hash: #4238486defb1a4cbdb36bd012357ba5bed28f371

MartinJohns commented 1 year ago

@jcalz: image

samchan0221 commented 1 year ago

I would say there's a few aspects of this.

First, trying to associate a value with an appropriate named type based on narrowing logic would be prohibitively expensive (and probably undecidable), and has some pitfalls that @MartinJohns kind of alludes to.

Closer to what you might want is a way to say the type of myLife is equivalent to something like Life & MessedUp. That seems more likely, and we've brainstormed through it, but has issues with how control flow analysis re-joins types when we encounter unions of intersections. If that's more of the request, I would agree that this is a duplicate of #4238.

Thank you all for answering and providing related issues. I searched narrow and didn't found what I want so I opened this issue. I looked at that issue and think they are pretty similar improvement request.

I agree with the fact that trying to associate a value with an appropriate named type is undecidble. What I want is just the structural type changing throughout the control flow as it passes through condition checks (which is already working in if the type is a union type).

I don't know if my Experimental type would help but it's the idea of that computed type is to have the cross product of all key value combinations. I would love to study a little bit on typescript source code to see how it works and how difficult it will be to make this kind of changes (I expect this to be mega hard, since I've no langauge implementation experience)

This is also my first issue on Typescript, can feel the helpfulness of the community ❤️

typescript-bot commented 1 year ago

This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.