Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
62 changes: 14 additions & 48 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9124,7 +9124,7 @@ namespace ts {
markPropertyAsReferenced(prop, accessExpression, /*isThisAccess*/ accessExpression.expression.kind === SyntaxKind.ThisKeyword);
if (isAssignmentTarget(accessExpression) && (isReferenceToReadonlyEntity(accessExpression, prop) || isReferenceThroughNamespaceImport(accessExpression))) {
error(accessExpression.argumentExpression, Diagnostics.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property, symbolToString(prop));
return errorType;
return accessNode ? errorType : unknownType;
}
if (cacheSymbol) {
getNodeLinks(accessNode!).resolvedSymbol = prop;
Expand Down Expand Up @@ -9182,7 +9182,7 @@ namespace ts {
}
}
}
return anyType;
return accessNode ? errorType : unknownType;
}
}
if (isJSLiteralType(objectType)) {
Expand All @@ -9200,7 +9200,10 @@ namespace ts {
error(indexNode, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeToString(indexType));
}
}
return errorType;
if (isTypeAny(indexType)) {
return indexType;
}
return accessNode ? errorType : unknownType;
}

function isGenericObjectType(type: Type): boolean {
Expand All @@ -9211,22 +9214,6 @@ namespace ts {
return maybeTypeOfKind(type, TypeFlags.InstantiableNonPrimitive | TypeFlags.Index);
}

// Return true if the given type is a non-generic object type with a string index signature and no
// other members.
function isStringIndexOnlyType(type: Type): boolean {
if (type.flags & TypeFlags.Object && !isGenericMappedType(type)) {
const t = resolveStructuredTypeMembers(<ObjectType>type);
return t.properties.length === 0 &&
t.callSignatures.length === 0 && t.constructSignatures.length === 0 &&
!!t.stringIndexInfo && !t.numberIndexInfo;
}
return false;
}

function isMappedTypeToNever(type: Type): boolean {
return !!(getObjectFlags(type) & ObjectFlags.Mapped) && getTemplateTypeFromMappedType(type as MappedType) === neverType;
}

function getSimplifiedType(type: Type): Type {
return type.flags & TypeFlags.IndexedAccess ? getSimplifiedIndexedAccessType(<IndexedAccessType>type) : type;
}
Expand All @@ -9241,35 +9228,11 @@ namespace ts {
// We recursively simplify the object type as it may in turn be an indexed access type. For example, with
// '{ [P in T]: { [Q in U]: number } }[T][U]' we want to first simplify the inner indexed access type.
const objectType = getSimplifiedType(type.objectType);
if (objectType.flags & TypeFlags.Intersection && isGenericObjectType(objectType)) {
// Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or
// more object types with only a string index signature, e.g. '(U & V & { [x: string]: D })[K]', return a
// transformed type of the form '(U & V)[K] | D'. This allows us to properly reason about higher order indexed
// access types with default property values as expressed by D.
if (some((<IntersectionType>objectType).types, isStringIndexOnlyType)) {
const regularTypes: Type[] = [];
const stringIndexTypes: Type[] = [];
for (const t of (<IntersectionType>objectType).types) {
if (isStringIndexOnlyType(t)) {
stringIndexTypes.push(getIndexTypeOfType(t, IndexKind.String)!);
}
else {
regularTypes.push(t);
}
}
return type.simplified = getUnionType([
getSimplifiedType(getIndexedAccessType(getIntersectionType(regularTypes), type.indexType)),
getIntersectionType(stringIndexTypes)
]);
}
// Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or
// more mapped types with a template type `never`, '(U & V & { [P in T]: never })[K]', return a
// transformed type that removes the never-mapped type: '(U & V)[K]'. This mirrors what would happen
// eventually anyway, but it easier to reason about.
if (some((<IntersectionType>objectType).types, isMappedTypeToNever)) {
const nonNeverTypes = filter((<IntersectionType>objectType).types, t => !isMappedTypeToNever(t));
return type.simplified = getSimplifiedType(getIndexedAccessType(getIntersectionType(nonNeverTypes), type.indexType));
}
if (objectType.flags & TypeFlags.Union) {
return type.simplified = mapType(objectType, t => getIndexedAccessType(t, type.indexType));
}
if (objectType.flags & TypeFlags.Intersection) {
return type.simplified = getIntersectionType(map((objectType as IntersectionType).types, t => getIndexedAccessType(t, type.indexType)));
}
// If the object type is a mapped type { [P in K]: E }, where K is generic, instantiate E using a mapper
// that substitutes the index type for P. For example, for an index access { [P in K]: Box<T[P]> }[X], we
Expand Down Expand Up @@ -9322,6 +9285,9 @@ namespace ts {
const propTypes: Type[] = [];
for (const t of (<UnionType>indexType).types) {
const propType = getPropertyTypeForIndexType(apparentObjectType, t, accessNode, /*cacheSymbol*/ false);
if (propType === unknownType) {
return unknownType;
}
if (propType === errorType) {
return errorType;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType9_ES5.ts(6,5): error TS2322: Type '{ [x: number]: string | number; }' is not assignable to type 'I'.
Index signatures are incompatible.
Type 'string | number' is not assignable to type 'boolean'.
Type 'string' is not assignable to type 'boolean'.
tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType9_ES5.ts(7,5): error TS2418: Type of computed property's value is 'string', which is not assignable to type 'boolean'.
tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType9_ES5.ts(8,5): error TS2418: Type of computed property's value is 'number', which is not assignable to type 'boolean'.


==== tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType9_ES5.ts (1 errors) ====
==== tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType9_ES5.ts (2 errors) ====
interface I {
[s: string]: boolean;
[s: number]: boolean;
}

var o: I = {
~
!!! error TS2322: Type '{ [x: number]: string | number; }' is not assignable to type 'I'.
!!! error TS2322: Index signatures are incompatible.
!!! error TS2322: Type 'string | number' is not assignable to type 'boolean'.
!!! error TS2322: Type 'string' is not assignable to type 'boolean'.
[+"foo"]: "",
~~~~~~~~
!!! error TS2418: Type of computed property's value is 'string', which is not assignable to type 'boolean'.
!!! related TS6501 tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType9_ES5.ts:2:5: The expected type comes from this index signature.
[+"bar"]: 0
~~~~~~~~
!!! error TS2418: Type of computed property's value is 'number', which is not assignable to type 'boolean'.
!!! related TS6501 tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType9_ES5.ts:2:5: The expected type comes from this index signature.
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType9_ES6.ts(6,5): error TS2322: Type '{ [x: number]: string | number; }' is not assignable to type 'I'.
Index signatures are incompatible.
Type 'string | number' is not assignable to type 'boolean'.
Type 'string' is not assignable to type 'boolean'.
tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType9_ES6.ts(7,5): error TS2418: Type of computed property's value is 'string', which is not assignable to type 'boolean'.
tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType9_ES6.ts(8,5): error TS2418: Type of computed property's value is 'number', which is not assignable to type 'boolean'.


==== tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType9_ES6.ts (1 errors) ====
==== tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType9_ES6.ts (2 errors) ====
interface I {
[s: string]: boolean;
[s: number]: boolean;
}

var o: I = {
~
!!! error TS2322: Type '{ [x: number]: string | number; }' is not assignable to type 'I'.
!!! error TS2322: Index signatures are incompatible.
!!! error TS2322: Type 'string | number' is not assignable to type 'boolean'.
!!! error TS2322: Type 'string' is not assignable to type 'boolean'.
[+"foo"]: "",
~~~~~~~~
!!! error TS2418: Type of computed property's value is 'string', which is not assignable to type 'boolean'.
!!! related TS6501 tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType9_ES6.ts:2:5: The expected type comes from this index signature.
[+"bar"]: 0
~~~~~~~~
!!! error TS2418: Type of computed property's value is 'number', which is not assignable to type 'boolean'.
!!! related TS6501 tests/cases/conformance/es6/computedProperties/computedPropertyNamesContextualType9_ES6.ts:2:5: The expected type comes from this index signature.
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
tests/cases/conformance/types/conditional/conditionalTypesExcessProperties.ts(8,5): error TS2322: Type '{ test: string; arg: A; }' is not assignable to type 'Something<A>'.
Type '{ test: string; arg: A; }' is not assignable to type 'A extends object ? { arg: A; } : { arg?: undefined; }'.
tests/cases/conformance/types/conditional/conditionalTypesExcessProperties.ts(9,33): error TS2322: Type 'A' is not assignable to type 'Something<A>["arr"]'.
Type 'object' is not assignable to type 'Something<A>["arr"]'.
tests/cases/conformance/types/conditional/conditionalTypesExcessProperties.ts(9,33): error TS2322: Type '{ test: string; arg: A; arr: A; }' is not assignable to type 'Something<A>'.
Object literal may only specify known properties, and 'arr' does not exist in type 'Something<A>'.


==== tests/cases/conformance/types/conditional/conditionalTypesExcessProperties.ts (2 errors) ====
Expand All @@ -17,8 +17,8 @@ tests/cases/conformance/types/conditional/conditionalTypesExcessProperties.ts(9,
!!! error TS2322: Type '{ test: string; arg: A; }' is not assignable to type 'Something<A>'.
!!! error TS2322: Type '{ test: string; arg: A; }' is not assignable to type 'A extends object ? { arg: A; } : { arg?: undefined; }'.
sa = { test: 'bye', arg: a, arr: a } // excess
~~~
!!! error TS2322: Type 'A' is not assignable to type 'Something<A>["arr"]'.
!!! error TS2322: Type 'object' is not assignable to type 'Something<A>["arr"]'.
~~~~~~
!!! error TS2322: Type '{ test: string; arg: A; arr: A; }' is not assignable to type 'Something<A>'.
!!! error TS2322: Object literal may only specify known properties, and 'arr' does not exist in type 'Something<A>'.
}

31 changes: 0 additions & 31 deletions tests/baselines/reference/deferredLookupTypeResolution2.errors.txt

This file was deleted.

37 changes: 37 additions & 0 deletions tests/baselines/reference/indexedAccessRelation.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
tests/cases/compiler/indexedAccessRelation.ts(16,23): error TS2345: Argument of type '{ a: T; }' is not assignable to parameter of type 'Pick<S & State<T>, "a">'.
Types of property 'a' are incompatible.
Type 'T' is not assignable to type 'S["a"] & T'.
Type 'Foo' is not assignable to type 'S["a"] & T'.
Type 'Foo' is not assignable to type 'S["a"]'.
Type 'T' is not assignable to type 'S["a"]'.
Type 'Foo' is not assignable to type 'S["a"]'.


==== tests/cases/compiler/indexedAccessRelation.ts (1 errors) ====
// Repro from #14723

class Component<S> {
setState<K extends keyof S>(state: Pick<S, K>) {}
}

export interface State<T> {
a?: T;
}

class Foo {}

class Comp<T extends Foo, S> extends Component<S & State<T>>
{
foo(a: T) {
this.setState({ a: a });
~~~~~~~~
!!! error TS2345: Argument of type '{ a: T; }' is not assignable to parameter of type 'Pick<S & State<T>, "a">'.
!!! error TS2345: Types of property 'a' are incompatible.
!!! error TS2345: Type 'T' is not assignable to type 'S["a"] & T'.
!!! error TS2345: Type 'Foo' is not assignable to type 'S["a"] & T'.
!!! error TS2345: Type 'Foo' is not assignable to type 'S["a"]'.
!!! error TS2345: Type 'T' is not assignable to type 'S["a"]'.
!!! error TS2345: Type 'Foo' is not assignable to type 'S["a"]'.
}
}

2 changes: 1 addition & 1 deletion tests/baselines/reference/indexedAccessRelation.types
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class Comp<T extends Foo, S> extends Component<S & State<T>>
>a : T

this.setState({ a: a });
>this.setState({ a: a }) : void
>this.setState({ a: a }) : any
>this.setState : <K extends keyof S | "a">(state: Pick<S & State<T>, K>) => void
>this : this
>setState : <K extends keyof S | "a">(state: Pick<S & State<T>, K>) => void
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/indexingTypesWithNever.types
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ declare function genericFn3<

// Should be never
const result5 = genericFn3({ g: "gtest", h: "htest" }, "g", "h"); // 'g' & 'h' will reduce to never
>result5 : any
>genericFn3({ g: "gtest", h: "htest" }, "g", "h") : any
>result5 : unknown
>genericFn3({ g: "gtest", h: "htest" }, "g", "h") : unknown
>genericFn3 : <T extends { [K in keyof T]: T[K]; }, U extends keyof T, V extends keyof T>(obj: T, u: U, v: V) => T[U & V]
>{ g: "gtest", h: "htest" } : { g: string; h: string; }
>g : string
Expand Down
Loading