TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

[Bug] Mixin parameters failed to use as Method Parameter types of Inner Classes

Open TechQuery opened this issue 3 years ago • 8 comments

🔎 Search Terms

mixin, parameter, class method, type declaration, decorator

🕗 Version & Regression Information

  • This is a crash
  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about mixin & parameter

⏯ Playground Link

Playground link with relevant code

💻 Code

const register: MethodDecorator = () => { }

const validate: ParameterDecorator = () => { }

function mixin<T>(Model: new (...data: any[]) => T) {
    class Trait {
        @register
        method(@validate input: Model) { }
    }
    return Trait;
}

class TestModel {
    id = 0
}

class TestController extends mixin(TestModel) { }

🙁 Actual behavior

Type checking crashed

error TS2749: 'Model' refers to a value, but is being used as a type here. Did you mean 'typeof Model'?

         method(@validate input: Model) {
                                 ~~~~~

Compiled seems to work

function mixin(Model) {
    var _a;
    class Trait {
        method(input) { }
    }
    __decorate([
        register,
        __param(0, validate),
        __metadata("design:type", Function),
        __metadata("design:paramtypes", [typeof (_a = typeof Model !== "undefined" && Model) === "function" ? _a : Object]),
        __metadata("design:returntype", void 0)
    ], Trait.prototype, "method", null);
    return Trait;
}

🙂 Expected behavior

Shown sample code works, so that projects with Decorator frameworks will be much simpler, like what my service scaffold does: idea2app/REST-Node-ts#1

TechQuery avatar Sep 15 '22 19:09 TechQuery

The error message is correct. Model refers to a value of a function parameter and you should use typeof to refer to the type of it. See https://www.typescriptlang.org/docs/handbook/2/typeof-types.html.

whzx5byb avatar Sep 15 '22 19:09 whzx5byb

The error message is correct. Model refers to a value of a function parameter and you should use typeof to refer to the type of it. See https://www.typescriptlang.org/docs/handbook/2/typeof-types.html.

I know that typeof is the "correct syntax", but if I change it like that, Parameter Decorator will failed to get emited classes:

const register: MethodDecorator = () => { }

const validate: ParameterDecorator = () => { }

function mixin<T>(Model: new (...data: any[]) => T) {
    class Trait {
        @register
-        method(@validate input: Model) { }
+        method(@validate input: typeof Model) { }
    }
    return Trait;
}

class TestModel {
    id = 0
}

class TestController extends mixin(TestModel) { }
function mixin(Model) {
    var _a;
    class Trait {
        method(input) { }
    }
    __decorate([
        register,
        __param(0, validate),
        __metadata("design:type", Function),
-        __metadata("design:paramtypes", [typeof (_a = typeof Model !== "undefined" && Model) === "function" ? _a : Object]),
+        __metadata("design:paramtypes", [Object]),
        __metadata("design:returntype", void 0)
    ], Trait.prototype, "method", null);
    return Trait;
}

TechQuery avatar Sep 15 '22 19:09 TechQuery

I think this workaround should work:

function mixin<T>(Model: new (...data: any[]) => T) {
+    type Model = typeof Model;
    
    class Trait {
        @register
        method(@validate input: Model) { }
    }
    return Trait;
}

whzx5byb avatar Sep 15 '22 19:09 whzx5byb

@whzx5byb Thanks your hack code, but I wonder that class syntax is both type & value, why should I use typeof in mixins?

TechQuery avatar Sep 15 '22 20:09 TechQuery

@whzx5byb If I use both type & value of classes, your hack code shows a type error in my VS Code 1.71.0:

- const register: MethodDecorator = () => { }
+ const register: (Model: new (...data: any[]) => any) => MethodDecorator =
    () => () => { }

const validate: ParameterDecorator = () => { }

function mixin<T>(Model: new (...data: any[]) => T) {
+    type Model = typeof Model;

    class Trait {
-        @register
+        @register(Model)
        method(@validate input: Model) { }
    }
    return Trait;
}

class TestModel {
    id = 0
}

class TestController extends mixin(TestModel) { }
ts[2502] Model is referenced directly or indirectly in its own type annotation. 

but tsc compiling with the same [email protected] in node_modules works fine...

TS playground & GitPod's VS Code 1.69.2 works fine, too...

TechQuery avatar Sep 15 '22 20:09 TechQuery

@TechQuery

why should I use typeof in mixins?

A class declaration does create value and type entities but a variable only create the value one. In your case Model is just a variable refers to the class, so it doesn't create a type entity.

If I use both type & value of classes, your hack code shows a type error in my VS Code 1.71.0

I believe this is a bug, which is similar to https://github.com/microsoft/TypeScript/issues/50191 and https://github.com/microsoft/TypeScript/issues/50161. I have reported it in https://github.com/microsoft/TypeScript/issues/50795.

whzx5byb avatar Sep 15 '22 20:09 whzx5byb

Minimal reproduction.

const A: new() => any = class B {}
function validate(...a: any[]) {}
class B {
    @validate test(a: typeof A) {}
}

compiles to

image

It looks like the behavior of typeof Expr is not handled properly in decorator metadata.

HerringtonDarkholme avatar Sep 15 '22 21:09 HerringtonDarkholme

@whzx5byb Thanks your explanation & issue reporting.

When I open my GitPod VS Code again, the same error hints...

Just like #50161 said: sometimes...

TechQuery avatar Sep 15 '22 21:09 TechQuery