Skip to content
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

assignabiltity between interfaces and types #14736

Closed
wclr opened this issue Mar 19, 2017 · 8 comments
Closed

assignabiltity between interfaces and types #14736

wclr opened this issue Mar 19, 2017 · 8 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@wclr
Copy link

wclr commented Mar 19, 2017

TypeScript Version: 2.2.1 / nightly (2.3.0)

I believe my issue can be narrowed down:

export interface Params {
  [name: string]: string
}

function doSmth(params: Params) { }

interface MyParams {
  prop: string
}

let myParams: MyParams = { prop: 'X' }

let params: Params = myParams // NOT OK

Actual behavior:

Type 'MyParams' is not assignable to type 'Params'.
  Index signature is missing in type 'MyParams'.

if to replace interface with type there is no error:

type MyParams = {
  prop: string
}
@ahejlsberg
Copy link
Member

This is working as intended. See #7029 for the rationale.

@ahejlsberg ahejlsberg added the Working as Intended The behavior described is the intended behavior; this is not a bug label Mar 19, 2017
@wclr
Copy link
Author

wclr commented Mar 19, 2017

@ahejlsberg
Can you please explain, what is wrong with this particular case of usage? How is it supposed to be fixed correctly? Is type should be used for this case instead of interface? Why?

@kitsonk
Copy link
Contributor

kitsonk commented Mar 20, 2017

MyParams interface needs an index signature that is assignable to Params.

I believe the justification, reading #7029, is that a type is more like a type derived from an object literal, and therefore is assignable without an explicit index. This is to keep things more "usable" in TypeScript, but when using an interface, you are being more explicit about the typing and so the assignability does not work and index signature assignability is enforced.

@wclr
Copy link
Author

wclr commented Mar 20, 2017

MyParams interface needs an index signature that is assignable to Params.

I see this. I've modified the code to narrow the issue down. I need to understand more about differences between interface and type in terms of the issue and in general.

Is there a strong recommendations where it is more appropriate to use interface or type for defining object shape?

@wclr wclr changed the title extends constrains with an interface error assignabiltity between interfaces and types Mar 20, 2017
@kitsonk
Copy link
Contributor

kitsonk commented Mar 20, 2017

Back in my time we didn't have type, though the internals of the compiler obviously had access to the concept. I don't think there are any formal guidelines, so I will give my personal opinion.

type is a way to name a type that you are going to re-use multiple places and almost always contains some sort of type operator (e.g. a union or intersection operator). This then creates a shorthand (or reusable) type name that can be used in a type location.

interface is the way to specify an abstract shape of an Object, Function or Class which will be the formal contract that people will consume your code with. Classes (or constructor Functions) have two interfaces, the constructor and the instance interfaces.

@wclr
Copy link
Author

wclr commented Mar 20, 2017

@kitsonk Ok thanks for the explanation.

@ahejlsberg
Copy link
Member

Generally, given a type T with an index signature, a type S is assignable to T only if S has a compatible index signature. This rule ensures in a transitive manner that S is as least as constrained as T. Consider:

interface Params {
    [x: string]: string;
}

interface MyParams {
    log: string;
}

const logWithNumber = {
    log: "foo",
    count: 42
};

let myParams: MyParams = logWithNumber;  // Ok, source permitted to have additional properties
let params: Params = myParams;  // Error

Without the rule that requires a matching index signatures, the last line in the example above wouldn't be an error and we would have violated the index signature constraint of the target. Effectively, because of subtype substitutability, an instance of an interface type without an index signature may have additional properties that we know nothing about.

However, when it comes to object literals, we do know that they don't have additional properties. Therefore, when the source is an object literal is it safe to permit assignment as long as all of the properties meet the constraint of the target. Now, the way we track that something is an object literal is by checking that its type originated in an object literal expression or an object literal type. The latter technically doesn't guarantee anything about additional properties (as you've discovered) but is included such that anything that applies to an inferred object literal type also applies to an explicitly specified object literal type. So it's a compromise we have to make for object literal types, but not for interface types.

@wclr
Copy link
Author

wclr commented Mar 20, 2017

So I probably may close this one. Thanks for explanation.

@wclr wclr closed this as completed Mar 20, 2017
@microsoft microsoft locked and limited conversation to collaborators Jun 21, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

3 participants