Suggestion: Infer type of `property` (to left of `in`) when using "in operator" in if statements on readonly objects
See original GitHub issueSuggestion
🔍 Search Terms
- in operator type inference
- inference in
- in operator
- infer type in operator
- in operator type inference
- in operator string type inference
✅ Viability Checklist
My suggestion meets these guidelines:
- This wouldn’t be a breaking change in existing TypeScript/JavaScript code
- This wouldn’t change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn’t a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This feature would agree with the rest of TypeScript’s Design Goals.
⭐ Suggestion
When using the in operator, the type of object
* is inferred.
const el = document.querySelector("a");
if("href" in el){
console.log(el); // <-- here el is HTMLAnchorElement
}
property
* is not inferred on as const
/readonly objects, which it could (and IMO should) be.
📃 Motivating Example
const obj = { key1: "value1", key2: "value2" } as const;
const string = prompt("Enter a string");
if (string in obj) {
console.log(string); // <-- here string HAS TO be `"key1" | "key2"` because obj is readonly and the in operator always does the same thing
// however, `string` is still `typeof string` here
}
💻 Use Cases
What do you want to use this for?
Situations where it’s important to have a precise type of a string to not get another type error, like this one:
const sections = {
"Motivating Example": process1,
"Use Cases": process2,
} as const;
const processed_sections = new Set<keyof typeof sections>();
for (const { innerText } of document.querySelectorAll<HTMLHeadingElement>("h2")) {
if (innerText in sections) {
sections[innerText]();
processed_sections.add(innerText); // <-- Argument of type 'string' is not assignable to parameter of type '"Motivating Example" | "Use Cases"'.
}
}
What shortcomings exist with current approaches?
Getting a type error for something that is obviously not wrong in any way. It is impossible, in runtime, for innerText
not to be "Motivating Example" | "Use Cases"
inside of the if-statement at runtime.
What workarounds are you using in the meantime?
Making a second variable inside of the if-statement that has the name of the first one but _with_correct_type
appended where I cast the type to what it is when I define it. (i.e. const innerText_with_correct_type = innerText as keyof typeof sections;
);
Related issues:
https://github.com/microsoft/TypeScript/issues/21732 is about inferring types on object
and therefore not the same issue, this one refers to the type of the property
side.
Footnotes
*: object
refers to the thing to the right of the in operator
*: property
refers to the thing to the left of the in operator
Issue Analytics
- State:
- Created 2 years ago
- Reactions:1
- Comments:9 (5 by maintainers)
You’re just wrongly assuming objects are sealed, when they’re not. Here’s a different example:
The type of
section
has the properties"a"
and"b"
, but the value has the additional property"c"
. This is valid, because TypeScript is structurally typed.Now assume someone entered
"c"
in your prompt function. If you were to narrow the left side of thein
operator to"a" | "b"
you would have unsound behaviour. You could pass this typed variable to a function accepting"a" | "b"
, when at runtime it’s actually the incompatible value"c"
. A recipe for disaster.It really seems like you might want #12936 and not know it yet.
And if you really want this unsoundness, you could just as easy provide it by wrapping it in a function:
Playground link