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

Implicit index signatures #7029

Merged
merged 10 commits into from Feb 16, 2016

Conversation

Projects
None yet
9 participants
@ahejlsberg
Copy link
Member

commented Feb 11, 2016

Fixes #6041.

With this PR an object literal type is assignable to a type with an index signature if all known properties in the object literal are assignable to that index signature. This makes it possible to pass a variable that was initialized with an object literal as parameter to a function that expects a map or dictionary:

function httpService(path: string, headers: { [x: string]: string }) { }

const headers = {
    "Content-Type": "application/x-www-form-urlencoded"
};

httpService("", { "Content-Type": "application/x-www-form-urlencoded" });  // Ok
httpService("", headers);  // Now ok, previously wasn't

The PR adds the following three assignment compatibility rules (where an object literal type is the inferred type of an object literal or a type declared using an object type literal with no call or construct signatures):

  • An object literal type S with no index signatures is assignable to a type T with a string index signature M if each property in S is of a type that is assignable to the type of M.
  • An object literal type S with no index signatures is assignable to a type T with a numeric index signature M if each numerically named property in S is of a type that is assignable to the type of M.
  • An object literal type S with no string index signature and a numeric index signature N is assignable to a type T with a string index signature M if the type of N is assignable to the type of M and each property in S is of a type that is assignable to the type of M.

The PR adds corresponding type inference rules.

The PR furthermore removes the rule that adds index signatures to an object literal when it is contextually typed by a type that has index signatures. Instead, the type inferred for an object literal has index signatures only if it contains computed properties. For example:

let s = "hello";
let n = 123;
let o = {
    [s]: new Date(),
    [n]: new Error()
};

In the above, the type of o is { [x: string]: Date | Error, [x: number]: Error }.

Removing the automatic contextual index signatures from object literal types has the nice effect of reducing noise in our error messages. Also, we can now report the name of the offending property when an object literal doesn't meet the constraint implied by a target index signature (previously you'd have to deduce it yourself from two incompatible union types).

@@ -6263,6 +6265,15 @@ namespace ts {
return !!(type.flags & TypeFlags.Tuple);
}

/**
* Return true if type was inferred from an object literal or written as an object type literal

This comment has been minimized.

Copy link
@RyanCavanaugh

RyanCavanaugh Feb 11, 2016

Member

... " with no call/construct signatures"

@RyanCavanaugh

This comment has been minimized.

Copy link
Member

commented Feb 11, 2016

👍

1 similar comment
@sandersn

This comment has been minimized.

Copy link
Member

commented Feb 11, 2016

👍

@@ -0,0 +1,15 @@
tests/cases/compiler/contextualTypeAny.ts(3,5): error TS2322: Type '{ p: string; q: any; }' is not assignable to type '{ [s: string]: number; }'.
Property 'p' is incompatible with index signature.

This comment has been minimized.

Copy link
@DanielRosenwasser

DanielRosenwasser Feb 12, 2016

Member

Can you not report the index signature here? This is probably confusing for users, especially if the index signature type ever gets cut out.

This comment has been minimized.

Copy link
@ahejlsberg

ahejlsberg Feb 12, 2016

Author Member

I dunno. I think it is pretty decent the way it is now and the last line makes it completely clear what isn't compatible with what.

This comment has been minimized.

Copy link
@DanielRosenwasser

DanielRosenwasser Feb 12, 2016

Member

It's not clear what the type of p is without the full type, so it'd be better to just report the index signature given that it'd take relatively little effort on our part.

This comment has been minimized.

Copy link
@RyanCavanaugh

RyanCavanaugh Feb 12, 2016

Member

Agreed, I don't see how it would be better if we didn't show the index signature

This comment has been minimized.

Copy link
@ahejlsberg

ahejlsberg Feb 12, 2016

Author Member

Right, but the whole point of our elaboration scheme is to not try to show everything on one line. The respective types of p and the index signature follow in the next elaboration. It's completely analogous to how we report a mismatch between two properties.

!!! error TS2322: Property 'p' is incompatible with index signature.
!!! error TS2322: Type 'string' is not assignable to type 'number'.

var arr: number[] = ["", x];

This comment has been minimized.

Copy link
@RyanCavanaugh

RyanCavanaugh Feb 12, 2016

Member

It's unfortunate that this is still legal.

This comment has been minimized.

Copy link
@ahejlsberg

ahejlsberg Feb 12, 2016

Author Member

Yeah, would be nice to catch this, but it would require adding discrete properties 0, 1, 2,... to the array which I don't think anyone wants. Or perhaps we could consider giving array literals type any[] only when all elements are of type any and otherwise just ignore the any elements. Basically like saying that any elements in an array literal are presumed to be of the same type(s) as the non-any elements. Not sure how well that would work, though.

ahejlsberg added a commit that referenced this pull request Feb 16, 2016

@ahejlsberg ahejlsberg merged commit a8633ee into master Feb 16, 2016

2 checks passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details
continuous-integration/travis-ci/push The Travis CI build passed
Details

@ahejlsberg ahejlsberg deleted the implicitIndexSignatures branch Feb 16, 2016

@RyanCavanaugh

This comment has been minimized.

Copy link
Member

commented Feb 17, 2016

Sad that I'm not going to rake in 30-100 Stack Overflow rep every week for my answer on this one anymore.

@MrHen

This comment has been minimized.

Copy link

commented Feb 17, 2016

@tinganho

This comment has been minimized.

Copy link
Contributor

commented Feb 25, 2016

I just tried this out. Shouldn't a type parameter inherit the index signature?

I have the following class:

export abstract class Component<P, T extends { [index: string]: string }, E extends { [element: string]: DOMElement }> extends EventEmitter {

And I was hoping I could just write:

export abstract class MyComponent<P, T, E> extends Component<P, T, E> {

And it would just inherit the index signature. But instead I need to explicitly write the constrained index signature. Which is quite painful, since I have many subclasses of Component.

@tinganho

This comment has been minimized.

Copy link
Contributor

commented Feb 25, 2016

Ok seems like this PR doesn't work for type arguments? Just passing a type, as a type argument, with all members satisfying the index signature doesn't work for me.

@JabX

This comment has been minimized.

Copy link

commented Feb 26, 2016

I would also love to have what @tinganho wants.

My use case is the following:

interface ITest {
    [x: string]: string
}

class Test<T extends ITest> {
    content: T
}

const myObject = {
    test: "hello"
};

class MyTest extends Test<typeof myObject> {
    // Error, 'typeof myObject' doesn't implement the index signature...
    method() {
        console.log(this.content.test)
    }
}

I'm planning to do this extensively with model-generated object litterals (like myObject here) and it'd be nice not to have to also generate the corresponding interface for all those litterals... it could work out though, so I guess it's not that big of an issue.

If you want to keep forcing the index signature to be respecified in a derived interface, maybe then my issue could be fixed by typeof infering an index signature if applicable, if that's not too stupid of an idea.

mmiszy referenced this pull request in DefinitelyTyped/DefinitelyTyped Mar 22, 2016

@whitecolor

This comment has been minimized.

Copy link

commented Mar 20, 2017

Asked this in #14736

Why this case:

function httpService(path: string, headers: { [x: string]: string }) { }

interface MyHeaders {
  'Content-Type': string
}

const headers: MyHeaders = {
    "Content-Type": "application/x-www-form-urlencoded"
};

httpService("", { "Content-Type": "application/x-www-form-urlencoded" });  // Ok
httpService("", headers);  // NOT OK

is NOT valid, the error is:

Argument of type 'MyHeaders' is not assignable to parameter of type '{ [x: string]: string; }'.
  Index signature is missing in type 'MyHeaders'.

@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
You can’t perform that action at this time.