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

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

Open
4 tasks done
ExE-Boss opened this issue Nov 12, 2021 · 3 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Has Repro This issue has compiler-backed repros: https://aka.ms/ts-repros Suggestion An idea for TypeScript

Comments

@ExE-Boss
Copy link
Contributor

ExE-Boss commented Nov 12, 2021

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

@MartinJohns
Copy link
Contributor

MartinJohns commented Nov 12, 2021

The behavior is the same in TypeScript: Playground link
image

This is the behaviour in general, not related to never and void:
image

@jcalz
Copy link
Contributor

jcalz commented Nov 12, 2021

Seems like a special case of #31470 / #1373 to me.

@andrewbranch andrewbranch added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript labels Nov 12, 2021
@typescript-bot typescript-bot added the Has Repro This issue has compiler-backed repros: https://aka.ms/ts-repros label Nov 12, 2021
@typescript-bot
Copy link
Collaborator

typescript-bot commented Apr 13, 2022

👋 Hi, I'm the Repro bot. I can help narrow down and track compiler bugs across releases! This comment reflects the current state of the repro in the issue body running against the nightly TypeScript.


Issue body code block by @ExE-Boss

❌ Failed: -

  • Property 'raise' in type 'Parser' is not assignable to the same property in base type 'Lexer'. Type '(template: any, context: any, ...templateArgs: any[]) => void' is not assignable to type '(template: string, context?: number | object | undefined, ...templateArgs: readonly unknown[]) => never'. Type 'void' is not assignable to type 'never'.
  • Property 'unexpected' in type 'Parser' is not assignable to the same property in base type 'Lexer'. Type '(context: any, ...templateArgs: any[]) => void' is not assignable to type '(context?: number | object | undefined, ...templateArgs: readonly unknown[]) => never'. Type 'void' is not assignable to type 'never'.

Historical Information
Version Reproduction Outputs
4.3.2, 4.4.2, 4.5.2, 4.6.2

❌ Failed: -

  • Property 'raise' in type 'Parser' is not assignable to the same property in base type 'Lexer'. Type '(template: any, context: any, ...templateArgs: any[]) => void' is not assignable to type '(template: string, context?: number | object | undefined, ...templateArgs: readonly unknown[]) => never'. Type 'void' is not assignable to type 'never'.
  • Property 'unexpected' in type 'Parser' is not assignable to the same property in base type 'Lexer'. Type '(context: any, ...templateArgs: any[]) => void' is not assignable to type '(context?: number | object | undefined, ...templateArgs: readonly unknown[]) => never'. Type 'void' is not assignable to type 'never'.

4.2.2

❌ Failed: -

  • Property 'raise' in type 'Parser' is not assignable to the same property in base type 'Lexer'. Type '(template: any, context: any, ...templateArgs: {}) => void' is not assignable to type '(template: string, context?: number | object | undefined, ...templateArgs: {}) => never'. Type 'void' is not assignable to type 'never'.
  • Property 'unexpected' in type 'Parser' is not assignable to the same property in base type 'Lexer'. Type '(context: any, ...templateArgs: {}) => void' is not assignable to type '(context?: number | object | undefined, ...templateArgs: {}) => never'. Type 'void' is not assignable to type 'never'.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Has Repro This issue has compiler-backed repros: https://aka.ms/ts-repros Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

5 participants