Skip to content

Proposal: throw type #30289

Closed
Closed

Description

Search Terms

throw type proposal expression language service

Summary

image

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

  1. 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

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions