Skip to content

Support inferring never as the return type in JavaScript when overriding a class method that returns never #46791

Open
@ExE-Boss

Description

@ExE-Boss

Suggestion

🔍 Search Terms

  • JavaScript return never
  • JavaScript method return never
  • JavaScript override method return never
  • JavaScript abstract method return never

✅ Viability 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, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

When implementing an abstract method with a signature that returns never, the concrete implementation should have its return type inferred as never if the method body contains an explicit throw and no return statements including the implicit return.

📃 Motivating Example

Workbench Repro:

// @allowJs
// @checkJs
// @noEmit
// @noImplicitAny: false
// @target: ESNext
// @module: ESNext
// @lib: ESNext

// @filename: tokens.d.ts
// Based on: https://github.com/engine262/engine262/tree/main/src/parser/tokens.mjs
export enum Token {
	// ...
	EOS = 13,
	// ...
}

// @filename: Lexer.d.ts
// Based on: https://github.com/engine262/engine262/tree/main/src/parser/Lexer.mjs
import { Token } from "./tokens.js";

export interface ParsedToken {
	type: Token;
	// ...
}

export abstract class Lexer {
	// ...
	peek(): ParsedToken;

	// ...
	abstract createSyntaxError(
		context: object | number | undefined,
		template: string,
		templateArgs: readonly unknown[]
	): SyntaxError;
	abstract raiseEarly(
		template: string,
		context?: object | number,
		...templateArgs: readonly unknown[]
	): SyntaxError;
	abstract raise(
	//          ^?
		template: string,
		context?: object | number,
		...templateArgs: readonly unknown[]
	): never;
	abstract unexpected(
		context?: object | number,
		...templateArgs: readonly unknown[]
	): never;
}

// @filename: Parser.js
// Based on: https://github.com/engine262/engine262/tree/main/src/parser/Parser.mjs
import { Lexer } from "./Lexer.js";
import { Token } from "./tokens.js";

export class Parser extends Lexer {
	earlyErrors = new globalThis.Set();

	createSyntaxError(context = this.peek(), template, templateArgs) {
		if (template === "UnexpectedToken" && context.type === Token.EOS) {
			template = "UnexpectedEOS";
		}
		// ...
		return new globalThis.SyntaxError(/* ... */);
	}

	raiseEarly(template, context, ...templateArgs) {
		const e = this.createSyntaxError(context, template, templateArgs);
		this.earlyErrors.add(e);
		return e;
	}

	// Expected return type:	never
	//   Actual return type:	void
	raise(template, context, ...templateArgs) {
	// ^?
		throw this.createSyntaxError(context, template, templateArgs);
	}

	unexpected(context, ...templateArgs) {
		return this.raise(context, "UnexpectedToken", templateArgs);
	}
}

💻 Use Cases

To get better type inference while working on engine262’s parser.

Relevant issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    Awaiting More FeedbackThis means we'd like to hear from more people who would be helped by this featureHas ReproThis issue has compiler-backed repros: https://aka.ms/ts-reprosSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions