question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Suggestion: Infer type of `property` (to left of `in`) when using "in operator" in if statements on readonly objects

See original GitHub issue

Suggestion

🔍 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
}
However, the type of 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:open
  • Created 2 years ago
  • Reactions:1
  • Comments:9 (5 by maintainers)

github_iconTop GitHub Comments

2reactions
MartinJohnscommented, Jan 15, 2022

You’re just wrongly assuming objects are sealed, when they’re not. Here’s a different example:

const sections = {
  a: 1,
  b: 2
} as const;

function foo(data: { a: number; b: number; c?: number }) {
  data.c = 3;
}

foo(sections);

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 the in 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.

1reaction
MartinJohnscommented, Jan 15, 2022

And if you really want this unsoundness, you could just as easy provide it by wrapping it in a function:

const obj = { key1: "value1", key2: "value2" } as const;
const str = prompt("Enter a string")!;
//    ^? string
if (isPropertyOf(str, obj)) {
  console.log(str);
  //          ^? "key1" | "key2"
}

function isPropertyOf<T extends object>(key: string | number | symbol, obj: T): key is keyof T {
  return key in obj;
}

Playground link

Read more comments on GitHub >

github_iconTop Results From Across the Web

Use the JavaScript “in” operator for automatic type inference in ...
Using the JavaScript “in” operator, we can test for the presence of different properties on the argument object, and TypeScript will automatically infer...
Read more >
How do I prevent the error "Index signature of object type ...
With union types, it's better if you let TypeScript infer the type instead of defining it. // Type of `secondValue` is `string|IOtherObject` let...
Read more >
Understanding infer in TypeScript - LogRocket Blog
The infer keyword and conditional typing in TypeScript allow us to take a type and isolate any piece of it for later use....
Read more >
Documentation - TypeScript 2.8
For a given infer type variable V , if any candidates were inferred from ... variables are replaced with the types inferred in...
Read more >
strict-boolean-expressions | typescript-eslint
Conditions for if , for , while , and do-while statements. Operands of logical binary operators ( lhs || rhs and lhs &&...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found