-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
TS1092: Type parameters cannot appear on a constructor declaration #10860
Comments
(A) makes no sense even without the type parameters. Constructors can't have a return type annotation because constructors don't return anything, and even if they did it would be the same class as what they're defined in. The error about type parameters is just hiding the error about return annotations: For the same reason it doesn't make sense for constructors to have type parameters. The type parameters would go on the class they're a part of. |
@Arnavion (A) does make sense - it's the same thing as (B). If (A) made no sense, then neither should (B), but that compiles fine. (A) is returning an instance of the same class, just with more specific compile-time typing. It works at runtime, and it also works using the BTW constructors do return something, even in ES6. TypeScript even provides intellisense for their return type. It's just that the compiler applies different rules to |
I was going to edit this in right before you answered: Also, while TS does allow you to write (B), you can't implement it in the way your type signatures intend at runtime either. Of course both of these may change depending on how #7574 is fixed, i.e., if TS did allow you to return arbitrary things from constructors. |
To be clear, the code does not return arbitrary things from the constructor, it returns valid instances of the
Can you elaborate on this? The implementation I'm using with (B) works fine. |
Yes, I understand that. I assume your
I'm saying you cannot implement (B), i.e. the actual implementation of Now, if you're able to change the implementation, you could switch from |
@Arnavion here's a working implementation of (B). It's 100% TypeScript, works at runtime, and achieves the desired goal of getting compile-time type-inference from the arg passed to the constructor: // 'Workaround' for achieving compile-time type inference with a constructor
interface WrapperConstructor {
new<TR>(wrapped: () => TR): NullaryWrapper<TR>;
new<TR, T0>(wrapped: ($0: T0) => TR): UnaryWrapper<TR, T0>;
new<TR, T0, T1>(wrapped: ($0: T0, $1: T1) => TR): BinaryWrapper<TR, T0, T1>;
}
interface Wrapper { invoke(...args: any[]): any;}
interface NullaryWrapper<TR> extends Wrapper { invoke(): TR; }
interface UnaryWrapper<TR, T0> extends Wrapper { invoke($0: T0): TR; }
interface BinaryWrapper<TR, T0, T1> extends Wrapper { invoke($0: T0, $1: T1): TR; }
let Wrapper: WrapperConstructor = class {
constructor(private wrapped: Function) { }
invoke(...args) {
let result = this.wrapped(...args);
console.log(`WRAPPED RESULT: ${result}`);
return result;
}
};
// Above code is runtime-valid...
new Wrapper(() => 42).invoke(); // prints 'WRAPPED RESULT: 42'
new Wrapper((n: number) => n * 2).invoke(10); // prints 'WRAPPED RESULT: 20'
// ...and the invoke method has its type inferred accurately from the ctor argument
new Wrapper((n: number) => n * 2).invoke('foo');// ERROR: 'string' not assignable to 'number'
new Wrapper((a,b) => a+b).invoke();// ERROR: supplied parameters do not match signature The I'd prefer to just be able to write the equivalent class directly without having to split up into static/instance-side interfaces just to get it to compile. Note the implementation of the class is // NB: This doesn't compile
class Wrapper {
constructor<TR>(wrapped: () => TR): NullaryWrapper<TR>;
constructor<TR, T0>(wrapped: ($0: T0) => TR): UnaryWrapper<TR, T0>;
constructor<TR, T0, T1>(wrapped: ($0: T0, $1: T1) => TR): BinaryWrapper<TR, T0, T1>;
constructor(private wrapped: Function) { }
invoke(...args) {
let result = this.wrapped(...args);
console.log(`WRAPPED RESULT: ${result}`);
return result;
}
}
interface NullaryWrapper<TR> extends Wrapper { invoke(): TR; }
interface UnaryWrapper<TR, T0> extends Wrapper { invoke($0: T0): TR; }
interface BinaryWrapper<TR, T0, T1> extends Wrapper { invoke($0: T0, $1: T1): TR; } |
I've been trying to find a page that I think was in the handbook which showed that you could always rewrite a class declaration as a pair of interface declarations, one for the static side and one for the instance side. I've only found this, with an example There's a purely mechanical transform between these two formulations of a class type, and I thought they were supposed to be more-or-less identical to the type system. That's why I'm suggesting that the two formulations should work the same in the OP here. Either they should both work (I'd prefer this), or they should both give the same error. |
Right, you're accepting a
Yes, I already know you can do this. But usually when one does this the static side only has one construct signature (that returns the same type as the instance side), or multiple construct signatures that return the same type as the instance side. Personally I think TS is being lenient by allowing you to have construct signatures targeting arbitrary types in the first place, atleast until #7574 is implemented. It makes no sense in general. But anyway, I get your point. No more argument from me. |
The reason you can't write type parameters on constructors is that there's no place to specify them in a class Foo<T> {
constructor<U>(x: T, y: U) { }
}
let g = new Foo<number>(..., ...); // T: ?, U: ? The main difference between a |
Thanks for explaining @RyanCavanaugh. I don't mind sticking to the decomposed approach. Interestingly the point about having no place to specify the type parameters on a If the OP problem could be solved with a type parameter on the class I'd do that, but I don't think it's possible because of the variadic number of type parameters needed to capture the various function signatures. So is there any guidance here? Would #5453 provide a way to do this? Something like: // Using variadic kinds from #5453
declare class Wrapper<...TParams, TReturn> {
constructor(wrapped: (...args: ...TParams) => TReturn);
invoke(...args: ...TParams): TReturn;
} |
Slightly offtopic since the technique is not used here, but why does TypeScript assume ES6 classes have a unified return type from |
This is definitely technically true, but it's really hard to imagine why you'd bother writing an ES6 class if you were going to |
@RyanCavanaugh here is a real example of returning something else from the constructor in an ES6 class. The example there creates a callable object using a constructor, preserving Of course you can create an ES6 class that subclasses |
Now that we have default generics, I'd like to open this back up, because now the type parameters don't need to be specified in a My situation is that I'm doing some functional programming fu where I'd like to pass a chain of functions which represent transforms. class Foo<A, Z> {
constructor(f1: (a: A) => Z)
constructor<B = any>(f1: (a: A) => B, f2: (b: B) => Z)
constructor<B = any, C = any>(f1: (a: A) => B, f2: (b: B) => C, f3: (c: C) => Z)
constructor<B = any, C = any, D = any>(f1: (a: A) => B, f2: (b: B) => C, f3: (c: C) => D, f4: (d: D) => Z)
constructor(... functions: ((a: any) => any)[]) {
...
}
} I don't care what types B-D are, so long as they form a consistent chain. When the compiler can't infer them, it can use the defaults. In my actual code I'd probably use a more constrained default than "any" but same idea applies. I propose that TypeScript allow generic type parameters on constructors when they have defaults. I'm afraid I'm not enough of a compiler geek to provide a more detailed proposal, but maybe someone else can help out? |
TypeScript Version: nightly (2.1.0-dev.20160906)
Code
I'm trying to infer a strongly-typed shape for the class instance from the arguments passed to the constructor. There is no runtime shenanigans, this is purely for improved type-checking.
Are the
TS1092
errors in the code at (A) above really necessary, given that the equivalent code at (B) works fine? I'd rather be able to write theWrapper
class directly than use theWrapperConstructor
workaround of a var/interface combination.They do appear to be equivalent typewise, but the (B) version seems a bit hacky. And if the compiler can accept (B), couldn't it also allow (A)?
Or is there a better way to do what I'm trying to do here?
The text was updated successfully, but these errors were encountered: