With the following code, and the noImplicitAny flag on, I get the error TS7017: Index signature of object type implicitly has an 'any' type.
interface ICurrency {
[key: string]: any;
currencyId:number;
// ...
}
class Currency implements ICurrency {
public currencyId:number;
// ...
}
let currency = new Currency();
let x = 'currencyId';
console.log(currency[x]); // error
console.log(currency['currencyId']); // ok
Surely that's a bug?
Can't seem to reproduce this. Which version of the compiler are you using?
It looks like I went one step too far in simplifying the problem, and accidentally erased the problem itself :smile:
The issue occurs when using a variable as the key to a property:
let x = 'currencyId';
console.log(currency[x]); // error
I've corrected the original description to reflect that.
I'm using 1.6.2 by the way.
This is an unfortunate inconsistency on our assignability rules vs substitutability in practice.
The index signature [key: string]: any; is somewhat meaningless - it says that you can index this object by a string to get back a value of type any. That is true of _any_ object, it's just a question of whether that any is "implicit" (an error under noImplicitAny), or if it's explicit (because the type has an index signature). So when we test the assignability of a type, the [key: string]: any; declaration is ignored in the target type because it's universally true(ish), which means that the class's implements clause is satisfied even though the class has no explicit (non-implicit-any) index signature.
Depending on your scenario, it might be better to define the index signature as [key: string]: {};, or to add an explicit index signature to Currency (by adding [key: string]: number; to its body).
So, an explicit index signature on an interface becomes an implicit signature in the implementing class. I think I get that. (right?)
I'm fairly new to TypeScript and therefore my thinking is very much from a user's point of view, without knowledge of the internals. And what I don't understand about this issue is the difference it makes when using a string variable as key vs an explicit string value, to access a property. I expect the behaviour to be the same because in both cases the type of key in use is known to be a string. As far as the types are concerned there is no difference in the compiler's knowledge.
The issue does not have to do with the any return value, even if you set a type there it still will not work. Typescript doesn't seem to allow you to access properties via a variable:
interface ICurrency {
[key: string]: string;
currencyId:number;
// ...
}
class Currency implements ICurrency {
public currencyId:number;
// ...
}
let currency = new Currency();
let x = 'currencyId';
console.log(currency[x]); // error
console.log(currency['currencyId']); // ok
@amritk setting the suppressImplicitAnyIndexErrors flag on the compile "fixes" the issue even in the presence of the noImplicitAny flag.
(this begs the question of why this is not always the case when noImplicitAny is set)
An index signature can be defined on the class to constraint the access of properties with string names. i.e:
class Currency implements ICurrency {
public currencyId: string;
[key: string]: string;
}
let currency = new Currency();
let x = "currencyId";
var y = currency[x];// stinrg;
var z = currency["currencyId"];// stinrg;
the index signature on the interface is just not inherited by the class. this is a general distinction between implements and extends. extending a class gives you all its properties and signatures, but implementing an interface, is just a check to make sure the class has a compatible shape, but nothing more.
But [key: string]: string; makes the class lose it's shape by claiming any string as a key. There doesn't seem to be any way to allow a class or interface to retain it's shape while also allowing it's properties to be accessed with a variable EVEN IF that variable is properly typed as the names of the class's properties, like
type word = 'firstName';
const myVar: word = 'firstName';
myInstance[myVar] // (Assuming myInstance has a 'firstName' property) This causes an "implicit 'any'" error :(
Most helpful comment
An index signature can be defined on the class to constraint the access of properties with string names. i.e:
the index signature on the interface is just not inherited by the class. this is a general distinction between implements and extends. extending a class gives you all its properties and signatures, but implementing an interface, is just a check to make sure the class has a compatible shape, but nothing more.