Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: throw type #30289

Closed
5 tasks done
Jack-Works opened this issue Mar 9, 2019 · 4 comments
Closed
5 tasks done

Proposal: throw type #30289

Jack-Works opened this issue Mar 9, 2019 · 4 comments
Labels
Duplicate An existing issue was already created

Comments

@Jack-Works
Copy link
Contributor

Jack-Works commented Mar 9, 2019

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.
@RyanCavanaugh
Copy link
Member

Duplicate #23689 ?

@Jack-Works
Copy link
Contributor Author

Yes. Does that under considering?

@jack-williams
Copy link
Collaborator

I don't think this is a duplicate of #23689. That proposal is for a new type that represents an exception during type-checking and should not be related to any other type: the dual of any.

I believe this proposal is for something that is essentially a labelled never that has no distinct type checking behaviour, but gives better error messages.

One potential issue with this proposal is that if the throws type evaluates to never it will get swallowed by subtype reduction. Basically, the same problem where people want

type Colour = "blue" | "green" | string

to give autocompletion suggestions, but TypeScript removes the literals because they are semantically meaningless.

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Mar 18, 2019
@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Mar 18, 2019

I think the broad use case of "Use the type system to indicate error states" is what's being discussed in both issues, with the particulars of topness/bottomness of those flagging types being a design detail to hammer out later - certainly we wouldn't do both, so figuring which is preferable is something we can track at #23689

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

3 participants