1

I'm trying to understand the extent of the typesafety offered by Typescript. I've come across a scenario where I expect an error, but Typescript doesn't complain.

I've defined a function with a parameter matching a certain interface. Then I call the function with some arguments which don't match. Here's the code (or in playground):

interface ArgumentInterface {
    [key: number]: string
}

interface InvalidArgumentInterface {
    [key: string]: number
}

interface InvalidArgumentInterface2 {
    foo: number
}

function myFunction(arg: ArgumentInterface) {
    // function body
}

let validArgument: ArgumentInterface = {};
validArgument[5] = 'I am a string';

let invalidArgument: InvalidArgumentInterface = {
    foo: 42
};

let invalidArgument2: {foo: number} = {
    foo: 42
};

let invalidArgument3: InvalidArgumentInterface2 = {
    foo: 42
};

let invalidArgument4 = {
    foo: 42
};

myFunction(validArgument); // no typescript error, as expected
myFunction(invalidArgument); // typescript error, as expected
myFunction(invalidArgument2); // no typescript error!
myFunction(invalidArgument3); // typescript error, as expected
myFunction(invalidArgument4); // no typescript error!

When my argument variable explicitly declares an incompatible interface, I get a Typescript error as expected. But when my argument variable declares a type literal (without an interface) or declares no type at all, Typescript doesn't complain at all, although I'd expect an error.

I have the "noImplicitAny" flag set to true.

Can anybody explain this behavior?

2 Answers 2

2

You're not getting an error for:

myFunction(invalidArgument2);
myFunction(invalidArgument4);

Because their type is { foo: number; } and it doesn't contradict the definition of ArgumentInterface, a value can be both:

let a = {
    1: "one",
    2: "two",
    foo: 4
}

Here what's indexed with a number has a string value, but the foo index has a number value.

if ArgumentInterface is:

interface ArgumentInterface {
    [key: number]: string;
    [key: string]: string;
}

Then you get the errors where you expected them.

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

2 Comments

I think I've discovered what I misunderstood: defining an index signature for [key: number] doesn't prevent adding string keys, it only restricts the value type if the key is a number ? Is that correct?
Yes, that seems the case. But it doesn't work the other way around. Also, you should be aware that key in a js object are always strings. If you pass a number it will be converted into a string. If you really want a map between number and strings use a Map
1

This is because TypeScript uses structural typing (like Ocaml).

invalidArgument2 and invalidArgument4 are both structurally compatible to ArgumentInterface, and as such, TypeScript happily accepts them.

3 Comments

No, they are not equivalent. ArgumentInterface is of type { [k: number]: string } while the other two are of type { foo: number }. Those are different structures.
replaced the word with "compatible", which is used in the docs I link to.
Well, compatible makes more sense. Though, I'd say that there are no conflicts between the two.

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.