Closed
Description
openedon Mar 9, 2019
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 Okay
Grammar
// 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()
// OK
Problem 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment