Skip to content

Allow 'declare' on methods and constructorΒ #48290

Open

Description

Suggestion

Allow declare on methods and constructor of a (non-declared) class.

πŸ” Search Terms

"declare modifier cannot appear on class elements of this kind"
"TS1031"
"1031"

βœ… 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

It is currently not possible to use declare on methods (including the constructor) of a (non-declared) class. TypeScript responds with the error

'declare' modifier cannot appear on class elements of this kind.(1031)

However, there are use cases where I would like to refine the type of a method or the constructor in a subclass without changing the implementation.

So, like for fields, I suggest to allow declare on methods and the constructor, too.

πŸ“ƒ Motivating Example

In a subclass, overriding methods are allowed to change parameter types to compatible types. Usually, a different method signature means a different implementation, too, but there are cases where inheriting the implementation would be fine, and I just want to declare the changed method signature. This is often the case if the inherited implementation delegates to another method, which is overridden with a changed method signature in the subclass.

Here is a TypeScript playground link to a working example.

The example defines a class Super whose constructor takes some configuration object of type A. The constructor of its subclass Sub takes a parameter of B, which is a sub-type of A.

The constructor of Super delegates to method init, which also has parameter type A. Thus, overriding init in Sub to support parameter type B implies that Sub's constructor now also supports arguments of type B.

Now, it would be nice if we could just declare the new signature of Sub's constructor, instead of implementing it with boiler-plate code that simply hands through the arguments.

While TypeScript allows to use the declare keyword on fields, it forbids to use it on methods and constructors. The example uses @ts-ignore to show that if I override this restriction, I get exactly the result that I want: new Sub(...) now expects a parameter of type B. The implementation of Sub's constructor is inherited from Super and delegates to the (overridden) init method.

πŸ’» Use Cases

Workaround 1 is shown in the example: Use @ts-ignore or @ts-expect-error to ignore this error (TS1031). Ugly, but works.

Workaround 2: Of course, you can implement the subclass constructor by calling super and handing through all parameters, but this results in boiler-plate code and even adds run-time overhead.

Similar use cases as in the motivating example would be to specify a method (instead of the constructor) whose signature is changed in the subclass without changing its implementation. For that use case, there is another workaround, so I'm missing the feature for constructors more desperately. For methods, you can take advantage of declaration merging and specify the new signature in an interface with the same name as the subclass. But even that results in more code than simply using the declare keyword. Both this use case and the workaround are shown in this second TypeScript playground example.

Activity

  1. fatcerberus commented on Mar 16, 2022

    @fatcerberus

    I hope it is not considered "breaking" if someone used @ts-expect-error on exactly the construct I suggest and, after the change, type check would fail, because there no longer is an error.

    This is exactly what @ts-expect-error does, by design: if there’s no error on the guarded line, the ts-expect-error comment itself produces an error. This is specifically so you know to delete the comment when/if a known type system bug/limitation gets fixed. If that were considered a breaking change, no such improvements could ever be made to the type system ever again, defeating the purpose of the directive. πŸ˜›

  2. fwienber commented on Mar 17, 2022

    @fwienber
    Author

    I hope it is not considered "breaking" if someone used @ts-expect-error on exactly the construct I suggest and, after the change, type check would fail, because there no longer is an error.

    This is exactly what @ts-expect-error does, by design: if there’s no error on the guarded line, the ts-expect-error comment itself produces an error. This is specifically so you know to delete the comment when/if a known type system bug/limitation gets fixed. If that were considered a breaking change, no such improvements could ever be made to the type system ever again, defeating the purpose of the directive. πŸ˜›

    Thanks! That's exactly what I was hoping you'd answer, and I just added the remark to be on the safe side. Since you say it is redundant, I removed the remark from the description.

  3. jcalz commented on Jun 26, 2023

    @jcalz
    Contributor

    linking to #38008

  4. xiaoxiyao commented on Nov 19, 2024

    @xiaoxiyao

    Any plans about this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    In DiscussionNot yet reached consensusSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions