6

I'm writing TypeScript in Visual Studio 2015, with version 2.3.3.0 of the language service extension installed. I have the noImplicitAny parameter set to true in my project's tsconfig.json.

Given this simple example code:

interface ITransformer<TInput, TOutput> {
    transform(input: TInput): TOutput;
}

class Input {
    name: string;
}

class Output {
    name: string;

    constructor(name: string) {
        this.name = name;
    }
}

class Test {
    name: string;
}

class Transformer implements ITransformer<Input, Output> {
    transform = (input) => new Output(input.name);
}

The TS compiler gives me an error: TS7006: Parameter 'input' implicitly has an 'any' type:

enter image description here

Now, I can easily fix this error by adding a type annotation to the input parameter:

class Transformer implements ITransformer<Input, Output> {
    transform = (input: Input) => new Output(input.name);
}

But my question is, why should I have to? It seems to me that the type of that parameter should be inferred from the type of TInput in the implementation of the interface (in this case, Input).

More worryingly, I can happily do this:

class Transformer implements ITransformer<Input, Output> {
    transform = (input: Test) => new Test();
}

Which accepts and returns a completely different type not referred to by either of the type parameters, and the compiler seems to have no problem with it...

Coming from a C# background, this just seems wrong. What am I missing?

0

1 Answer 1

7

When implementing the ITransformer interface you can override transform with different signatures, for example:

class Transformer implements ITransformer<Input, Output> {
    transform(input: string): Output;
    transform(input: number): Output;
    transform(input: Input): Output;
    transform(input: any): Output {
        // ...
    }
}

You are not forced to only have an implementation for the signature in the interface. Because of that, you have to implicitly write the type:

transform = (input: Input) => new Output(input.name);

As for why this works:

transform = (input: Test) => new Test();

That's because TypeScript is based on structural subtyping and the two types have the same structure ({ name: string }). Try to add another property to just one of them and you'll get an error.

The last thing I want to point out is that the way you define class methods won't actually create methods, but just members with a function type:

class Test {
    method1() { }

    method2 = () => {}
}

Here only method1 is really a method, while method2 is just a property which is assigned in the constructor, it won't be part of the prototype and you won't be able to override it if you extend the class.

Here's the compiled version of this code:

var Test = (function () {
    function Test() {
        this.method2 = function () { };
    }
    Test.prototype.method1 = function () { };
    return Test;
}());

This approach is fine and is being used, but just be aware.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks, that all makes much more sense now! So let me see if I understand the structural subtyping part: although it's possible to implement the transform method from the interface with a different parameter type than that specified in the interface signature, it will only actually compile if the alternative type has exactly the same properties as the interface signature type. In which case, the property signatures of the different objects are identical in the compiled JavaScript, so no runtime errors can occur. Do I have that right?
You're mixing two different issues. You can have different overloads for the method declared in the interface as long as one of those signatures matches the one in the interface. As for the structural subtyping issue, it's how the compiler compares types. Two types are the same as long as they match in properties. Because of that, at runtime the properties (and their types) are the same so it should not cause any errors.
My comment was written under the assumption that there is only one method overload in the implementing class (as in my example), but I do see what you mean now. Anyway, the last part of your comment is basically confirming the last part of mine, so thanks for helping me understand.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.