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

Allow extending types referenced through interfaces #31843

Open
inad9300 opened this issue Jun 10, 2019 · 12 comments
Open

Allow extending types referenced through interfaces #31843

inad9300 opened this issue Jun 10, 2019 · 12 comments
Labels
Has Repro This issue has compiler-backed repros: https://aka.ms/ts-repros In Discussion Not yet reached consensus Suggestion An idea for TypeScript

Comments

@inad9300
Copy link

Suggestion

Allow things like the following:

interface I extends HTMLElementTagNameMap['abbr'] {}

Currently, the following error message is given: "An interface can only extend an identifier/qualified-name with optional type arguments." Which I don't even understand.

I believe it requires no further clarification or justification, but please let me know if that is the case...

@fatcerberus
Copy link

That error message could definitely be worded better. "An interface can't extend an expression" would probably be clearer (and more concise!).

I don't know about the "qualified-name" part (kind of weird) but "identifier with optional type arguments" basically just means you can either extends Foo or extends Foo<T>. And then if Foo is declared as, like,

interface Foo<T = any>
{
    /* magic goes here */
}

You can just extends Foo again without passing in a type. On the other hand HTMLElementTagNameMap['abbr'], taken as a whole, is not an identifier - it's an identifier with an indexing operator after it, making it an expression. A type-level expression, but an expression nonetheless.

That said, I don't see any theoretical reason why this couldn't work. Might be pretty useful. 👍

@AnyhowStep
Copy link
Contributor

What if the expression is a conditional type?

@dragomirtitian
Copy link
Contributor

@AnyhowStep It might be a conditional type hiding behind a type alias right now as well, that works under certain conditions and errors under others. Same rules should apply.

@fatcerberus
Copy link

fatcerberus commented Jun 10, 2019

Another point in favor of this is that class C extends <expression>, where the expression evaluates to a constructor at runtime, is legal (one of the reasons why classes aren’t hoisted), so there’s no reason why you shouldn’t be able to do the same with interfaces, at the type level.

@RyanCavanaugh RyanCavanaugh added In Discussion Not yet reached consensus Suggestion An idea for TypeScript labels Jun 13, 2019
@RyanCavanaugh
Copy link
Member

In principle it's doable - we can detect when the resolved entity is a legal extends target

@coreh
Copy link

coreh commented Jul 9, 2019

As long as you assign the type a name it works, and it does the proper check already:

interface Bar {}
interface Baz {}

// This doesn't work
interface Foo extends (Bar & Baz) {}

// This works
type _tmp = Bar & Baz;
interface Foo extends _tmp {};

// This fails with a meaningful error message:
// > An interface can only extend an object type or intersection of object types with statically known members. ts(2312)
type _tmp2 = Bar | Baz;
interface Foo extends _tmp2 {};

So IMO we should definitely support the anonymous/expression version of this for orthogonality/consistency

@Tyriar
Copy link
Member

Tyriar commented Aug 1, 2021

I just hit this. It's a hassle to need the extra type alias below to workaround this and also agree the error message isn't great.

const enum Key {
  A = 'a'
}

interface IMapped {
  [Key.A]: ITarget
}

interface ITarget {
  foo: number;
}

function f(
  arg: IMapped[Key.A] // Works fine
) {
  arg.foo; // Works fine
}

// An interface can only extend an identifier/qualified-name with optional type arguments.(2499)
interface IExtended extends IMapped[Key.A] {
}

// Workaround
type Alias = IMapped[Key.A];
interface IExtended2 extends Alias {
}

Playground link

@tadhgmister
Copy link

Wrapping the expression in a NO_OP that just resolves to the generic it is given works here, surely typescript is smart enough these days we can drop this restriction with relative ease?

playground link

interface A{
  field : {
    foo: any, bar: any
  }
}
// this is invalid - error 2499 
// An interface can only extend an identifier/qualified-name with optional type arguments.
interface B extends A["field"] {}

type NO_OP<T> = T
// this is valid though?
interface C extends NO_OP<A["field"]> {}

I really don't want to start writing code that actually relies on a "do nothing" type in order to get around seemingly useless restrictions...

@rotu
Copy link

rotu commented Jan 12, 2024

It even fails if the type is just an otherwise legal name in parentheses, or a literal object type!

interface A{}
// works
interface B extends A {}
// fails: "An interface can only extend an identifier/qualified-name with optional type arguments."
interface B extends (A) {}
// fails: "An interface can only extend an identifier/qualified-name with optional type arguments."
interface B extends {x:number} {}

Workbench Repro

@typescript-bot typescript-bot added the Has Repro This issue has compiler-backed repros: https://aka.ms/ts-repros label Jan 12, 2024
@typescript-bot
Copy link
Collaborator

typescript-bot commented Jan 12, 2024

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


Comment by @rotu

❌ Failed: -

  • An interface can only extend an identifier/qualified-name with optional type arguments.
  • An interface can only extend an identifier/qualified-name with optional type arguments.

Historical Information
Version Reproduction Outputs
4.9.3, 5.0.2, 5.1.3, 5.2.2, 5.3.2

❌ Failed: -

  • An interface can only extend an identifier/qualified-name with optional type arguments.
  • An interface can only extend an identifier/qualified-name with optional type arguments.

@RyanCavanaugh
Copy link
Member

The error really does mean exactly what it says 🙃

@rotu
Copy link

rotu commented Jan 12, 2024

The error really does mean exactly what it says 🙃

lol. I think it's a little more confusing because types are not nominal. That is, the interface extends "the type referenced by Foo", not "the identifier Foo".

Ideally, I'd like to see the issue resolved by allowing the base type to be a type expression per this issue. But it would be a step in the right direction to reword the message to "An interface's base type must be identified by name (possibly with optional type arguments)".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Has Repro This issue has compiler-backed repros: https://aka.ms/ts-repros In Discussion Not yet reached consensus Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

10 participants