Skip to content

Conversation

ahejlsberg
Copy link
Member

Fixes #1446.
Fixes #1448.
Fixes #1655.
Fixes #1723.

With this PR the type of an object literal has increased subtype compatibility until it is widened, similar to how we treat null and undefined in expressions.

In #919 we tightened subtyping rules such that optional properties are required to be present in subtypes. This change is desirable because it makes subtyping more selective to the benefit of union types and overload resolution.

However, #919 is too strict in certain situations involving object literals. For example, the object literal { a: "foo" } is not considered a subtype of { a: string; b?: string; } because it is missing the optional member b. That just doesn't seem right. But at the same time it is entirely meaningful to say that { a: string } is a supertype (and not a subtype) of { a: string; b?: string; }.

The interesting fact here is that when an object literal omits optional properties, we can say for certain those optional properties aren't present, and it is therefore safe to consider it a subtype. But once we infer a variable's type from an object literal, that variable might acquire additional properties that we don't know about and we need to be stricter.

And that's the gist of this PR: The type of an object literal is a subtype of a given type T even when it is missing optional properties from T, but once the object literal type is widened to a regular type that is no longer the case.

An example:

interface Item {
    name: string;
    description?: string;
}

declare function foo(item: Item): string;
declare function foo(item: any): number;

var x1 = foo({ name: "Sprocket" });  // x1 of type string
var x2 = foo({ name: "Sprocket", description: "Bumpy wheel" });  // x2 of type string
var x3 = foo({ name: "Sprocket", description: false });  // x3 of type number

var o1 = { name: "Sprocket" };
var o2 = { name: "Sprocket", description: "Bumpy wheel" };
var o3 = { name: "Sprocket", description: false };

var y1 = foo(o1);  // y1 of type number
var y2 = foo(o2);  // y2 of type string
var y3 = foo(o3);  // y3 of type number

Note the difference between x1 and y1. In the x1 case, the object literal argument is considered a subtype of Item. It omitted description so we know for sure description isn't anything else. But in the y1 case, o1 has type { name: string } and might very well have a description property that isn't of type string. So we can't consider it a subtype.

The PR fixes #1446 and provides a better solution to #1723 (effectively removing the fix in #1774). It also fixes #1448 and #1655 which were caused by incorrect tracking of null and undefined types in unwidened types.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 good change here

@JsonFreeman
Copy link
Contributor

To clarify, are we keeping change #1774? I think it would only make a difference for object literals returned from contextually typed function expressions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ContainsObjectLiteral

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, why do you need both ObjectLiteral and ContainsLiteral?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ObjectLiteral flag better enables us to check if a type originated in an object literal (as opposed to contains one). It replaces the old isTypeOfObjectLiteral function that was kind of convoluted.

@ahejlsberg
Copy link
Member Author

@JsonFreeman No, #1774 (and it's funny effects on error reporting) is reverted by this.

@JsonFreeman
Copy link
Contributor

👍

Conflicts:
	src/compiler/checker.ts
	tests/baselines/reference/intTypeCheck.errors.txt
ahejlsberg added a commit that referenced this pull request Jan 27, 2015
Improved subtype compatibility rules for types of object literals
@ahejlsberg ahejlsberg merged commit ba5a612 into master Jan 27, 2015
@ahejlsberg ahejlsberg deleted the objectLiteralWidening branch January 27, 2015 14:48
@microsoft microsoft locked and limited conversation to collaborators Jun 18, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

4 participants