microsoft / microsoft/TypeScript

Incorrect parse errors issued when linebreak is in mapped type `as` clause

Offen
#53,589 6 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

Bug Domain: Parser Help Wanted
Vorherrschende Sprache
Go
Sterne
111k
Forks
14.4k
Ø Merge
1 T. 19 Std.
Gemergte PRs (30 T.)
117

Beschreibung

Bug Report

Today, putting a linebreak before the extends keyword is a syntax error, but not a line break after the extends keyword.

Especially in the context of mapped types which use conditionals (like Omit, if you expand it), it can be very surprising if you aren't aware of this. I can see an argument for leaving it alone in the case of X5 (below), hopefully the case of Omit3 demonstrates where this can catch users by surprise.

Perhaps if nothing else, there is some way to improve the error message in this situation (Omit3 below), which is:

Mapped object type implicitly has an 'any' template type. (7039)
']' expected.(1005) (if you happen hover in just the right place over the extends)
';' expected.(1005) (if you happen hover in just the right place over the truthy part of the condition)

Which doesn't help as much as the error message in X5 below:

Declaration or statement expected. (1128) (if you hover over the extends)
'number' only refers to a type, but is being used as a value here.(2693) (if you hover over the thing right of the extends)

🔎 Search Terms

line break, linebreak, extends, newline, new line, extends keyword

🕗 Version & Regression Information

This is present since at least TypeScript 4.1 when key remapping was introduced.

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about key remapping
⏯ Playground Link

Playground link with relevant code

💻 Code
// ✅ BASELINE: normal syntax
type X1<T> = T extends number ? true : false;

type X2 = X1<1>;
//   ^? X2 = true

// ✅ works fine to have a new line after `extends`
type X3<T> = T extends
number ? true : false;

type X4 = X3<1>;
//   ^? X4 = true

// ❌ Placing a new line _before_ the `extends` is a syntax error
type X5<T> = T
extends number ? true : false;
// ^ Error: 'number' only refers to a type, but is being used as a value here. (2693)
//         ^ Error: 'number' only refers to a type, but is being used as a value here. (2693)

type X6 = X5<1>;
//   ^? X6 = 1

// ✅ This works fine in some other contexts that are less ambiguous:
type Omit2<T, K
extends keyof any> = Pick<T, Exclude<keyof T, K>>;

type Y1 = Omit<{ a: 1, b: 2 }, 'a'>;
//   ^? Y1 = { b: 2 }

// ❌ But if you use it in a remapping, it fails
type Omit3<T, K extends keyof any> = {
//                                   ^ Error: Mapped object type implicitly has an 'any' template type. (7039)
  [P in keyof T as P
    extends K ? never : P]: T[P];
//  ^ Error: ']' expected.(1005)
//              ^ Error: ';' expected.(1005)
}

type Y2 = Omit3<{ a: 1, b: 2 }, 'a'>;
//   ^? type Y3 = { a: any; b: any; }


// ✅ But if you use it in a remapping, it fails
type Omit4<T, K extends keyof any> = {
  [P in keyof T as P extends
  K ? never : P]: T[P];
}

type Y3 = Omit4<{ a: 1, b: 2 }, 'a'>;
//   ^? type Y3 = { b: 2; }
🙁 Actual behavior
  • X4 is not a syntax error: you can linebreak after an extends keyword in simple conditional types
  • X5 is a syntax error: you cannot linebreak before an extends keyword in simple conditional types
  • Omit3 is a syntax error: you cannot linebreak before an extends keyword in key remapping
  • Omit4 is not a syntax error: you can linebreak after an extends keyword in key remapping
🙂 Expected behavior

None should be syntax errors.

Omit3 is the one that I care about the most because it's the one that comes up the most when writing complex types.

here are some more real-world examples where this kind of thing is likely to come up:

here are some more real-world examples where this kind of thing is likely to come up

in these situations, it's pretty reasonable to want to put the extends on the next line.

type GreaterThanDigit<
  First extends Numeric,
  Second extends Numeric
> =
  `0123456789` extends // here
    `${string}${Second}${string}${First}${string}`
  ? true
  : false
type UnionToFnInsertion<T> =
  (T extends T ? (arg: () => T) => unknown : never) extends // here
    (arg: infer R) => unknown
  ? R
  : never
type Equal<X, Y> =
  (<T>() => T extends X ? 1 : 2) extends // here
  (<T>() => T extends Y ? 1 : 2)
  ? true
  : false

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Beginne mit dem verlinkten TypeScript Playground und reproduziere die Diagnosen für X5 und Omit3, indem du sie mit X3 und Omit4 vergleichst. Verfolge das Parser-Verhalten bei Zeilenumbrüchen rund um extends-Klauseln bedingter Typen; abgeschlossen ist die Aufgabe, wenn die gemeldeten Beispiele die beabsichtigten Formen akzeptieren und angemessene Diagnosen erzeugen und eine Regressionstestabdeckung für diese Fälle vorhanden ist.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
typescript
Bereich
compilers
Issue-Typ
Bug
Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Aktivitätsstatus
Veraltet
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
35/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.