-
Notifications
You must be signed in to change notification settings - Fork 13.1k
Closed
Labels
DuplicateAn existing issue was already createdAn existing issue was already created
Description
Search Terms
throw type proposal expression language service
Summary
Provide custom error message on type level. By throw 'error message' (which evals to never)
Detail
Now we have type never to represent a type that has 0 value in it. But it does not provide enough information for it.
For example:
function divide10by<T extends number>(n: T): T extends 0 ? never : number
function divide10by(n: number): number {
return 10 / n
}
divide10by(1) // number
divide10by(0) // never
const result = divide10by(0).toString()
// Property 'toString' does not exist on type 'never'.This prevents we accidentally provide the wrong parameters. But it's not clear enough.
Let's imagine we get a new type of Type expression like this: throw 'Any string' that equals never but show error details in the language service.
function divide10by<T extends number>(n: T): T extends 0 ? throw 'You cannot divided by zero' : number
function divide10by(n: number): number {
return 10 / n
}
divide10by(1) // number
divide10by(0) // never, You cannot divided by zero
const result = divide10by(0).toString()
// Property 'toString' does not exist on type 'never'.
// You cannot divided by zero.This is useful in complex types.
class ElementOrTextArray<T> {
map<NextType>(fn: (current: T) => NextType): ElementOrTextArray<NextType> { return {} as any }
eachParentElement: T extends HTMLElement ? (() => ElementOrTextArray<HTMLElement>) : throw `Cannot invoke eachParentElement on type ${T}. ${T} is not a subtype of HTMLElement` = (() => {}) as any
}
const x = new ElementOrTextArray<string>().eachParentElement()
// ^ Cannot invoke an expression whose type lacks a call signature. Type 'never' has no compatible call signatures.
// Cannot invoke eachParentElement on type string. string is not a subtype of HTMLElement
const y = new ElementOrTextArray<HTMLDivElement>().eachParentElement()
// This is OkayGrammar
// throw 'message'
<throw_type> ::= throw <string>
// throw `message, T = ${typeof t}`
<throw_type> ::= throw <string_literal>
Evaluate:
Any throw expression in type context evaluates to never.
Additional information to language service
throw 'string'evaluates to 'string'- throw `Before ${Type} After` evaluates to 'Before ' + Type + ' After'
Hacks
- A named interface
interface CannotInvokeEachParentElementForTWhereTIsNotSubTypeOfHTMLElement {}
class ElementOrTextArray<T> {
map<NextType>(fn: (current: T) => NextType): ElementOrTextArray<NextType> { return {} as any }
eachParentElement: T extends HTMLElement ? (() => ElementOrTextArray<HTMLElement>) : CannotInvokeEachParentElementForTWhereTIsNotSubTypeOfHTMLElement = (() => {
}) as any
}
const x = new ElementOrTextArray<string>().eachParentElement()
// Cannot invoke an expression whose type lacks a call signature. Type 'CannotInvokeEachParentElementForTWhereTIsNotSubTypeOfHTMLElement' has no compatible call signatures.
const y = new ElementOrTextArray<HTMLDivElement>().eachParentElement()
// OKProblem of this way to strict caller's generic
I cannot specify one thing to be T extends Q ? A : never, in the same time assign it as A.
class A<T> {
x: T extends Q ? (() => void): never = () => {}
// NO, I can't do this.
}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, etc.)
- This feature would agree with the rest of TypeScript's Design Goals.
dragomirtitian, pavelvlasov and aandrejevas
Metadata
Metadata
Assignees
Labels
DuplicateAn existing issue was already createdAn existing issue was already created
