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

Strict object literal assignment checking #3823

Merged
merged 23 commits into from
Jul 21, 2015
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
cd0d3ba
Object literals can only have properties that exist in contextual type
ahejlsberg Jul 9, 2015
11aecee
Switch to assignability check and fix compiler bugs found by check
ahejlsberg Jul 11, 2015
3fe7591
Accepting new baselines
ahejlsberg Jul 11, 2015
f57991e
Support union and intersection types in checks
ahejlsberg Jul 11, 2015
aa26980
Addressing CR feedback
ahejlsberg Jul 13, 2015
d78fa18
Ignore freshness in subtype reduction / Treat Object as {}
ahejlsberg Jul 13, 2015
2eca3d5
Fixing bug in test
ahejlsberg Jul 13, 2015
c7b0732
Accepting new baselines
ahejlsberg Jul 13, 2015
c42b8f7
New deduplication algorithm for union types
ahejlsberg Jul 15, 2015
d34557a
Deduplication of tuple types in unions
ahejlsberg Jul 16, 2015
1a4252d
Updating fourslash tests
ahejlsberg Jul 16, 2015
7dbb69a
Accepting new baselines
ahejlsberg Jul 16, 2015
c8423d3
Getting rid of subtype reduction for union types
ahejlsberg Jul 16, 2015
eeeb05b
Fixing fourslash tests
ahejlsberg Jul 16, 2015
acd8c77
Accepting new baselines
ahejlsberg Jul 16, 2015
a05ebc4
Preserve order in union types
ahejlsberg Jul 19, 2015
3cbc3db
Fixing fourslash tests
ahejlsberg Jul 19, 2015
592319d
Accepting new baselines
ahejlsberg Jul 19, 2015
2913cb0
Merge branch 'master' into strictObjectLiterals
ahejlsberg Jul 19, 2015
155ee4b
Better error message
ahejlsberg Jul 20, 2015
967df39
Accepting new baselines
ahejlsberg Jul 20, 2015
57f1a99
Adding comma in error message
ahejlsberg Jul 21, 2015
5f7bc51
Accepting new baselines
ahejlsberg Jul 21, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 82 additions & 14 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1966,15 +1966,12 @@ namespace ts {
}

return _displayBuilder || (_displayBuilder = {
symbolToString: symbolToString,
typeToString: typeToString,
buildSymbolDisplay: buildSymbolDisplay,
buildTypeDisplay: buildTypeDisplay,
buildTypeParameterDisplay: buildTypeParameterDisplay,
buildParameterDisplay: buildParameterDisplay,
buildDisplayForParametersAndDelimiters: buildDisplayForParametersAndDelimiters,
buildDisplayForTypeParametersAndDelimiters: buildDisplayForTypeParametersAndDelimiters,
buildDisplayForTypeArgumentsAndDelimiters: buildDisplayForTypeArgumentsAndDelimiters,
buildTypeParameterDisplayFromSymbol: buildTypeParameterDisplayFromSymbol,
buildSignatureDisplay: buildSignatureDisplay,
buildReturnTypeDisplay: buildReturnTypeDisplay
Expand Down Expand Up @@ -3355,6 +3352,29 @@ namespace ts {
return undefined;
}

// Check if a property with the given name is known anywhere in the given type. In an object
// type, a property is considered known if the object type is empty, if it has any index
// signatures, or if the property is actually declared in the type. In a union or intersection
// type, a property is considered known if it is known in any constituent type.
function isKnownProperty(type: Type, name: string): boolean {
if (type.flags & TypeFlags.ObjectType) {
var resolved = resolveStructuredTypeMembers(type);
return !!(resolved.properties.length === 0 ||
resolved.stringIndexType ||
resolved.numberIndexType ||
Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, so you do these checks on the target side as opposed to when contextually typing the object literal. Actually, this is probably a good thing, because not every assignability check is guaranteed to be accompanied by contextual typing. So if the object did not get contextually typed, the assignment would still be allowed, which is good I think.

Copy link
Contributor

Choose a reason for hiding this comment

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

But I would add some comments explaining the rationale behind these heuristics.

getPropertyOfType(type, name));
}
if (type.flags & TypeFlags.UnionOrIntersection) {
for (let t of (<UnionOrIntersectionType>type).types) {
if (isKnownProperty(t, name)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I agree that this loop is doing the right thing for intersections. For unions, I would not say that a property is known if some constituent has it. Doesn't it seem more correct to demand that all union constituents have the property?

Actually, this does make sense. Really you're trying to figure out if you have heard of this property anywhere in the target. If you have, then it would not be considered excess in the source. So it's not that the target is known to have this property, it's that this property was mentioned as a potential property of the target. This also why optional property are "known" even though they might not actually be present on the target.

Copy link
Member Author

Choose a reason for hiding this comment

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

Exactly.

return true;
}
}
return false;
}
return true;
}

function getSignaturesOfStructuredType(type: Type, kind: SignatureKind): Signature[] {
if (type.flags & TypeFlags.StructuredType) {
let resolved = resolveStructuredTypeMembers(<ObjectType>type);
Expand Down Expand Up @@ -4480,6 +4500,16 @@ namespace ts {
errorInfo = chainDiagnosticMessages(errorInfo, message, arg0, arg1, arg2);
}

function reportRelationError(message: DiagnosticMessage, source: Type, target: Type) {
let sourceType = typeToString(source);
let targetType = typeToString(target);
if (sourceType === targetType) {
sourceType = typeToString(source, /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType);
targetType = typeToString(target, /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType);
}
reportError(message || Diagnostics.Type_0_is_not_assignable_to_type_1, sourceType, targetType);
}

// Compare two types and return
// Ternary.True if they are related with no assumptions,
// Ternary.Maybe if they are related with assumptions of other relationships, or
Expand All @@ -4499,7 +4529,23 @@ namespace ts {
if (source === numberType && target.flags & TypeFlags.Enum) return Ternary.True;
}
}

if (relation !== identityRelation && source.flags & TypeFlags.FreshObjectLiteral) {
if (hasExcessProperties(<FreshObjectLiteralType>source, target, reportErrors)) {
if (reportErrors) {
reportRelationError(headMessage, source, target);
}
return Ternary.False;
}
// Above we check for excess properties with respect to the entire target type. When union
// and intersection types are further deconstructed on the target side, we don't want to
// make the check again (as it might fail for a partial target type). Therefore we obtain
// the regular source type and proceed with that.
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks. This makes sense now

source = getRegularTypeOfObjectLiteral(source);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is it necessary to do this?

Copy link
Member Author

Choose a reason for hiding this comment

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

Because we otherwise will fail in cases like this:

interface A { a: number }
interface B { b: number }
var x: A & B = { a: 1, b: 2 };

We make the check upfront for the entire target type, but then as we descend into the structure of the target type we no longer want to make the check again (as it would now fail).

}

let saveErrorInfo = errorInfo;

if (source.flags & TypeFlags.Reference && target.flags & TypeFlags.Reference && (<TypeReference>source).target === (<TypeReference>target).target) {
// We have type references to same target type, see if relationship holds for all type arguments
if (result = typesRelatedTo((<TypeReference>source).typeArguments, (<TypeReference>target).typeArguments, reportErrors)) {
Expand Down Expand Up @@ -4576,18 +4622,22 @@ namespace ts {
}

if (reportErrors) {
headMessage = headMessage || Diagnostics.Type_0_is_not_assignable_to_type_1;
let sourceType = typeToString(source);
let targetType = typeToString(target);
if (sourceType === targetType) {
sourceType = typeToString(source, /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType);
targetType = typeToString(target, /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType);
}
reportError(headMessage, sourceType, targetType);
reportRelationError(headMessage, source, target);
}
return Ternary.False;
}

function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean {
for (let prop of getPropertiesOfObjectType(source)) {
if (!isKnownProperty(target, prop.name)) {
if (reportErrors) {
reportError(Diagnostics.Property_0_does_not_exist_on_type_1, symbolToString(prop), typeToString(target));
Copy link
Member

Choose a reason for hiding this comment

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

Given that we already have the specific codepath and context here to give a better error message I'd really like to see something more specific and distinct from a normal assignability error. I fully expect to see bug reports/Stack Overflow questions on this feature if the error simply looks like any other assignability error that changes when you add/remove temps in your chain of assignments. Minimally, a unique enough message that you could put in a search engine and get a distinct StackOverflow answer. Ideally, a message that actually communicates why assignments of this form differ from a regular assignment to a sufficient degree that you don't need to switch to StackOverflow to understand the error you just made.

}
return true;
}
}
}

function eachTypeRelatedToSomeType(source: UnionOrIntersectionType, target: UnionOrIntersectionType): Ternary {
let result = Ternary.True;
let sourceTypes = source.types;
Expand Down Expand Up @@ -5255,6 +5305,24 @@ namespace ts {
return (type.flags & TypeFlags.Tuple) && !!(<TupleType>type).elementTypes;
}

function getRegularTypeOfObjectLiteral(type: Type): Type {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would just make this take a FreshObjectLiteralType

Copy link
Member Author

Choose a reason for hiding this comment

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

No, the point of the method is to remove "freshness" from the type regardless of the kind of type. I suppose we could call it getRegularTypeOfType.

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, that sounds fine

if (type.flags & TypeFlags.FreshObjectLiteral) {
let regularType = (<FreshObjectLiteralType>type).regularType;
if (!regularType) {
regularType = <ResolvedType>createType((<ResolvedType>type).flags & ~TypeFlags.FreshObjectLiteral);
regularType.symbol = (<ResolvedType>type).symbol;
regularType.members = (<ResolvedType>type).members;
regularType.properties = (<ResolvedType>type).properties;
regularType.callSignatures = (<ResolvedType>type).callSignatures;
regularType.constructSignatures = (<ResolvedType>type).constructSignatures;
regularType.stringIndexType = (<ResolvedType>type).stringIndexType;
regularType.numberIndexType = (<ResolvedType>type).numberIndexType;
}
return regularType;
}
return type;
}

function getWidenedTypeOfObjectLiteral(type: Type): Type {
let properties = getPropertiesOfObjectType(type);
let members: SymbolTable = {};
Expand Down Expand Up @@ -6886,7 +6954,7 @@ namespace ts {
let stringIndexType = getIndexType(IndexKind.String);
let numberIndexType = getIndexType(IndexKind.Number);
let result = createAnonymousType(node.symbol, propertiesTable, emptyArray, emptyArray, stringIndexType, numberIndexType);
result.flags |= TypeFlags.ObjectLiteral | TypeFlags.ContainsObjectLiteral | (typeFlags & TypeFlags.ContainsUndefinedOrNull);
result.flags |= TypeFlags.ObjectLiteral | TypeFlags.FreshObjectLiteral | TypeFlags.ContainsObjectLiteral | (typeFlags & TypeFlags.ContainsUndefinedOrNull);
return result;

function getIndexType(kind: IndexKind) {
Expand Down Expand Up @@ -8767,7 +8835,7 @@ namespace ts {
}

function checkAssertion(node: AssertionExpression) {
let exprType = checkExpression(node.expression);
let exprType = getRegularTypeOfObjectLiteral(checkExpression(node.expression));
let targetType = getTypeFromTypeNode(node.type);
if (produceDiagnostics && targetType !== unknownType) {
let widenedType = getWidenedType(exprType);
Expand Down Expand Up @@ -9544,7 +9612,7 @@ namespace ts {
return getUnionType([leftType, rightType]);
case SyntaxKind.EqualsToken:
checkAssignmentOperator(rightType);
return rightType;
return getRegularTypeOfObjectLiteral(rightType);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does an assignment expression give the regular type of the right as opposed to the original type? I thought the type that gets assigned to the left is the regular type, but the type of the expression should be the original right type.

Copy link
Member Author

Choose a reason for hiding this comment

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

I added this because of code similar to the following in tsserver:

interface A { a: number }
interface B extends A { b: number }
var last: B;
function foo(): A {
    return last = { a: 1, b: 2 };
}

Once the object literal is successfully assigned to a variable it seems pedantic to insist that b is an unknown property. And, really, following an assignment, the object literal isn't "fresh" anymore.

Copy link
Contributor

Choose a reason for hiding this comment

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

My reasoning was that assigning it to last only renders the object "not fresh" if you refer to it via last. In this case, you are still returning the object literal, and it has excess properties with respect to A.

Maybe we could rationalize it by saying that if we tried to assign it to last and it had excess properties relative to last, then we would have already given an error. But then again, this seems weird when you consider overload resolution.

Using the same A and B that you've defined:

declare function foo(param: A): A;
declare function foo(param: B): B;
var last: B;

foo({a: 1, b: 2 }); // returns B
foo(last = {a: 1, b: 2 }); // returns A

I don't think taking the regular type here makes sense.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm. Contextual typing only comes from the first assignment target, so we're currently consistent with that. I suppose you could argue we should check against a union of all assignment targets, i.e. as long as each property is known in some assignment target we're good. But that would add a bunch of complexity that I don't think is justified.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we would need to explicitly check against the union of all assignment targets. Each assignment target does an assignability check, and if a certain source type passes all the assignability checks, it's good. So the correct behavior should just fall out if we remove the call to getRegularTypeOfObjectLiteral, no?

It's true that contextual typing only comes from the first target, but I'm not sure why contextual typing is relevant. We chose to build this check into a mechanism other than contextual typing, so I don't see why we would consider contextual typing a factor here.

case SyntaxKind.CommaToken:
return rightType;
}
Expand Down
17 changes: 14 additions & 3 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1743,10 +1743,12 @@ namespace ts {
FromSignature = 0x00040000, // Created for signature assignment check
ObjectLiteral = 0x00080000, // Originates in an object literal
/* @internal */
ContainsUndefinedOrNull = 0x00100000, // Type is or contains Undefined or Null type
FreshObjectLiteral = 0x00100000, // Fresh object literal type
Copy link
Contributor

Choose a reason for hiding this comment

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

Please define fresh object literal in the comment.

Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need a new flag here? This is set in the same place as ObjectLiteral. And when you get the regular type, you turn off FreshObjectLiteral, but not ObjectLiteral, and I don't really understand why. So a regular type is allowed to have excess properties, but it still does not need to have all the optional properties of the target in a subtype check?

Copy link
Member Author

Choose a reason for hiding this comment

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

Well, it's related to the issue of removing freshness after the first assignment, but still remembering that the source was an object literal. It may be that we can combine the two if we give up on the first assignment bit.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yup, I think we should give up the first assignment bit. I think it is a strange rule.

Copy link
Member Author

Choose a reason for hiding this comment

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

So, I don't think we can get rid of the new flag. We use TypeFlag.ObjectLiteral to indicate that a type originated in an object literal and may contain null or undefined types. We can't turn off that flag unless we also widen the type. Yet, in type assertions and during subtype reduction we want to turn off freshness but not widen.

That said, we could still drop the assignment rule. They're really orthogonal issues.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, let's drop the assignment rule.

I understand what you mean about TypeFlags.ObjectLiteral, but I still think we can use it. We actually use ContainsUndefinedOrNull and ContainsObjectLiteral for what you are talking about, not ObjectLiteral directly. For type assertions, I think it's fine to widen, we already do in the downcast direction, and I think it's fine to do it for the upcast (it uses assignability so we should be fine). For creation of a union type, we already give it TypeFlags.Union plus all the widening flags (which do not include ObjectLiteral anyway). So all the flags that getWidenedType checks for would still be there.

So I think it is safe to remove it.

Copy link
Contributor

Choose a reason for hiding this comment

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

To elaborate about the type assertion case, widening would essentially do two things:

  1. null and undefined become any. They are bottom types anyway, so changing them to any should have no effect.
  2. The object literal is changed with respect to the optional-properties-being-required rule. This rule only applies to subtype, and type assertions use assignability.

Copy link
Contributor

Choose a reason for hiding this comment

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

I admit it's still possible that I missed something, but if I did, I'd like to understand it before allowing us to have two flags that sound really similar and are easy to confuse.

/* @internal */
ContainsObjectLiteral = 0x00200000, // Type is or contains object literal type
ESSymbol = 0x00400000, // Type of symbol primitive introduced in ES6
ContainsUndefinedOrNull = 0x00200000, // Type is or contains Undefined or Null type
/* @internal */
ContainsObjectLiteral = 0x00400000, // Type is or contains object literal type
ESSymbol = 0x00800000, // Type of symbol primitive introduced in ES6

/* @internal */
Intrinsic = Any | String | Number | Boolean | ESSymbol | Void | Undefined | Null,
Expand Down Expand Up @@ -1839,6 +1841,14 @@ namespace ts {
numberIndexType?: Type; // Numeric index type
}

/* @internal */
// Object literals are initially marked fresh. Freshness disappears following an assignment,
// before a type assertion, or when when an object literal's type is widened. The regular
// version of a fresh type is identical except for the TypeFlags.FreshObjectLiteral flag.
export interface FreshObjectLiteralType extends ResolvedType {
regularType: ResolvedType; // Regular version of fresh type
Copy link
Contributor

Choose a reason for hiding this comment

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

Does regular mean not fresh? Are they opposites?

Copy link
Member Author

Choose a reason for hiding this comment

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

They are exactly the same, except the TypeFlags.FreshObjectLiteral flag is set in the fresh version.

}

// Just a place to cache element types of iterables and iterators
/* @internal */
export interface IterableOrIteratorType extends ObjectType, UnionType {
Expand Down Expand Up @@ -2189,6 +2199,7 @@ namespace ts {

export interface CompilerHost {
getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile;
getCancellationToken?(): CancellationToken;
getDefaultLibFileName(options: CompilerOptions): string;
writeFile: WriteFileCallback;
getCurrentDirectory(): string;
Expand Down
3 changes: 1 addition & 2 deletions src/harness/fourslash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,8 @@ module FourSlash {
export interface FourSlashFile {
// The contents of the file (with markers, etc stripped out)
content: string;

fileName: string;

version: number;
// File-specific options (name/value pairs)
fileOptions: { [index: string]: string; };
}
Expand Down
1 change: 1 addition & 0 deletions src/harness/loggedIO.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface FindFileResult {
}

interface IOLog {
timestamp: string;
arguments: string[];
executingPath: string;
currentDirectory: string;
Expand Down
4 changes: 1 addition & 3 deletions src/server/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,9 +202,7 @@ namespace ts.server {
return {
isMemberCompletion: false,
isNewIdentifierLocation: false,
entries: response.body,
fileName: fileName,
position: position
entries: response.body
};
}

Expand Down
5 changes: 4 additions & 1 deletion src/services/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1102,6 +1102,7 @@ namespace ts {
}

export interface HighlightSpan {
fileName?: string;
textSpan: TextSpan;
kind: string;
}
Expand Down Expand Up @@ -1408,7 +1409,9 @@ namespace ts {
* @param fileName The name of the file to be released
* @param compilationSettings The compilation settings used to acquire the file
*/
releaseDocument(fileName: string, compilationSettings: CompilerOptions): void
releaseDocument(fileName: string, compilationSettings: CompilerOptions): void;

reportStats(): string;
}

// TODO: move these to enums
Expand Down
4 changes: 2 additions & 2 deletions src/services/shims.ts
Original file line number Diff line number Diff line change
Expand Up @@ -428,11 +428,11 @@ namespace ts {
}
}

export function realizeDiagnostics(diagnostics: Diagnostic[], newLine: string): { message: string; start: number; length: number; category: string; } []{
export function realizeDiagnostics(diagnostics: Diagnostic[], newLine: string): { message: string; start: number; length: number; category: string; code: number; } []{
return diagnostics.map(d => realizeDiagnostic(d, newLine));
}

function realizeDiagnostic(diagnostic: Diagnostic, newLine: string): { message: string; start: number; length: number; category: string; } {
function realizeDiagnostic(diagnostic: Diagnostic, newLine: string): { message: string; start: number; length: number; category: string; code: number; } {
return {
message: flattenDiagnosticMessageText(diagnostic.messageText, newLine),
start: diagnostic.start,
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/arrayCast.errors.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
tests/cases/compiler/arrayCast.ts(3,1): error TS2352: Neither type '{ foo: string; }[]' nor type '{ id: number; }[]' is assignable to the other.
Type '{ foo: string; }' is not assignable to type '{ id: number; }'.
Property 'id' is missing in type '{ foo: string; }'.
Property 'foo' does not exist on type '{ id: number; }'.


==== tests/cases/compiler/arrayCast.ts (1 errors) ====
Expand All @@ -10,7 +10,7 @@ tests/cases/compiler/arrayCast.ts(3,1): error TS2352: Neither type '{ foo: strin
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2352: Neither type '{ foo: string; }[]' nor type '{ id: number; }[]' is assignable to the other.
!!! error TS2352: Type '{ foo: string; }' is not assignable to type '{ id: number; }'.
!!! error TS2352: Property 'id' is missing in type '{ foo: string; }'.
!!! error TS2352: Property 'foo' does not exist on type '{ id: number; }'.

// Should succeed, as the {} element causes the type of the array to be {}[]
<{ id: number; }[]>[{ foo: "s" }, {}];
72 changes: 72 additions & 0 deletions tests/baselines/reference/arrayLiteralTypeInference.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
tests/cases/compiler/arrayLiteralTypeInference.ts(13,5): error TS2322: Type '({ id: number; trueness: boolean; } | { id: number; name: string; })[]' is not assignable to type 'Action[]'.
Type '{ id: number; trueness: boolean; } | { id: number; name: string; }' is not assignable to type 'Action'.
Type '{ id: number; trueness: boolean; }' is not assignable to type 'Action'.
Property 'trueness' does not exist on type 'Action'.
tests/cases/compiler/arrayLiteralTypeInference.ts(29,5): error TS2322: Type '({ id: number; trueness: boolean; } | { id: number; name: string; })[]' is not assignable to type '{ id: number; }[]'.
Type '{ id: number; trueness: boolean; } | { id: number; name: string; }' is not assignable to type '{ id: number; }'.
Type '{ id: number; trueness: boolean; }' is not assignable to type '{ id: number; }'.
Property 'trueness' does not exist on type '{ id: number; }'.


==== tests/cases/compiler/arrayLiteralTypeInference.ts (2 errors) ====
class Action {
id: number;
}

class ActionA extends Action {
value: string;
}

class ActionB extends Action {
trueNess: boolean;
Copy link
Member

Choose a reason for hiding this comment

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

I think this should be trueness -- it doesn't look like this test was intended to fail. Another 🏆 for this change's effectiveness.

Copy link

Choose a reason for hiding this comment

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

}

var x1: Action[] = [
~~
!!! error TS2322: Type '({ id: number; trueness: boolean; } | { id: number; name: string; })[]' is not assignable to type 'Action[]'.
!!! error TS2322: Type '{ id: number; trueness: boolean; } | { id: number; name: string; }' is not assignable to type 'Action'.
!!! error TS2322: Type '{ id: number; trueness: boolean; }' is not assignable to type 'Action'.
!!! error TS2322: Property 'trueness' does not exist on type 'Action'.
{ id: 2, trueness: false },
{ id: 3, name: "three" }
]

var x2: Action[] = [
new ActionA(),
new ActionB()
]

var x3: Action[] = [
new Action(),
new ActionA(),
new ActionB()
]

var z1: { id: number }[] =
~~
!!! error TS2322: Type '({ id: number; trueness: boolean; } | { id: number; name: string; })[]' is not assignable to type '{ id: number; }[]'.
!!! error TS2322: Type '{ id: number; trueness: boolean; } | { id: number; name: string; }' is not assignable to type '{ id: number; }'.
!!! error TS2322: Type '{ id: number; trueness: boolean; }' is not assignable to type '{ id: number; }'.
!!! error TS2322: Property 'trueness' does not exist on type '{ id: number; }'.
[
{ id: 2, trueness: false },
{ id: 3, name: "three" }
]

var z2: { id: number }[] =
[
new ActionA(),
new ActionB()
]

var z3: { id: number }[] =
[
new Action(),
new ActionA(),
new ActionB()
]





Loading