Merged
Show file tree
Hide file tree
Changes from all 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
136 changes: 43 additions & 93 deletions src/compiler/checker.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3119,7 +3119,7 @@ namespace ts {
}

function resolveTupleTypeMembers(type: TupleType) {
let arrayType = resolveStructuredTypeMembers(createArrayType(getUnionType(type.elementTypes, /*noDeduplication*/ true)));
let arrayType = resolveStructuredTypeMembers(createArrayType(getUnionType(type.elementTypes, /*noSubtypeReduction*/ true)));
let members = createTupleTypeMemberSymbols(type.elementTypes);
addInheritedMembers(members, arrayType.properties);
setObjectTypeMembers(type, members, arrayType.callSignatures, arrayType.constructSignatures, arrayType.stringIndexType, arrayType.numberIndexType);
Expand DownExpand Up@@ -3451,29 +3451,6 @@ 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 && type !== globalObjectType) {
const resolved = resolveStructuredTypeMembers(type);
return !!(resolved.properties.length === 0 ||
resolved.stringIndexType ||
resolved.numberIndexType ||
getPropertyOfType(type, name));
}
if (type.flags & TypeFlags.UnionOrIntersection) {
for (let t of (<UnionOrIntersectionType>type).types) {
if (isKnownProperty(t, name)) {
return true;
}
}
return false;
}
return true;
}

function getSignaturesOfStructuredType(type: Type, kind: SignatureKind): Signature[] {
if (type.flags & TypeFlags.StructuredType) {
let resolved = resolveStructuredTypeMembers(<ObjectType>type);
Expand DownExpand Up@@ -4103,73 +4080,20 @@ namespace ts {
}
}

function isObjectLiteralTypeDuplicateOf(source: ObjectType, target: ObjectType): boolean {
let sourceProperties = getPropertiesOfObjectType(source);
let targetProperties = getPropertiesOfObjectType(target);
if (sourceProperties.length !== targetProperties.length) {
return false;
}
for (let sourceProp of sourceProperties) {
let targetProp = getPropertyOfObjectType(target, sourceProp.name);
if (!targetProp ||
getDeclarationFlagsFromSymbol(targetProp) & (NodeFlags.Private | NodeFlags.Protected) ||
!isTypeDuplicateOf(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp))) {
return false;
}
}
return true;
}

function isTupleTypeDuplicateOf(source: TupleType, target: TupleType): boolean {
let sourceTypes = source.elementTypes;
let targetTypes = target.elementTypes;
if (sourceTypes.length !== targetTypes.length) {
return false;
}
for (var i = 0; i < sourceTypes.length; i++) {
if (!isTypeDuplicateOf(sourceTypes[i], targetTypes[i])) {
return false;
}
}
return true;
}

// Returns true if the source type is a duplicate of the target type. A source type is a duplicate of
// a target type if the the two are identical, with the exception that the source type may have null or
// undefined in places where the target type doesn't. This is by design an asymmetric relationship.
function isTypeDuplicateOf(source: Type, target: Type): boolean {
if (source === target) {
return true;
}
if (source.flags & TypeFlags.Undefined || source.flags & TypeFlags.Null && !(target.flags & TypeFlags.Undefined)) {
return true;
}
if (source.flags & TypeFlags.ObjectLiteral && target.flags & TypeFlags.ObjectType) {
return isObjectLiteralTypeDuplicateOf(<ObjectType>source, <ObjectType>target);
}
if (isArrayType(source) && isArrayType(target)) {
return isTypeDuplicateOf((<TypeReference>source).typeArguments[0], (<TypeReference>target).typeArguments[0]);
}
if (isTupleType(source) && isTupleType(target)) {
return isTupleTypeDuplicateOf(<TupleType>source, <TupleType>target);
}
return isTypeIdenticalTo(source, target);
}

function isTypeDuplicateOfSomeType(candidate: Type, types: Type[]): boolean {
for (let type of types) {
if (candidate !== type && isTypeDuplicateOf(candidate, type)) {
function isSubtypeOfAny(candidate: Type, types: Type[]): boolean {
for (let i = 0, len = types.length; i < len; i++) {
if (candidate !== types[i] && isTypeSubtypeOf(candidate, types[i])) {
return true;
}
}
return false;
}

function removeDuplicateTypes(types: Type[]) {
function removeSubtypes(types: Type[]) {
let i = types.length;
while (i > 0) {
i--;
if (isTypeDuplicateOfSomeType(types[i], types)) {
if (isSubtypeOfAny(types[i], types)) {
types.splice(i, 1);
}
}
Expand All@@ -4194,12 +4118,14 @@ namespace ts {
}
}

// We always deduplicate the constituent type set based on object identity, but we'll also deduplicate
// based on the structure of the types unless the noDeduplication flag is true, which is the case when
// creating a union type from a type node and when instantiating a union type. In both of those cases,
// structural deduplication has to be deferred to properly support recursive union types. For example,
// a type of the form "type Item = string | (() => Item)" cannot be deduplicated during its declaration.
function getUnionType(types: Type[], noDeduplication?: boolean): Type {
// We reduce the constituent type set to only include types that aren't subtypes of other types, unless
// the noSubtypeReduction flag is specified, in which case we perform a simple deduplication based on
// object identity. Subtype reduction is possible only when union types are known not to circularly
// reference themselves (as is the case with union types created by expression constructs such as array
// literals and the || and ?: operators). Named types can circularly reference themselves and therefore
// cannot be deduplicated during their declaration. For example, "type Item = string | (() => Item" is
// a named type that circularly references itself.
function getUnionType(types: Type[], noSubtypeReduction?: boolean): Type {
if (types.length === 0) {
return emptyObjectType;
}
Expand All@@ -4208,12 +4134,12 @@ namespace ts {
if (containsTypeAny(typeSet)) {
return anyType;
}
if (noDeduplication) {
if (noSubtypeReduction) {
removeAllButLast(typeSet, undefinedType);
removeAllButLast(typeSet, nullType);
}
else {
removeDuplicateTypes(typeSet);
removeSubtypes(typeSet);
}
if (typeSet.length === 1) {
return typeSet[0];
Expand All@@ -4230,7 +4156,7 @@ namespace ts {
function getTypeFromUnionTypeNode(node: UnionTypeNode): Type {
let links = getNodeLinks(node);
if (!links.resolvedType) {
links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), /*noDeduplication*/ true);
links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), /*noSubtypeReduction*/ true);
}
return links.resolvedType;
}
Expand DownExpand Up@@ -4526,7 +4452,7 @@ namespace ts {
return createTupleType(instantiateList((<TupleType>type).elementTypes, mapper, instantiateType));
}
if (type.flags & TypeFlags.Union) {
return getUnionType(instantiateList((<UnionType>type).types, mapper, instantiateType), /*noDeduplication*/ true);
return getUnionType(instantiateList((<UnionType>type).types, mapper, instantiateType), /*noSubtypeReduction*/ true);
}
if (type.flags & TypeFlags.Intersection) {
return getIntersectionType(instantiateList((<IntersectionType>type).types, mapper, instantiateType));
Expand DownExpand Up@@ -4813,6 +4739,30 @@ namespace ts {
return Ternary.False;
}

// 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 and the check is for assignability, if the object type has
// index signatures, or if the property is actually declared in the object 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 {

Choose a reason for hiding this comment

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

Perhaps isExpectedProperty would better describe this.

if (type.flags & TypeFlags.ObjectType) {
const resolved = resolveStructuredTypeMembers(type);
if (relation === assignableRelation && (type === globalObjectType || resolved.properties.length === 0) ||
resolved.stringIndexType || resolved.numberIndexType || getPropertyOfType(type, name)) {
return true;
}
return false;
}
if (type.flags & TypeFlags.UnionOrIntersection) {
for (let t of (<UnionOrIntersectionType>type).types) {
if (isKnownProperty(t, name)) {
return true;
}
}
return false;
}
return true;
}

function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean {
for (let prop of getPropertiesOfObjectType(source)) {
if (!isKnownProperty(target, prop.name)) {
Expand DownExpand Up@@ -5594,7 +5544,7 @@ namespace ts {
return getWidenedTypeOfObjectLiteral(type);
}
if (type.flags & TypeFlags.Union) {
return getUnionType(map((<UnionType>type).types, getWidenedType));
return getUnionType(map((<UnionType>type).types, getWidenedType), /*noSubtypeReduction*/ true);
}
if (isArrayType(type)) {
return createArrayType(getWidenedType((<TypeReference>type).typeArguments[0]));
Expand Down
44 changes: 22 additions & 22 deletions tests/baselines/reference/arrayBestCommonTypes.types
Original file line numberDiff line numberDiff line change
Expand Up@@ -294,8 +294,8 @@ module EmptyTypes {

// Order matters here so test all the variants
var a1 = [{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }];
>a1 : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>a1 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand All@@ -313,8 +313,8 @@ module EmptyTypes {
>'a' : string

var a2 = [{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }];
>a2 : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>a2 : { x: any; y: string; }[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: anyObj, y: 'a' } : { x: any; y: string; }
>x : any
>anyObj : any
Expand All@@ -332,8 +332,8 @@ module EmptyTypes {
>'a' : string

var a3 = [{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }];
>a3 : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>a3 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand DownExpand Up@@ -639,7 +639,7 @@ module NonEmptyTypes {
>x : number
>y : base
>base : base
>[{ x: 7, y: new derived() }, { x: 5, y: new base() }] : ({ x: number; y: derived; } | { x: number; y: base; })[]
>[{ x: 7, y: new derived() }, { x: 5, y: new base() }] : { x: number; y: base; }[]
>{ x: 7, y: new derived() } : { x: number; y: derived; }
>x : number
>7 : number
Expand All@@ -658,7 +658,7 @@ module NonEmptyTypes {
>x : boolean
>y : base
>base : base
>[{ x: true, y: new derived() }, { x: false, y: new base() }] : ({ x: boolean; y: derived; } | { x: boolean; y: base; })[]
>[{ x: true, y: new derived() }, { x: false, y: new base() }] : { x: boolean; y: base; }[]
>{ x: true, y: new derived() } : { x: boolean; y: derived; }
>x : boolean
>true : boolean
Expand DownExpand Up@@ -697,8 +697,8 @@ module NonEmptyTypes {

// Order matters here so test all the variants
var a1 = [{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }];
>a1 : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>a1 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand All@@ -716,8 +716,8 @@ module NonEmptyTypes {
>'a' : string

var a2 = [{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }];
>a2 : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>a2 : { x: any; y: string; }[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: anyObj, y: 'a' } : { x: any; y: string; }
>x : any
>anyObj : any
Expand All@@ -735,8 +735,8 @@ module NonEmptyTypes {
>'a' : string

var a3 = [{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }];
>a3 : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>a3 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand DownExpand Up@@ -769,29 +769,29 @@ module NonEmptyTypes {
>base2 : typeof base2

var b1 = [baseObj, base2Obj, ifaceObj];
>b1 : (base | base2 | iface)[]
>[baseObj, base2Obj, ifaceObj] : (base | base2 | iface)[]
>b1 : iface[]
>[baseObj, base2Obj, ifaceObj] : iface[]
>baseObj : base
>base2Obj : base2
>ifaceObj : iface

var b2 = [base2Obj, baseObj, ifaceObj];
>b2 : (base2 | base | iface)[]
>[base2Obj, baseObj, ifaceObj] : (base2 | base | iface)[]
>b2 : iface[]
>[base2Obj, baseObj, ifaceObj] : iface[]
>base2Obj : base2
>baseObj : base
>ifaceObj : iface

var b3 = [baseObj, ifaceObj, base2Obj];
>b3 : (base | iface | base2)[]
>[baseObj, ifaceObj, base2Obj] : (base | iface | base2)[]
>b3 : iface[]
>[baseObj, ifaceObj, base2Obj] : iface[]
>baseObj : base
>ifaceObj : iface
>base2Obj : base2

var b4 = [ifaceObj, baseObj, base2Obj];
>b4 : (iface | base | base2)[]
>[ifaceObj, baseObj, base2Obj] : (iface | base | base2)[]
>b4 : iface[]
>[ifaceObj, baseObj, base2Obj] : iface[]
>ifaceObj : iface
>baseObj : base
>base2Obj : base2
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,8 +36,8 @@ var cs = [a, b, c]; // { x: number; y?: number };[]
>c : { x: number; a?: number; }

var ds = [(x: Object) => 1, (x: string) => 2]; // { (x:Object) => number }[]
>ds : (((x: Object) => number) | ((x: string) => number))[]
>[(x: Object) => 1, (x: string) => 2] : (((x: Object) => number) | ((x: string) => number))[]
>ds : ((x: Object) => number)[]
>[(x: Object) => 1, (x: string) => 2] : ((x: Object) => number)[]
>(x: Object) => 1 : (x: Object) => number
>x : Object
>Object : Object
Expand All@@ -47,8 +47,8 @@ var ds = [(x: Object) => 1, (x: string) => 2]; // { (x:Object) => number }[]
>2 : number

var es = [(x: string) => 2, (x: Object) => 1]; // { (x:string) => number }[]
>es : (((x: string) => number) | ((x: Object) => number))[]
>[(x: string) => 2, (x: Object) => 1] : (((x: string) => number) | ((x: Object) => number))[]
>es : ((x: string) => number)[]
>[(x: string) => 2, (x: Object) => 1] : ((x: string) => number)[]
>(x: string) => 2 : (x: string) => number
>x : string
>2 : number
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -77,8 +77,8 @@ var myDerivedList: DerivedList<number>;
>DerivedList : DerivedList<U>

var as = [list, myDerivedList]; // List<number>[]
>as : (List<number> | DerivedList<number>)[]
>[list, myDerivedList] : (List<number> | DerivedList<number>)[]
>as : List<number>[]
>[list, myDerivedList] : List<number>[]
>list : List<number>
>myDerivedList : DerivedList<number>

Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
136 changes: 43 additions & 93 deletions src/compiler/checker.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3119,7 +3119,7 @@ namespace ts {
}

function resolveTupleTypeMembers(type: TupleType) {
let arrayType = resolveStructuredTypeMembers(createArrayType(getUnionType(type.elementTypes, /*noDeduplication*/ true)));
let arrayType = resolveStructuredTypeMembers(createArrayType(getUnionType(type.elementTypes, /*noSubtypeReduction*/ true)));
let members = createTupleTypeMemberSymbols(type.elementTypes);
addInheritedMembers(members, arrayType.properties);
setObjectTypeMembers(type, members, arrayType.callSignatures, arrayType.constructSignatures, arrayType.stringIndexType, arrayType.numberIndexType);
Expand DownExpand Up@@ -3451,29 +3451,6 @@ 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 && type !== globalObjectType) {
const resolved = resolveStructuredTypeMembers(type);
return !!(resolved.properties.length === 0 ||
resolved.stringIndexType ||
resolved.numberIndexType ||
getPropertyOfType(type, name));
}
if (type.flags & TypeFlags.UnionOrIntersection) {
for (let t of (<UnionOrIntersectionType>type).types) {
if (isKnownProperty(t, name)) {
return true;
}
}
return false;
}
return true;
}

function getSignaturesOfStructuredType(type: Type, kind: SignatureKind): Signature[] {
if (type.flags & TypeFlags.StructuredType) {
let resolved = resolveStructuredTypeMembers(<ObjectType>type);
Expand DownExpand Up@@ -4103,73 +4080,20 @@ namespace ts {
}
}

function isObjectLiteralTypeDuplicateOf(source: ObjectType, target: ObjectType): boolean {
let sourceProperties = getPropertiesOfObjectType(source);
let targetProperties = getPropertiesOfObjectType(target);
if (sourceProperties.length !== targetProperties.length) {
return false;
}
for (let sourceProp of sourceProperties) {
let targetProp = getPropertyOfObjectType(target, sourceProp.name);
if (!targetProp ||
getDeclarationFlagsFromSymbol(targetProp) & (NodeFlags.Private | NodeFlags.Protected) ||
!isTypeDuplicateOf(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp))) {
return false;
}
}
return true;
}

function isTupleTypeDuplicateOf(source: TupleType, target: TupleType): boolean {
let sourceTypes = source.elementTypes;
let targetTypes = target.elementTypes;
if (sourceTypes.length !== targetTypes.length) {
return false;
}
for (var i = 0; i < sourceTypes.length; i++) {
if (!isTypeDuplicateOf(sourceTypes[i], targetTypes[i])) {
return false;
}
}
return true;
}

// Returns true if the source type is a duplicate of the target type. A source type is a duplicate of
// a target type if the the two are identical, with the exception that the source type may have null or
// undefined in places where the target type doesn't. This is by design an asymmetric relationship.
function isTypeDuplicateOf(source: Type, target: Type): boolean {
if (source === target) {
return true;
}
if (source.flags & TypeFlags.Undefined || source.flags & TypeFlags.Null && !(target.flags & TypeFlags.Undefined)) {
return true;
}
if (source.flags & TypeFlags.ObjectLiteral && target.flags & TypeFlags.ObjectType) {
return isObjectLiteralTypeDuplicateOf(<ObjectType>source, <ObjectType>target);
}
if (isArrayType(source) && isArrayType(target)) {
return isTypeDuplicateOf((<TypeReference>source).typeArguments[0], (<TypeReference>target).typeArguments[0]);
}
if (isTupleType(source) && isTupleType(target)) {
return isTupleTypeDuplicateOf(<TupleType>source, <TupleType>target);
}
return isTypeIdenticalTo(source, target);
}

function isTypeDuplicateOfSomeType(candidate: Type, types: Type[]): boolean {
for (let type of types) {
if (candidate !== type && isTypeDuplicateOf(candidate, type)) {
function isSubtypeOfAny(candidate: Type, types: Type[]): boolean {
for (let i = 0, len = types.length; i < len; i++) {
if (candidate !== types[i] && isTypeSubtypeOf(candidate, types[i])) {
return true;
}
}
return false;
}

function removeDuplicateTypes(types: Type[]) {
function removeSubtypes(types: Type[]) {
let i = types.length;
while (i > 0) {
i--;
if (isTypeDuplicateOfSomeType(types[i], types)) {
if (isSubtypeOfAny(types[i], types)) {
types.splice(i, 1);
}
}
Expand All@@ -4194,12 +4118,14 @@ namespace ts {
}
}

// We always deduplicate the constituent type set based on object identity, but we'll also deduplicate
// based on the structure of the types unless the noDeduplication flag is true, which is the case when
// creating a union type from a type node and when instantiating a union type. In both of those cases,
// structural deduplication has to be deferred to properly support recursive union types. For example,
// a type of the form "type Item = string | (() => Item)" cannot be deduplicated during its declaration.
function getUnionType(types: Type[], noDeduplication?: boolean): Type {
// We reduce the constituent type set to only include types that aren't subtypes of other types, unless
// the noSubtypeReduction flag is specified, in which case we perform a simple deduplication based on
// object identity. Subtype reduction is possible only when union types are known not to circularly
// reference themselves (as is the case with union types created by expression constructs such as array
// literals and the || and ?: operators). Named types can circularly reference themselves and therefore
// cannot be deduplicated during their declaration. For example, "type Item = string | (() => Item" is
// a named type that circularly references itself.
function getUnionType(types: Type[], noSubtypeReduction?: boolean): Type {
if (types.length === 0) {
return emptyObjectType;
}
Expand All@@ -4208,12 +4134,12 @@ namespace ts {
if (containsTypeAny(typeSet)) {
return anyType;
}
if (noDeduplication) {
if (noSubtypeReduction) {
removeAllButLast(typeSet, undefinedType);
removeAllButLast(typeSet, nullType);
}
else {
removeDuplicateTypes(typeSet);
removeSubtypes(typeSet);
}
if (typeSet.length === 1) {
return typeSet[0];
Expand All@@ -4230,7 +4156,7 @@ namespace ts {
function getTypeFromUnionTypeNode(node: UnionTypeNode): Type {
let links = getNodeLinks(node);
if (!links.resolvedType) {
links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), /*noDeduplication*/ true);
links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), /*noSubtypeReduction*/ true);
}
return links.resolvedType;
}
Expand DownExpand Up@@ -4526,7 +4452,7 @@ namespace ts {
return createTupleType(instantiateList((<TupleType>type).elementTypes, mapper, instantiateType));
}
if (type.flags & TypeFlags.Union) {
return getUnionType(instantiateList((<UnionType>type).types, mapper, instantiateType), /*noDeduplication*/ true);
return getUnionType(instantiateList((<UnionType>type).types, mapper, instantiateType), /*noSubtypeReduction*/ true);
}
if (type.flags & TypeFlags.Intersection) {
return getIntersectionType(instantiateList((<IntersectionType>type).types, mapper, instantiateType));
Expand DownExpand Up@@ -4813,6 +4739,30 @@ namespace ts {
return Ternary.False;
}

// 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 and the check is for assignability, if the object type has
// index signatures, or if the property is actually declared in the object 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 {

Choose a reason for hiding this comment

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

Perhaps isExpectedProperty would better describe this.

if (type.flags & TypeFlags.ObjectType) {
const resolved = resolveStructuredTypeMembers(type);
if (relation === assignableRelation && (type === globalObjectType || resolved.properties.length === 0) ||
resolved.stringIndexType || resolved.numberIndexType || getPropertyOfType(type, name)) {
return true;
}
return false;
}
if (type.flags & TypeFlags.UnionOrIntersection) {
for (let t of (<UnionOrIntersectionType>type).types) {
if (isKnownProperty(t, name)) {
return true;
}
}
return false;
}
return true;
}

function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean {
for (let prop of getPropertiesOfObjectType(source)) {
if (!isKnownProperty(target, prop.name)) {
Expand DownExpand Up@@ -5594,7 +5544,7 @@ namespace ts {
return getWidenedTypeOfObjectLiteral(type);
}
if (type.flags & TypeFlags.Union) {
return getUnionType(map((<UnionType>type).types, getWidenedType));
return getUnionType(map((<UnionType>type).types, getWidenedType), /*noSubtypeReduction*/ true);
}
if (isArrayType(type)) {
return createArrayType(getWidenedType((<TypeReference>type).typeArguments[0]));
Expand Down
44 changes: 22 additions & 22 deletions tests/baselines/reference/arrayBestCommonTypes.types
Original file line numberDiff line numberDiff line change
Expand Up@@ -294,8 +294,8 @@ module EmptyTypes {

// Order matters here so test all the variants
var a1 = [{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }];
>a1 : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>a1 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand All@@ -313,8 +313,8 @@ module EmptyTypes {
>'a' : string

var a2 = [{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }];
>a2 : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>a2 : { x: any; y: string; }[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: anyObj, y: 'a' } : { x: any; y: string; }
>x : any
>anyObj : any
Expand All@@ -332,8 +332,8 @@ module EmptyTypes {
>'a' : string

var a3 = [{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }];
>a3 : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>a3 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand DownExpand Up@@ -639,7 +639,7 @@ module NonEmptyTypes {
>x : number
>y : base
>base : base
>[{ x: 7, y: new derived() }, { x: 5, y: new base() }] : ({ x: number; y: derived; } | { x: number; y: base; })[]
>[{ x: 7, y: new derived() }, { x: 5, y: new base() }] : { x: number; y: base; }[]
>{ x: 7, y: new derived() } : { x: number; y: derived; }
>x : number
>7 : number
Expand All@@ -658,7 +658,7 @@ module NonEmptyTypes {
>x : boolean
>y : base
>base : base
>[{ x: true, y: new derived() }, { x: false, y: new base() }] : ({ x: boolean; y: derived; } | { x: boolean; y: base; })[]
>[{ x: true, y: new derived() }, { x: false, y: new base() }] : { x: boolean; y: base; }[]
>{ x: true, y: new derived() } : { x: boolean; y: derived; }
>x : boolean
>true : boolean
Expand DownExpand Up@@ -697,8 +697,8 @@ module NonEmptyTypes {

// Order matters here so test all the variants
var a1 = [{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }];
>a1 : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>a1 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand All@@ -716,8 +716,8 @@ module NonEmptyTypes {
>'a' : string

var a2 = [{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }];
>a2 : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>a2 : { x: any; y: string; }[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: anyObj, y: 'a' } : { x: any; y: string; }
>x : any
>anyObj : any
Expand All@@ -735,8 +735,8 @@ module NonEmptyTypes {
>'a' : string

var a3 = [{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }];
>a3 : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>a3 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand DownExpand Up@@ -769,29 +769,29 @@ module NonEmptyTypes {
>base2 : typeof base2

var b1 = [baseObj, base2Obj, ifaceObj];
>b1 : (base | base2 | iface)[]
>[baseObj, base2Obj, ifaceObj] : (base | base2 | iface)[]
>b1 : iface[]
>[baseObj, base2Obj, ifaceObj] : iface[]
>baseObj : base
>base2Obj : base2
>ifaceObj : iface

var b2 = [base2Obj, baseObj, ifaceObj];
>b2 : (base2 | base | iface)[]
>[base2Obj, baseObj, ifaceObj] : (base2 | base | iface)[]
>b2 : iface[]
>[base2Obj, baseObj, ifaceObj] : iface[]
>base2Obj : base2
>baseObj : base
>ifaceObj : iface

var b3 = [baseObj, ifaceObj, base2Obj];
>b3 : (base | iface | base2)[]
>[baseObj, ifaceObj, base2Obj] : (base | iface | base2)[]
>b3 : iface[]
>[baseObj, ifaceObj, base2Obj] : iface[]
>baseObj : base
>ifaceObj : iface
>base2Obj : base2

var b4 = [ifaceObj, baseObj, base2Obj];
>b4 : (iface | base | base2)[]
>[ifaceObj, baseObj, base2Obj] : (iface | base | base2)[]
>b4 : iface[]
>[ifaceObj, baseObj, base2Obj] : iface[]
>ifaceObj : iface
>baseObj : base
>base2Obj : base2
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,8 +36,8 @@ var cs = [a, b, c]; // { x: number; y?: number };[]
>c : { x: number; a?: number; }

var ds = [(x: Object) => 1, (x: string) => 2]; // { (x:Object) => number }[]
>ds : (((x: Object) => number) | ((x: string) => number))[]
>[(x: Object) => 1, (x: string) => 2] : (((x: Object) => number) | ((x: string) => number))[]
>ds : ((x: Object) => number)[]
>[(x: Object) => 1, (x: string) => 2] : ((x: Object) => number)[]
>(x: Object) => 1 : (x: Object) => number
>x : Object
>Object : Object
Expand All@@ -47,8 +47,8 @@ var ds = [(x: Object) => 1, (x: string) => 2]; // { (x:Object) => number }[]
>2 : number

var es = [(x: string) => 2, (x: Object) => 1]; // { (x:string) => number }[]
>es : (((x: string) => number) | ((x: Object) => number))[]
>[(x: string) => 2, (x: Object) => 1] : (((x: string) => number) | ((x: Object) => number))[]
>es : ((x: string) => number)[]
>[(x: string) => 2, (x: Object) => 1] : ((x: string) => number)[]
>(x: string) => 2 : (x: string) => number
>x : string
>2 : number
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -77,8 +77,8 @@ var myDerivedList: DerivedList<number>;
>DerivedList : DerivedList<U>

var as = [list, myDerivedList]; // List<number>[]
>as : (List<number> | DerivedList<number>)[]
>[list, myDerivedList] : (List<number> | DerivedList<number>)[]
>as : List<number>[]
>[list, myDerivedList] : List<number>[]
>list : List<number>
>myDerivedList : DerivedList<number>

Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
136 changes: 43 additions & 93 deletions src/compiler/checker.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3119,7 +3119,7 @@ namespace ts {
}

function resolveTupleTypeMembers(type: TupleType) {
let arrayType = resolveStructuredTypeMembers(createArrayType(getUnionType(type.elementTypes, /*noDeduplication*/ true)));
let arrayType = resolveStructuredTypeMembers(createArrayType(getUnionType(type.elementTypes, /*noSubtypeReduction*/ true)));
let members = createTupleTypeMemberSymbols(type.elementTypes);
addInheritedMembers(members, arrayType.properties);
setObjectTypeMembers(type, members, arrayType.callSignatures, arrayType.constructSignatures, arrayType.stringIndexType, arrayType.numberIndexType);
Expand DownExpand Up@@ -3451,29 +3451,6 @@ 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 && type !== globalObjectType) {
const resolved = resolveStructuredTypeMembers(type);
return !!(resolved.properties.length === 0 ||
resolved.stringIndexType ||
resolved.numberIndexType ||
getPropertyOfType(type, name));
}
if (type.flags & TypeFlags.UnionOrIntersection) {
for (let t of (<UnionOrIntersectionType>type).types) {
if (isKnownProperty(t, name)) {
return true;
}
}
return false;
}
return true;
}

function getSignaturesOfStructuredType(type: Type, kind: SignatureKind): Signature[] {
if (type.flags & TypeFlags.StructuredType) {
let resolved = resolveStructuredTypeMembers(<ObjectType>type);
Expand DownExpand Up@@ -4103,73 +4080,20 @@ namespace ts {
}
}

function isObjectLiteralTypeDuplicateOf(source: ObjectType, target: ObjectType): boolean {
let sourceProperties = getPropertiesOfObjectType(source);
let targetProperties = getPropertiesOfObjectType(target);
if (sourceProperties.length !== targetProperties.length) {
return false;
}
for (let sourceProp of sourceProperties) {
let targetProp = getPropertyOfObjectType(target, sourceProp.name);
if (!targetProp ||
getDeclarationFlagsFromSymbol(targetProp) & (NodeFlags.Private | NodeFlags.Protected) ||
!isTypeDuplicateOf(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp))) {
return false;
}
}
return true;
}

function isTupleTypeDuplicateOf(source: TupleType, target: TupleType): boolean {
let sourceTypes = source.elementTypes;
let targetTypes = target.elementTypes;
if (sourceTypes.length !== targetTypes.length) {
return false;
}
for (var i = 0; i < sourceTypes.length; i++) {
if (!isTypeDuplicateOf(sourceTypes[i], targetTypes[i])) {
return false;
}
}
return true;
}

// Returns true if the source type is a duplicate of the target type. A source type is a duplicate of
// a target type if the the two are identical, with the exception that the source type may have null or
// undefined in places where the target type doesn't. This is by design an asymmetric relationship.
function isTypeDuplicateOf(source: Type, target: Type): boolean {
if (source === target) {
return true;
}
if (source.flags & TypeFlags.Undefined || source.flags & TypeFlags.Null && !(target.flags & TypeFlags.Undefined)) {
return true;
}
if (source.flags & TypeFlags.ObjectLiteral && target.flags & TypeFlags.ObjectType) {
return isObjectLiteralTypeDuplicateOf(<ObjectType>source, <ObjectType>target);
}
if (isArrayType(source) && isArrayType(target)) {
return isTypeDuplicateOf((<TypeReference>source).typeArguments[0], (<TypeReference>target).typeArguments[0]);
}
if (isTupleType(source) && isTupleType(target)) {
return isTupleTypeDuplicateOf(<TupleType>source, <TupleType>target);
}
return isTypeIdenticalTo(source, target);
}

function isTypeDuplicateOfSomeType(candidate: Type, types: Type[]): boolean {
for (let type of types) {
if (candidate !== type && isTypeDuplicateOf(candidate, type)) {
function isSubtypeOfAny(candidate: Type, types: Type[]): boolean {
for (let i = 0, len = types.length; i < len; i++) {
if (candidate !== types[i] && isTypeSubtypeOf(candidate, types[i])) {
return true;
}
}
return false;
}

function removeDuplicateTypes(types: Type[]) {
function removeSubtypes(types: Type[]) {
let i = types.length;
while (i > 0) {
i--;
if (isTypeDuplicateOfSomeType(types[i], types)) {
if (isSubtypeOfAny(types[i], types)) {
types.splice(i, 1);
}
}
Expand All@@ -4194,12 +4118,14 @@ namespace ts {
}
}

// We always deduplicate the constituent type set based on object identity, but we'll also deduplicate
// based on the structure of the types unless the noDeduplication flag is true, which is the case when
// creating a union type from a type node and when instantiating a union type. In both of those cases,
// structural deduplication has to be deferred to properly support recursive union types. For example,
// a type of the form "type Item = string | (() => Item)" cannot be deduplicated during its declaration.
function getUnionType(types: Type[], noDeduplication?: boolean): Type {
// We reduce the constituent type set to only include types that aren't subtypes of other types, unless
// the noSubtypeReduction flag is specified, in which case we perform a simple deduplication based on
// object identity. Subtype reduction is possible only when union types are known not to circularly
// reference themselves (as is the case with union types created by expression constructs such as array
// literals and the || and ?: operators). Named types can circularly reference themselves and therefore
// cannot be deduplicated during their declaration. For example, "type Item = string | (() => Item" is
// a named type that circularly references itself.
function getUnionType(types: Type[], noSubtypeReduction?: boolean): Type {
if (types.length === 0) {
return emptyObjectType;
}
Expand All@@ -4208,12 +4134,12 @@ namespace ts {
if (containsTypeAny(typeSet)) {
return anyType;
}
if (noDeduplication) {
if (noSubtypeReduction) {
removeAllButLast(typeSet, undefinedType);
removeAllButLast(typeSet, nullType);
}
else {
removeDuplicateTypes(typeSet);
removeSubtypes(typeSet);
}
if (typeSet.length === 1) {
return typeSet[0];
Expand All@@ -4230,7 +4156,7 @@ namespace ts {
function getTypeFromUnionTypeNode(node: UnionTypeNode): Type {
let links = getNodeLinks(node);
if (!links.resolvedType) {
links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), /*noDeduplication*/ true);
links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), /*noSubtypeReduction*/ true);
}
return links.resolvedType;
}
Expand DownExpand Up@@ -4526,7 +4452,7 @@ namespace ts {
return createTupleType(instantiateList((<TupleType>type).elementTypes, mapper, instantiateType));
}
if (type.flags & TypeFlags.Union) {
return getUnionType(instantiateList((<UnionType>type).types, mapper, instantiateType), /*noDeduplication*/ true);
return getUnionType(instantiateList((<UnionType>type).types, mapper, instantiateType), /*noSubtypeReduction*/ true);
}
if (type.flags & TypeFlags.Intersection) {
return getIntersectionType(instantiateList((<IntersectionType>type).types, mapper, instantiateType));
Expand DownExpand Up@@ -4813,6 +4739,30 @@ namespace ts {
return Ternary.False;
}

// 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 and the check is for assignability, if the object type has
// index signatures, or if the property is actually declared in the object 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 {

Choose a reason for hiding this comment

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

Perhaps isExpectedProperty would better describe this.

if (type.flags & TypeFlags.ObjectType) {
const resolved = resolveStructuredTypeMembers(type);
if (relation === assignableRelation && (type === globalObjectType || resolved.properties.length === 0) ||
resolved.stringIndexType || resolved.numberIndexType || getPropertyOfType(type, name)) {
return true;
}
return false;
}
if (type.flags & TypeFlags.UnionOrIntersection) {
for (let t of (<UnionOrIntersectionType>type).types) {
if (isKnownProperty(t, name)) {
return true;
}
}
return false;
}
return true;
}

function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean {
for (let prop of getPropertiesOfObjectType(source)) {
if (!isKnownProperty(target, prop.name)) {
Expand DownExpand Up@@ -5594,7 +5544,7 @@ namespace ts {
return getWidenedTypeOfObjectLiteral(type);
}
if (type.flags & TypeFlags.Union) {
return getUnionType(map((<UnionType>type).types, getWidenedType));
return getUnionType(map((<UnionType>type).types, getWidenedType), /*noSubtypeReduction*/ true);
}
if (isArrayType(type)) {
return createArrayType(getWidenedType((<TypeReference>type).typeArguments[0]));
Expand Down
44 changes: 22 additions & 22 deletions tests/baselines/reference/arrayBestCommonTypes.types
Original file line numberDiff line numberDiff line change
Expand Up@@ -294,8 +294,8 @@ module EmptyTypes {

// Order matters here so test all the variants
var a1 = [{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }];
>a1 : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>a1 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand All@@ -313,8 +313,8 @@ module EmptyTypes {
>'a' : string

var a2 = [{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }];
>a2 : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>a2 : { x: any; y: string; }[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: anyObj, y: 'a' } : { x: any; y: string; }
>x : any
>anyObj : any
Expand All@@ -332,8 +332,8 @@ module EmptyTypes {
>'a' : string

var a3 = [{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }];
>a3 : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>a3 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand DownExpand Up@@ -639,7 +639,7 @@ module NonEmptyTypes {
>x : number
>y : base
>base : base
>[{ x: 7, y: new derived() }, { x: 5, y: new base() }] : ({ x: number; y: derived; } | { x: number; y: base; })[]
>[{ x: 7, y: new derived() }, { x: 5, y: new base() }] : { x: number; y: base; }[]
>{ x: 7, y: new derived() } : { x: number; y: derived; }
>x : number
>7 : number
Expand All@@ -658,7 +658,7 @@ module NonEmptyTypes {
>x : boolean
>y : base
>base : base
>[{ x: true, y: new derived() }, { x: false, y: new base() }] : ({ x: boolean; y: derived; } | { x: boolean; y: base; })[]
>[{ x: true, y: new derived() }, { x: false, y: new base() }] : { x: boolean; y: base; }[]
>{ x: true, y: new derived() } : { x: boolean; y: derived; }
>x : boolean
>true : boolean
Expand DownExpand Up@@ -697,8 +697,8 @@ module NonEmptyTypes {

// Order matters here so test all the variants
var a1 = [{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }];
>a1 : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>a1 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand All@@ -716,8 +716,8 @@ module NonEmptyTypes {
>'a' : string

var a2 = [{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }];
>a2 : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>a2 : { x: any; y: string; }[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: anyObj, y: 'a' } : { x: any; y: string; }
>x : any
>anyObj : any
Expand All@@ -735,8 +735,8 @@ module NonEmptyTypes {
>'a' : string

var a3 = [{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }];
>a3 : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>a3 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand DownExpand Up@@ -769,29 +769,29 @@ module NonEmptyTypes {
>base2 : typeof base2

var b1 = [baseObj, base2Obj, ifaceObj];
>b1 : (base | base2 | iface)[]
>[baseObj, base2Obj, ifaceObj] : (base | base2 | iface)[]
>b1 : iface[]
>[baseObj, base2Obj, ifaceObj] : iface[]
>baseObj : base
>base2Obj : base2
>ifaceObj : iface

var b2 = [base2Obj, baseObj, ifaceObj];
>b2 : (base2 | base | iface)[]
>[base2Obj, baseObj, ifaceObj] : (base2 | base | iface)[]
>b2 : iface[]
>[base2Obj, baseObj, ifaceObj] : iface[]
>base2Obj : base2
>baseObj : base
>ifaceObj : iface

var b3 = [baseObj, ifaceObj, base2Obj];
>b3 : (base | iface | base2)[]
>[baseObj, ifaceObj, base2Obj] : (base | iface | base2)[]
>b3 : iface[]
>[baseObj, ifaceObj, base2Obj] : iface[]
>baseObj : base
>ifaceObj : iface
>base2Obj : base2

var b4 = [ifaceObj, baseObj, base2Obj];
>b4 : (iface | base | base2)[]
>[ifaceObj, baseObj, base2Obj] : (iface | base | base2)[]
>b4 : iface[]
>[ifaceObj, baseObj, base2Obj] : iface[]
>ifaceObj : iface
>baseObj : base
>base2Obj : base2
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,8 +36,8 @@ var cs = [a, b, c]; // { x: number; y?: number };[]
>c : { x: number; a?: number; }

var ds = [(x: Object) => 1, (x: string) => 2]; // { (x:Object) => number }[]
>ds : (((x: Object) => number) | ((x: string) => number))[]
>[(x: Object) => 1, (x: string) => 2] : (((x: Object) => number) | ((x: string) => number))[]
>ds : ((x: Object) => number)[]
>[(x: Object) => 1, (x: string) => 2] : ((x: Object) => number)[]
>(x: Object) => 1 : (x: Object) => number
>x : Object
>Object : Object
Expand All@@ -47,8 +47,8 @@ var ds = [(x: Object) => 1, (x: string) => 2]; // { (x:Object) => number }[]
>2 : number

var es = [(x: string) => 2, (x: Object) => 1]; // { (x:string) => number }[]
>es : (((x: string) => number) | ((x: Object) => number))[]
>[(x: string) => 2, (x: Object) => 1] : (((x: string) => number) | ((x: Object) => number))[]
>es : ((x: string) => number)[]
>[(x: string) => 2, (x: Object) => 1] : ((x: string) => number)[]
>(x: string) => 2 : (x: string) => number
>x : string
>2 : number
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -77,8 +77,8 @@ var myDerivedList: DerivedList<number>;
>DerivedList : DerivedList<U>

var as = [list, myDerivedList]; // List<number>[]
>as : (List<number> | DerivedList<number>)[]
>[list, myDerivedList] : (List<number> | DerivedList<number>)[]
>as : List<number>[]
>[list, myDerivedList] : List<number>[]
>list : List<number>
>myDerivedList : DerivedList<number>

Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
136 changes: 43 additions & 93 deletions src/compiler/checker.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3119,7 +3119,7 @@ namespace ts {
}

function resolveTupleTypeMembers(type: TupleType) {
let arrayType = resolveStructuredTypeMembers(createArrayType(getUnionType(type.elementTypes, /*noDeduplication*/ true)));
let arrayType = resolveStructuredTypeMembers(createArrayType(getUnionType(type.elementTypes, /*noSubtypeReduction*/ true)));
let members = createTupleTypeMemberSymbols(type.elementTypes);
addInheritedMembers(members, arrayType.properties);
setObjectTypeMembers(type, members, arrayType.callSignatures, arrayType.constructSignatures, arrayType.stringIndexType, arrayType.numberIndexType);
Expand DownExpand Up@@ -3451,29 +3451,6 @@ 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 && type !== globalObjectType) {
const resolved = resolveStructuredTypeMembers(type);
return !!(resolved.properties.length === 0 ||
resolved.stringIndexType ||
resolved.numberIndexType ||
getPropertyOfType(type, name));
}
if (type.flags & TypeFlags.UnionOrIntersection) {
for (let t of (<UnionOrIntersectionType>type).types) {
if (isKnownProperty(t, name)) {
return true;
}
}
return false;
}
return true;
}

function getSignaturesOfStructuredType(type: Type, kind: SignatureKind): Signature[] {
if (type.flags & TypeFlags.StructuredType) {
let resolved = resolveStructuredTypeMembers(<ObjectType>type);
Expand DownExpand Up@@ -4103,73 +4080,20 @@ namespace ts {
}
}

function isObjectLiteralTypeDuplicateOf(source: ObjectType, target: ObjectType): boolean {
let sourceProperties = getPropertiesOfObjectType(source);
let targetProperties = getPropertiesOfObjectType(target);
if (sourceProperties.length !== targetProperties.length) {
return false;
}
for (let sourceProp of sourceProperties) {
let targetProp = getPropertyOfObjectType(target, sourceProp.name);
if (!targetProp ||
getDeclarationFlagsFromSymbol(targetProp) & (NodeFlags.Private | NodeFlags.Protected) ||
!isTypeDuplicateOf(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp))) {
return false;
}
}
return true;
}

function isTupleTypeDuplicateOf(source: TupleType, target: TupleType): boolean {
let sourceTypes = source.elementTypes;
let targetTypes = target.elementTypes;
if (sourceTypes.length !== targetTypes.length) {
return false;
}
for (var i = 0; i < sourceTypes.length; i++) {
if (!isTypeDuplicateOf(sourceTypes[i], targetTypes[i])) {
return false;
}
}
return true;
}

// Returns true if the source type is a duplicate of the target type. A source type is a duplicate of
// a target type if the the two are identical, with the exception that the source type may have null or
// undefined in places where the target type doesn't. This is by design an asymmetric relationship.
function isTypeDuplicateOf(source: Type, target: Type): boolean {
if (source === target) {
return true;
}
if (source.flags & TypeFlags.Undefined || source.flags & TypeFlags.Null && !(target.flags & TypeFlags.Undefined)) {
return true;
}
if (source.flags & TypeFlags.ObjectLiteral && target.flags & TypeFlags.ObjectType) {
return isObjectLiteralTypeDuplicateOf(<ObjectType>source, <ObjectType>target);
}
if (isArrayType(source) && isArrayType(target)) {
return isTypeDuplicateOf((<TypeReference>source).typeArguments[0], (<TypeReference>target).typeArguments[0]);
}
if (isTupleType(source) && isTupleType(target)) {
return isTupleTypeDuplicateOf(<TupleType>source, <TupleType>target);
}
return isTypeIdenticalTo(source, target);
}

function isTypeDuplicateOfSomeType(candidate: Type, types: Type[]): boolean {
for (let type of types) {
if (candidate !== type && isTypeDuplicateOf(candidate, type)) {
function isSubtypeOfAny(candidate: Type, types: Type[]): boolean {
for (let i = 0, len = types.length; i < len; i++) {
if (candidate !== types[i] && isTypeSubtypeOf(candidate, types[i])) {
return true;
}
}
return false;
}

function removeDuplicateTypes(types: Type[]) {
function removeSubtypes(types: Type[]) {
let i = types.length;
while (i > 0) {
i--;
if (isTypeDuplicateOfSomeType(types[i], types)) {
if (isSubtypeOfAny(types[i], types)) {
types.splice(i, 1);
}
}
Expand All@@ -4194,12 +4118,14 @@ namespace ts {
}
}

// We always deduplicate the constituent type set based on object identity, but we'll also deduplicate
// based on the structure of the types unless the noDeduplication flag is true, which is the case when
// creating a union type from a type node and when instantiating a union type. In both of those cases,
// structural deduplication has to be deferred to properly support recursive union types. For example,
// a type of the form "type Item = string | (() => Item)" cannot be deduplicated during its declaration.
function getUnionType(types: Type[], noDeduplication?: boolean): Type {
// We reduce the constituent type set to only include types that aren't subtypes of other types, unless
// the noSubtypeReduction flag is specified, in which case we perform a simple deduplication based on
// object identity. Subtype reduction is possible only when union types are known not to circularly
// reference themselves (as is the case with union types created by expression constructs such as array
// literals and the || and ?: operators). Named types can circularly reference themselves and therefore
// cannot be deduplicated during their declaration. For example, "type Item = string | (() => Item" is
// a named type that circularly references itself.
function getUnionType(types: Type[], noSubtypeReduction?: boolean): Type {
if (types.length === 0) {
return emptyObjectType;
}
Expand All@@ -4208,12 +4134,12 @@ namespace ts {
if (containsTypeAny(typeSet)) {
return anyType;
}
if (noDeduplication) {
if (noSubtypeReduction) {
removeAllButLast(typeSet, undefinedType);
removeAllButLast(typeSet, nullType);
}
else {
removeDuplicateTypes(typeSet);
removeSubtypes(typeSet);
}
if (typeSet.length === 1) {
return typeSet[0];
Expand All@@ -4230,7 +4156,7 @@ namespace ts {
function getTypeFromUnionTypeNode(node: UnionTypeNode): Type {
let links = getNodeLinks(node);
if (!links.resolvedType) {
links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), /*noDeduplication*/ true);
links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), /*noSubtypeReduction*/ true);
}
return links.resolvedType;
}
Expand DownExpand Up@@ -4526,7 +4452,7 @@ namespace ts {
return createTupleType(instantiateList((<TupleType>type).elementTypes, mapper, instantiateType));
}
if (type.flags & TypeFlags.Union) {
return getUnionType(instantiateList((<UnionType>type).types, mapper, instantiateType), /*noDeduplication*/ true);
return getUnionType(instantiateList((<UnionType>type).types, mapper, instantiateType), /*noSubtypeReduction*/ true);
}
if (type.flags & TypeFlags.Intersection) {
return getIntersectionType(instantiateList((<IntersectionType>type).types, mapper, instantiateType));
Expand DownExpand Up@@ -4813,6 +4739,30 @@ namespace ts {
return Ternary.False;
}

// 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 and the check is for assignability, if the object type has
// index signatures, or if the property is actually declared in the object 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 {

Choose a reason for hiding this comment

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

Perhaps isExpectedProperty would better describe this.

if (type.flags & TypeFlags.ObjectType) {
const resolved = resolveStructuredTypeMembers(type);
if (relation === assignableRelation && (type === globalObjectType || resolved.properties.length === 0) ||
resolved.stringIndexType || resolved.numberIndexType || getPropertyOfType(type, name)) {
return true;
}
return false;
}
if (type.flags & TypeFlags.UnionOrIntersection) {
for (let t of (<UnionOrIntersectionType>type).types) {
if (isKnownProperty(t, name)) {
return true;
}
}
return false;
}
return true;
}

function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean {
for (let prop of getPropertiesOfObjectType(source)) {
if (!isKnownProperty(target, prop.name)) {
Expand DownExpand Up@@ -5594,7 +5544,7 @@ namespace ts {
return getWidenedTypeOfObjectLiteral(type);
}
if (type.flags & TypeFlags.Union) {
return getUnionType(map((<UnionType>type).types, getWidenedType));
return getUnionType(map((<UnionType>type).types, getWidenedType), /*noSubtypeReduction*/ true);
}
if (isArrayType(type)) {
return createArrayType(getWidenedType((<TypeReference>type).typeArguments[0]));
Expand Down
44 changes: 22 additions & 22 deletions tests/baselines/reference/arrayBestCommonTypes.types
Original file line numberDiff line numberDiff line change
Expand Up@@ -294,8 +294,8 @@ module EmptyTypes {

// Order matters here so test all the variants
var a1 = [{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }];
>a1 : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>a1 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand All@@ -313,8 +313,8 @@ module EmptyTypes {
>'a' : string

var a2 = [{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }];
>a2 : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>a2 : { x: any; y: string; }[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: anyObj, y: 'a' } : { x: any; y: string; }
>x : any
>anyObj : any
Expand All@@ -332,8 +332,8 @@ module EmptyTypes {
>'a' : string

var a3 = [{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }];
>a3 : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>a3 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand DownExpand Up@@ -639,7 +639,7 @@ module NonEmptyTypes {
>x : number
>y : base
>base : base
>[{ x: 7, y: new derived() }, { x: 5, y: new base() }] : ({ x: number; y: derived; } | { x: number; y: base; })[]
>[{ x: 7, y: new derived() }, { x: 5, y: new base() }] : { x: number; y: base; }[]
>{ x: 7, y: new derived() } : { x: number; y: derived; }
>x : number
>7 : number
Expand All@@ -658,7 +658,7 @@ module NonEmptyTypes {
>x : boolean
>y : base
>base : base
>[{ x: true, y: new derived() }, { x: false, y: new base() }] : ({ x: boolean; y: derived; } | { x: boolean; y: base; })[]
>[{ x: true, y: new derived() }, { x: false, y: new base() }] : { x: boolean; y: base; }[]
>{ x: true, y: new derived() } : { x: boolean; y: derived; }
>x : boolean
>true : boolean
Expand DownExpand Up@@ -697,8 +697,8 @@ module NonEmptyTypes {

// Order matters here so test all the variants
var a1 = [{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }];
>a1 : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>a1 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand All@@ -716,8 +716,8 @@ module NonEmptyTypes {
>'a' : string

var a2 = [{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }];
>a2 : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>a2 : { x: any; y: string; }[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: anyObj, y: 'a' } : { x: any; y: string; }
>x : any
>anyObj : any
Expand All@@ -735,8 +735,8 @@ module NonEmptyTypes {
>'a' : string

var a3 = [{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }];
>a3 : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>a3 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand DownExpand Up@@ -769,29 +769,29 @@ module NonEmptyTypes {
>base2 : typeof base2

var b1 = [baseObj, base2Obj, ifaceObj];
>b1 : (base | base2 | iface)[]
>[baseObj, base2Obj, ifaceObj] : (base | base2 | iface)[]
>b1 : iface[]
>[baseObj, base2Obj, ifaceObj] : iface[]
>baseObj : base
>base2Obj : base2
>ifaceObj : iface

var b2 = [base2Obj, baseObj, ifaceObj];
>b2 : (base2 | base | iface)[]
>[base2Obj, baseObj, ifaceObj] : (base2 | base | iface)[]
>b2 : iface[]
>[base2Obj, baseObj, ifaceObj] : iface[]
>base2Obj : base2
>baseObj : base
>ifaceObj : iface

var b3 = [baseObj, ifaceObj, base2Obj];
>b3 : (base | iface | base2)[]
>[baseObj, ifaceObj, base2Obj] : (base | iface | base2)[]
>b3 : iface[]
>[baseObj, ifaceObj, base2Obj] : iface[]
>baseObj : base
>ifaceObj : iface
>base2Obj : base2

var b4 = [ifaceObj, baseObj, base2Obj];
>b4 : (iface | base | base2)[]
>[ifaceObj, baseObj, base2Obj] : (iface | base | base2)[]
>b4 : iface[]
>[ifaceObj, baseObj, base2Obj] : iface[]
>ifaceObj : iface
>baseObj : base
>base2Obj : base2
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,8 +36,8 @@ var cs = [a, b, c]; // { x: number; y?: number };[]
>c : { x: number; a?: number; }

var ds = [(x: Object) => 1, (x: string) => 2]; // { (x:Object) => number }[]
>ds : (((x: Object) => number) | ((x: string) => number))[]
>[(x: Object) => 1, (x: string) => 2] : (((x: Object) => number) | ((x: string) => number))[]
>ds : ((x: Object) => number)[]
>[(x: Object) => 1, (x: string) => 2] : ((x: Object) => number)[]
>(x: Object) => 1 : (x: Object) => number
>x : Object
>Object : Object
Expand All@@ -47,8 +47,8 @@ var ds = [(x: Object) => 1, (x: string) => 2]; // { (x:Object) => number }[]
>2 : number

var es = [(x: string) => 2, (x: Object) => 1]; // { (x:string) => number }[]
>es : (((x: string) => number) | ((x: Object) => number))[]
>[(x: string) => 2, (x: Object) => 1] : (((x: string) => number) | ((x: Object) => number))[]
>es : ((x: string) => number)[]
>[(x: string) => 2, (x: Object) => 1] : ((x: string) => number)[]
>(x: string) => 2 : (x: string) => number
>x : string
>2 : number
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -77,8 +77,8 @@ var myDerivedList: DerivedList<number>;
>DerivedList : DerivedList<U>

var as = [list, myDerivedList]; // List<number>[]
>as : (List<number> | DerivedList<number>)[]
>[list, myDerivedList] : (List<number> | DerivedList<number>)[]
>as : List<number>[]
>[list, myDerivedList] : List<number>[]
>list : List<number>
>myDerivedList : DerivedList<number>

Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
136 changes: 43 additions & 93 deletions src/compiler/checker.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3119,7 +3119,7 @@ namespace ts {
}

function resolveTupleTypeMembers(type: TupleType) {
let arrayType = resolveStructuredTypeMembers(createArrayType(getUnionType(type.elementTypes, /*noDeduplication*/ true)));
let arrayType = resolveStructuredTypeMembers(createArrayType(getUnionType(type.elementTypes, /*noSubtypeReduction*/ true)));
let members = createTupleTypeMemberSymbols(type.elementTypes);
addInheritedMembers(members, arrayType.properties);
setObjectTypeMembers(type, members, arrayType.callSignatures, arrayType.constructSignatures, arrayType.stringIndexType, arrayType.numberIndexType);
Expand DownExpand Up@@ -3451,29 +3451,6 @@ 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 && type !== globalObjectType) {
const resolved = resolveStructuredTypeMembers(type);
return !!(resolved.properties.length === 0 ||
resolved.stringIndexType ||
resolved.numberIndexType ||
getPropertyOfType(type, name));
}
if (type.flags & TypeFlags.UnionOrIntersection) {
for (let t of (<UnionOrIntersectionType>type).types) {
if (isKnownProperty(t, name)) {
return true;
}
}
return false;
}
return true;
}

function getSignaturesOfStructuredType(type: Type, kind: SignatureKind): Signature[] {
if (type.flags & TypeFlags.StructuredType) {
let resolved = resolveStructuredTypeMembers(<ObjectType>type);
Expand DownExpand Up@@ -4103,73 +4080,20 @@ namespace ts {
}
}

function isObjectLiteralTypeDuplicateOf(source: ObjectType, target: ObjectType): boolean {
let sourceProperties = getPropertiesOfObjectType(source);
let targetProperties = getPropertiesOfObjectType(target);
if (sourceProperties.length !== targetProperties.length) {
return false;
}
for (let sourceProp of sourceProperties) {
let targetProp = getPropertyOfObjectType(target, sourceProp.name);
if (!targetProp ||
getDeclarationFlagsFromSymbol(targetProp) & (NodeFlags.Private | NodeFlags.Protected) ||
!isTypeDuplicateOf(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp))) {
return false;
}
}
return true;
}

function isTupleTypeDuplicateOf(source: TupleType, target: TupleType): boolean {
let sourceTypes = source.elementTypes;
let targetTypes = target.elementTypes;
if (sourceTypes.length !== targetTypes.length) {
return false;
}
for (var i = 0; i < sourceTypes.length; i++) {
if (!isTypeDuplicateOf(sourceTypes[i], targetTypes[i])) {
return false;
}
}
return true;
}

// Returns true if the source type is a duplicate of the target type. A source type is a duplicate of
// a target type if the the two are identical, with the exception that the source type may have null or
// undefined in places where the target type doesn't. This is by design an asymmetric relationship.
function isTypeDuplicateOf(source: Type, target: Type): boolean {
if (source === target) {
return true;
}
if (source.flags & TypeFlags.Undefined || source.flags & TypeFlags.Null && !(target.flags & TypeFlags.Undefined)) {
return true;
}
if (source.flags & TypeFlags.ObjectLiteral && target.flags & TypeFlags.ObjectType) {
return isObjectLiteralTypeDuplicateOf(<ObjectType>source, <ObjectType>target);
}
if (isArrayType(source) && isArrayType(target)) {
return isTypeDuplicateOf((<TypeReference>source).typeArguments[0], (<TypeReference>target).typeArguments[0]);
}
if (isTupleType(source) && isTupleType(target)) {
return isTupleTypeDuplicateOf(<TupleType>source, <TupleType>target);
}
return isTypeIdenticalTo(source, target);
}

function isTypeDuplicateOfSomeType(candidate: Type, types: Type[]): boolean {
for (let type of types) {
if (candidate !== type && isTypeDuplicateOf(candidate, type)) {
function isSubtypeOfAny(candidate: Type, types: Type[]): boolean {
for (let i = 0, len = types.length; i < len; i++) {
if (candidate !== types[i] && isTypeSubtypeOf(candidate, types[i])) {
return true;
}
}
return false;
}

function removeDuplicateTypes(types: Type[]) {
function removeSubtypes(types: Type[]) {
let i = types.length;
while (i > 0) {
i--;
if (isTypeDuplicateOfSomeType(types[i], types)) {
if (isSubtypeOfAny(types[i], types)) {
types.splice(i, 1);
}
}
Expand All@@ -4194,12 +4118,14 @@ namespace ts {
}
}

// We always deduplicate the constituent type set based on object identity, but we'll also deduplicate
// based on the structure of the types unless the noDeduplication flag is true, which is the case when
// creating a union type from a type node and when instantiating a union type. In both of those cases,
// structural deduplication has to be deferred to properly support recursive union types. For example,
// a type of the form "type Item = string | (() => Item)" cannot be deduplicated during its declaration.
function getUnionType(types: Type[], noDeduplication?: boolean): Type {
// We reduce the constituent type set to only include types that aren't subtypes of other types, unless
// the noSubtypeReduction flag is specified, in which case we perform a simple deduplication based on
// object identity. Subtype reduction is possible only when union types are known not to circularly
// reference themselves (as is the case with union types created by expression constructs such as array
// literals and the || and ?: operators). Named types can circularly reference themselves and therefore
// cannot be deduplicated during their declaration. For example, "type Item = string | (() => Item" is
// a named type that circularly references itself.
function getUnionType(types: Type[], noSubtypeReduction?: boolean): Type {
if (types.length === 0) {
return emptyObjectType;
}
Expand All@@ -4208,12 +4134,12 @@ namespace ts {
if (containsTypeAny(typeSet)) {
return anyType;
}
if (noDeduplication) {
if (noSubtypeReduction) {
removeAllButLast(typeSet, undefinedType);
removeAllButLast(typeSet, nullType);
}
else {
removeDuplicateTypes(typeSet);
removeSubtypes(typeSet);
}
if (typeSet.length === 1) {
return typeSet[0];
Expand All@@ -4230,7 +4156,7 @@ namespace ts {
function getTypeFromUnionTypeNode(node: UnionTypeNode): Type {
let links = getNodeLinks(node);
if (!links.resolvedType) {
links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), /*noDeduplication*/ true);
links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), /*noSubtypeReduction*/ true);
}
return links.resolvedType;
}
Expand DownExpand Up@@ -4526,7 +4452,7 @@ namespace ts {
return createTupleType(instantiateList((<TupleType>type).elementTypes, mapper, instantiateType));
}
if (type.flags & TypeFlags.Union) {
return getUnionType(instantiateList((<UnionType>type).types, mapper, instantiateType), /*noDeduplication*/ true);
return getUnionType(instantiateList((<UnionType>type).types, mapper, instantiateType), /*noSubtypeReduction*/ true);
}
if (type.flags & TypeFlags.Intersection) {
return getIntersectionType(instantiateList((<IntersectionType>type).types, mapper, instantiateType));
Expand DownExpand Up@@ -4813,6 +4739,30 @@ namespace ts {
return Ternary.False;
}

// 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 and the check is for assignability, if the object type has
// index signatures, or if the property is actually declared in the object 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 {

Choose a reason for hiding this comment

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

Perhaps isExpectedProperty would better describe this.

if (type.flags & TypeFlags.ObjectType) {
const resolved = resolveStructuredTypeMembers(type);
if (relation === assignableRelation && (type === globalObjectType || resolved.properties.length === 0) ||
resolved.stringIndexType || resolved.numberIndexType || getPropertyOfType(type, name)) {
return true;
}
return false;
}
if (type.flags & TypeFlags.UnionOrIntersection) {
for (let t of (<UnionOrIntersectionType>type).types) {
if (isKnownProperty(t, name)) {
return true;
}
}
return false;
}
return true;
}

function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean {
for (let prop of getPropertiesOfObjectType(source)) {
if (!isKnownProperty(target, prop.name)) {
Expand DownExpand Up@@ -5594,7 +5544,7 @@ namespace ts {
return getWidenedTypeOfObjectLiteral(type);
}
if (type.flags & TypeFlags.Union) {
return getUnionType(map((<UnionType>type).types, getWidenedType));
return getUnionType(map((<UnionType>type).types, getWidenedType), /*noSubtypeReduction*/ true);
}
if (isArrayType(type)) {
return createArrayType(getWidenedType((<TypeReference>type).typeArguments[0]));
Expand Down
44 changes: 22 additions & 22 deletions tests/baselines/reference/arrayBestCommonTypes.types
Original file line numberDiff line numberDiff line change
Expand Up@@ -294,8 +294,8 @@ module EmptyTypes {

// Order matters here so test all the variants
var a1 = [{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }];
>a1 : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>a1 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand All@@ -313,8 +313,8 @@ module EmptyTypes {
>'a' : string

var a2 = [{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }];
>a2 : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>a2 : { x: any; y: string; }[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: anyObj, y: 'a' } : { x: any; y: string; }
>x : any
>anyObj : any
Expand All@@ -332,8 +332,8 @@ module EmptyTypes {
>'a' : string

var a3 = [{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }];
>a3 : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>a3 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand DownExpand Up@@ -639,7 +639,7 @@ module NonEmptyTypes {
>x : number
>y : base
>base : base
>[{ x: 7, y: new derived() }, { x: 5, y: new base() }] : ({ x: number; y: derived; } | { x: number; y: base; })[]
>[{ x: 7, y: new derived() }, { x: 5, y: new base() }] : { x: number; y: base; }[]
>{ x: 7, y: new derived() } : { x: number; y: derived; }
>x : number
>7 : number
Expand All@@ -658,7 +658,7 @@ module NonEmptyTypes {
>x : boolean
>y : base
>base : base
>[{ x: true, y: new derived() }, { x: false, y: new base() }] : ({ x: boolean; y: derived; } | { x: boolean; y: base; })[]
>[{ x: true, y: new derived() }, { x: false, y: new base() }] : { x: boolean; y: base; }[]
>{ x: true, y: new derived() } : { x: boolean; y: derived; }
>x : boolean
>true : boolean
Expand DownExpand Up@@ -697,8 +697,8 @@ module NonEmptyTypes {

// Order matters here so test all the variants
var a1 = [{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }];
>a1 : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>a1 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand All@@ -716,8 +716,8 @@ module NonEmptyTypes {
>'a' : string

var a2 = [{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }];
>a2 : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>a2 : { x: any; y: string; }[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: anyObj, y: 'a' } : { x: any; y: string; }
>x : any
>anyObj : any
Expand All@@ -735,8 +735,8 @@ module NonEmptyTypes {
>'a' : string

var a3 = [{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }];
>a3 : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>a3 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand DownExpand Up@@ -769,29 +769,29 @@ module NonEmptyTypes {
>base2 : typeof base2

var b1 = [baseObj, base2Obj, ifaceObj];
>b1 : (base | base2 | iface)[]
>[baseObj, base2Obj, ifaceObj] : (base | base2 | iface)[]
>b1 : iface[]
>[baseObj, base2Obj, ifaceObj] : iface[]
>baseObj : base
>base2Obj : base2
>ifaceObj : iface

var b2 = [base2Obj, baseObj, ifaceObj];
>b2 : (base2 | base | iface)[]
>[base2Obj, baseObj, ifaceObj] : (base2 | base | iface)[]
>b2 : iface[]
>[base2Obj, baseObj, ifaceObj] : iface[]
>base2Obj : base2
>baseObj : base
>ifaceObj : iface

var b3 = [baseObj, ifaceObj, base2Obj];
>b3 : (base | iface | base2)[]
>[baseObj, ifaceObj, base2Obj] : (base | iface | base2)[]
>b3 : iface[]
>[baseObj, ifaceObj, base2Obj] : iface[]
>baseObj : base
>ifaceObj : iface
>base2Obj : base2

var b4 = [ifaceObj, baseObj, base2Obj];
>b4 : (iface | base | base2)[]
>[ifaceObj, baseObj, base2Obj] : (iface | base | base2)[]
>b4 : iface[]
>[ifaceObj, baseObj, base2Obj] : iface[]
>ifaceObj : iface
>baseObj : base
>base2Obj : base2
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,8 +36,8 @@ var cs = [a, b, c]; // { x: number; y?: number };[]
>c : { x: number; a?: number; }

var ds = [(x: Object) => 1, (x: string) => 2]; // { (x:Object) => number }[]
>ds : (((x: Object) => number) | ((x: string) => number))[]
>[(x: Object) => 1, (x: string) => 2] : (((x: Object) => number) | ((x: string) => number))[]
>ds : ((x: Object) => number)[]
>[(x: Object) => 1, (x: string) => 2] : ((x: Object) => number)[]
>(x: Object) => 1 : (x: Object) => number
>x : Object
>Object : Object
Expand All@@ -47,8 +47,8 @@ var ds = [(x: Object) => 1, (x: string) => 2]; // { (x:Object) => number }[]
>2 : number

var es = [(x: string) => 2, (x: Object) => 1]; // { (x:string) => number }[]
>es : (((x: string) => number) | ((x: Object) => number))[]
>[(x: string) => 2, (x: Object) => 1] : (((x: string) => number) | ((x: Object) => number))[]
>es : ((x: string) => number)[]
>[(x: string) => 2, (x: Object) => 1] : ((x: string) => number)[]
>(x: string) => 2 : (x: string) => number
>x : string
>2 : number
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -77,8 +77,8 @@ var myDerivedList: DerivedList<number>;
>DerivedList : DerivedList<U>

var as = [list, myDerivedList]; // List<number>[]
>as : (List<number> | DerivedList<number>)[]
>[list, myDerivedList] : (List<number> | DerivedList<number>)[]
>as : List<number>[]
>[list, myDerivedList] : List<number>[]
>list : List<number>
>myDerivedList : DerivedList<number>

Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
136 changes: 43 additions & 93 deletions src/compiler/checker.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3119,7 +3119,7 @@ namespace ts {
}

function resolveTupleTypeMembers(type: TupleType) {
let arrayType = resolveStructuredTypeMembers(createArrayType(getUnionType(type.elementTypes, /*noDeduplication*/ true)));
let arrayType = resolveStructuredTypeMembers(createArrayType(getUnionType(type.elementTypes, /*noSubtypeReduction*/ true)));
let members = createTupleTypeMemberSymbols(type.elementTypes);
addInheritedMembers(members, arrayType.properties);
setObjectTypeMembers(type, members, arrayType.callSignatures, arrayType.constructSignatures, arrayType.stringIndexType, arrayType.numberIndexType);
Expand DownExpand Up@@ -3451,29 +3451,6 @@ 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 && type !== globalObjectType) {
const resolved = resolveStructuredTypeMembers(type);
return !!(resolved.properties.length === 0 ||
resolved.stringIndexType ||
resolved.numberIndexType ||
getPropertyOfType(type, name));
}
if (type.flags & TypeFlags.UnionOrIntersection) {
for (let t of (<UnionOrIntersectionType>type).types) {
if (isKnownProperty(t, name)) {
return true;
}
}
return false;
}
return true;
}

function getSignaturesOfStructuredType(type: Type, kind: SignatureKind): Signature[] {
if (type.flags & TypeFlags.StructuredType) {
let resolved = resolveStructuredTypeMembers(<ObjectType>type);
Expand DownExpand Up@@ -4103,73 +4080,20 @@ namespace ts {
}
}

function isObjectLiteralTypeDuplicateOf(source: ObjectType, target: ObjectType): boolean {
let sourceProperties = getPropertiesOfObjectType(source);
let targetProperties = getPropertiesOfObjectType(target);
if (sourceProperties.length !== targetProperties.length) {
return false;
}
for (let sourceProp of sourceProperties) {
let targetProp = getPropertyOfObjectType(target, sourceProp.name);
if (!targetProp ||
getDeclarationFlagsFromSymbol(targetProp) & (NodeFlags.Private | NodeFlags.Protected) ||
!isTypeDuplicateOf(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp))) {
return false;
}
}
return true;
}

function isTupleTypeDuplicateOf(source: TupleType, target: TupleType): boolean {
let sourceTypes = source.elementTypes;
let targetTypes = target.elementTypes;
if (sourceTypes.length !== targetTypes.length) {
return false;
}
for (var i = 0; i < sourceTypes.length; i++) {
if (!isTypeDuplicateOf(sourceTypes[i], targetTypes[i])) {
return false;
}
}
return true;
}

// Returns true if the source type is a duplicate of the target type. A source type is a duplicate of
// a target type if the the two are identical, with the exception that the source type may have null or
// undefined in places where the target type doesn't. This is by design an asymmetric relationship.
function isTypeDuplicateOf(source: Type, target: Type): boolean {
if (source === target) {
return true;
}
if (source.flags & TypeFlags.Undefined || source.flags & TypeFlags.Null && !(target.flags & TypeFlags.Undefined)) {
return true;
}
if (source.flags & TypeFlags.ObjectLiteral && target.flags & TypeFlags.ObjectType) {
return isObjectLiteralTypeDuplicateOf(<ObjectType>source, <ObjectType>target);
}
if (isArrayType(source) && isArrayType(target)) {
return isTypeDuplicateOf((<TypeReference>source).typeArguments[0], (<TypeReference>target).typeArguments[0]);
}
if (isTupleType(source) && isTupleType(target)) {
return isTupleTypeDuplicateOf(<TupleType>source, <TupleType>target);
}
return isTypeIdenticalTo(source, target);
}

function isTypeDuplicateOfSomeType(candidate: Type, types: Type[]): boolean {
for (let type of types) {
if (candidate !== type && isTypeDuplicateOf(candidate, type)) {
function isSubtypeOfAny(candidate: Type, types: Type[]): boolean {
for (let i = 0, len = types.length; i < len; i++) {
if (candidate !== types[i] && isTypeSubtypeOf(candidate, types[i])) {
return true;
}
}
return false;
}

function removeDuplicateTypes(types: Type[]) {
function removeSubtypes(types: Type[]) {
let i = types.length;
while (i > 0) {
i--;
if (isTypeDuplicateOfSomeType(types[i], types)) {
if (isSubtypeOfAny(types[i], types)) {
types.splice(i, 1);
}
}
Expand All@@ -4194,12 +4118,14 @@ namespace ts {
}
}

// We always deduplicate the constituent type set based on object identity, but we'll also deduplicate
// based on the structure of the types unless the noDeduplication flag is true, which is the case when
// creating a union type from a type node and when instantiating a union type. In both of those cases,
// structural deduplication has to be deferred to properly support recursive union types. For example,
// a type of the form "type Item = string | (() => Item)" cannot be deduplicated during its declaration.
function getUnionType(types: Type[], noDeduplication?: boolean): Type {
// We reduce the constituent type set to only include types that aren't subtypes of other types, unless
// the noSubtypeReduction flag is specified, in which case we perform a simple deduplication based on
// object identity. Subtype reduction is possible only when union types are known not to circularly
// reference themselves (as is the case with union types created by expression constructs such as array
// literals and the || and ?: operators). Named types can circularly reference themselves and therefore
// cannot be deduplicated during their declaration. For example, "type Item = string | (() => Item" is
// a named type that circularly references itself.
function getUnionType(types: Type[], noSubtypeReduction?: boolean): Type {
if (types.length === 0) {
return emptyObjectType;
}
Expand All@@ -4208,12 +4134,12 @@ namespace ts {
if (containsTypeAny(typeSet)) {
return anyType;
}
if (noDeduplication) {
if (noSubtypeReduction) {
removeAllButLast(typeSet, undefinedType);
removeAllButLast(typeSet, nullType);
}
else {
removeDuplicateTypes(typeSet);
removeSubtypes(typeSet);
}
if (typeSet.length === 1) {
return typeSet[0];
Expand All@@ -4230,7 +4156,7 @@ namespace ts {
function getTypeFromUnionTypeNode(node: UnionTypeNode): Type {
let links = getNodeLinks(node);
if (!links.resolvedType) {
links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), /*noDeduplication*/ true);
links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), /*noSubtypeReduction*/ true);
}
return links.resolvedType;
}
Expand DownExpand Up@@ -4526,7 +4452,7 @@ namespace ts {
return createTupleType(instantiateList((<TupleType>type).elementTypes, mapper, instantiateType));
}
if (type.flags & TypeFlags.Union) {
return getUnionType(instantiateList((<UnionType>type).types, mapper, instantiateType), /*noDeduplication*/ true);
return getUnionType(instantiateList((<UnionType>type).types, mapper, instantiateType), /*noSubtypeReduction*/ true);
}
if (type.flags & TypeFlags.Intersection) {
return getIntersectionType(instantiateList((<IntersectionType>type).types, mapper, instantiateType));
Expand DownExpand Up@@ -4813,6 +4739,30 @@ namespace ts {
return Ternary.False;
}

// 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 and the check is for assignability, if the object type has
// index signatures, or if the property is actually declared in the object 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 {

Choose a reason for hiding this comment

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

Perhaps isExpectedProperty would better describe this.

if (type.flags & TypeFlags.ObjectType) {
const resolved = resolveStructuredTypeMembers(type);
if (relation === assignableRelation && (type === globalObjectType || resolved.properties.length === 0) ||
resolved.stringIndexType || resolved.numberIndexType || getPropertyOfType(type, name)) {
return true;
}
return false;
}
if (type.flags & TypeFlags.UnionOrIntersection) {
for (let t of (<UnionOrIntersectionType>type).types) {
if (isKnownProperty(t, name)) {
return true;
}
}
return false;
}
return true;
}

function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean {
for (let prop of getPropertiesOfObjectType(source)) {
if (!isKnownProperty(target, prop.name)) {
Expand DownExpand Up@@ -5594,7 +5544,7 @@ namespace ts {
return getWidenedTypeOfObjectLiteral(type);
}
if (type.flags & TypeFlags.Union) {
return getUnionType(map((<UnionType>type).types, getWidenedType));
return getUnionType(map((<UnionType>type).types, getWidenedType), /*noSubtypeReduction*/ true);
}
if (isArrayType(type)) {
return createArrayType(getWidenedType((<TypeReference>type).typeArguments[0]));
Expand Down
44 changes: 22 additions & 22 deletions tests/baselines/reference/arrayBestCommonTypes.types
Original file line numberDiff line numberDiff line change
Expand Up@@ -294,8 +294,8 @@ module EmptyTypes {

// Order matters here so test all the variants
var a1 = [{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }];
>a1 : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>a1 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand All@@ -313,8 +313,8 @@ module EmptyTypes {
>'a' : string

var a2 = [{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }];
>a2 : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>a2 : { x: any; y: string; }[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: anyObj, y: 'a' } : { x: any; y: string; }
>x : any
>anyObj : any
Expand All@@ -332,8 +332,8 @@ module EmptyTypes {
>'a' : string

var a3 = [{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }];
>a3 : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>a3 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand DownExpand Up@@ -639,7 +639,7 @@ module NonEmptyTypes {
>x : number
>y : base
>base : base
>[{ x: 7, y: new derived() }, { x: 5, y: new base() }] : ({ x: number; y: derived; } | { x: number; y: base; })[]
>[{ x: 7, y: new derived() }, { x: 5, y: new base() }] : { x: number; y: base; }[]
>{ x: 7, y: new derived() } : { x: number; y: derived; }
>x : number
>7 : number
Expand All@@ -658,7 +658,7 @@ module NonEmptyTypes {
>x : boolean
>y : base
>base : base
>[{ x: true, y: new derived() }, { x: false, y: new base() }] : ({ x: boolean; y: derived; } | { x: boolean; y: base; })[]
>[{ x: true, y: new derived() }, { x: false, y: new base() }] : { x: boolean; y: base; }[]
>{ x: true, y: new derived() } : { x: boolean; y: derived; }
>x : boolean
>true : boolean
Expand DownExpand Up@@ -697,8 +697,8 @@ module NonEmptyTypes {

// Order matters here so test all the variants
var a1 = [{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }];
>a1 : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>a1 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand All@@ -716,8 +716,8 @@ module NonEmptyTypes {
>'a' : string

var a2 = [{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }];
>a2 : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>a2 : { x: any; y: string; }[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: anyObj, y: 'a' } : { x: any; y: string; }
>x : any
>anyObj : any
Expand All@@ -735,8 +735,8 @@ module NonEmptyTypes {
>'a' : string

var a3 = [{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }];
>a3 : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>a3 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand DownExpand Up@@ -769,29 +769,29 @@ module NonEmptyTypes {
>base2 : typeof base2

var b1 = [baseObj, base2Obj, ifaceObj];
>b1 : (base | base2 | iface)[]
>[baseObj, base2Obj, ifaceObj] : (base | base2 | iface)[]
>b1 : iface[]
>[baseObj, base2Obj, ifaceObj] : iface[]
>baseObj : base
>base2Obj : base2
>ifaceObj : iface

var b2 = [base2Obj, baseObj, ifaceObj];
>b2 : (base2 | base | iface)[]
>[base2Obj, baseObj, ifaceObj] : (base2 | base | iface)[]
>b2 : iface[]
>[base2Obj, baseObj, ifaceObj] : iface[]
>base2Obj : base2
>baseObj : base
>ifaceObj : iface

var b3 = [baseObj, ifaceObj, base2Obj];
>b3 : (base | iface | base2)[]
>[baseObj, ifaceObj, base2Obj] : (base | iface | base2)[]
>b3 : iface[]
>[baseObj, ifaceObj, base2Obj] : iface[]
>baseObj : base
>ifaceObj : iface
>base2Obj : base2

var b4 = [ifaceObj, baseObj, base2Obj];
>b4 : (iface | base | base2)[]
>[ifaceObj, baseObj, base2Obj] : (iface | base | base2)[]
>b4 : iface[]
>[ifaceObj, baseObj, base2Obj] : iface[]
>ifaceObj : iface
>baseObj : base
>base2Obj : base2
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,8 +36,8 @@ var cs = [a, b, c]; // { x: number; y?: number };[]
>c : { x: number; a?: number; }

var ds = [(x: Object) => 1, (x: string) => 2]; // { (x:Object) => number }[]
>ds : (((x: Object) => number) | ((x: string) => number))[]
>[(x: Object) => 1, (x: string) => 2] : (((x: Object) => number) | ((x: string) => number))[]
>ds : ((x: Object) => number)[]
>[(x: Object) => 1, (x: string) => 2] : ((x: Object) => number)[]
>(x: Object) => 1 : (x: Object) => number
>x : Object
>Object : Object
Expand All@@ -47,8 +47,8 @@ var ds = [(x: Object) => 1, (x: string) => 2]; // { (x:Object) => number }[]
>2 : number

var es = [(x: string) => 2, (x: Object) => 1]; // { (x:string) => number }[]
>es : (((x: string) => number) | ((x: Object) => number))[]
>[(x: string) => 2, (x: Object) => 1] : (((x: string) => number) | ((x: Object) => number))[]
>es : ((x: string) => number)[]
>[(x: string) => 2, (x: Object) => 1] : ((x: string) => number)[]
>(x: string) => 2 : (x: string) => number
>x : string
>2 : number
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -77,8 +77,8 @@ var myDerivedList: DerivedList<number>;
>DerivedList : DerivedList<U>

var as = [list, myDerivedList]; // List<number>[]
>as : (List<number> | DerivedList<number>)[]
>[list, myDerivedList] : (List<number> | DerivedList<number>)[]
>as : List<number>[]
>[list, myDerivedList] : List<number>[]
>list : List<number>
>myDerivedList : DerivedList<number>

Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
136 changes: 43 additions & 93 deletions src/compiler/checker.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3119,7 +3119,7 @@ namespace ts {
}

function resolveTupleTypeMembers(type: TupleType) {
let arrayType = resolveStructuredTypeMembers(createArrayType(getUnionType(type.elementTypes, /*noDeduplication*/ true)));
let arrayType = resolveStructuredTypeMembers(createArrayType(getUnionType(type.elementTypes, /*noSubtypeReduction*/ true)));
let members = createTupleTypeMemberSymbols(type.elementTypes);
addInheritedMembers(members, arrayType.properties);
setObjectTypeMembers(type, members, arrayType.callSignatures, arrayType.constructSignatures, arrayType.stringIndexType, arrayType.numberIndexType);
Expand DownExpand Up@@ -3451,29 +3451,6 @@ 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 && type !== globalObjectType) {
const resolved = resolveStructuredTypeMembers(type);
return !!(resolved.properties.length === 0 ||
resolved.stringIndexType ||
resolved.numberIndexType ||
getPropertyOfType(type, name));
}
if (type.flags & TypeFlags.UnionOrIntersection) {
for (let t of (<UnionOrIntersectionType>type).types) {
if (isKnownProperty(t, name)) {
return true;
}
}
return false;
}
return true;
}

function getSignaturesOfStructuredType(type: Type, kind: SignatureKind): Signature[] {
if (type.flags & TypeFlags.StructuredType) {
let resolved = resolveStructuredTypeMembers(<ObjectType>type);
Expand DownExpand Up@@ -4103,73 +4080,20 @@ namespace ts {
}
}

function isObjectLiteralTypeDuplicateOf(source: ObjectType, target: ObjectType): boolean {
let sourceProperties = getPropertiesOfObjectType(source);
let targetProperties = getPropertiesOfObjectType(target);
if (sourceProperties.length !== targetProperties.length) {
return false;
}
for (let sourceProp of sourceProperties) {
let targetProp = getPropertyOfObjectType(target, sourceProp.name);
if (!targetProp ||
getDeclarationFlagsFromSymbol(targetProp) & (NodeFlags.Private | NodeFlags.Protected) ||
!isTypeDuplicateOf(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp))) {
return false;
}
}
return true;
}

function isTupleTypeDuplicateOf(source: TupleType, target: TupleType): boolean {
let sourceTypes = source.elementTypes;
let targetTypes = target.elementTypes;
if (sourceTypes.length !== targetTypes.length) {
return false;
}
for (var i = 0; i < sourceTypes.length; i++) {
if (!isTypeDuplicateOf(sourceTypes[i], targetTypes[i])) {
return false;
}
}
return true;
}

// Returns true if the source type is a duplicate of the target type. A source type is a duplicate of
// a target type if the the two are identical, with the exception that the source type may have null or
// undefined in places where the target type doesn't. This is by design an asymmetric relationship.
function isTypeDuplicateOf(source: Type, target: Type): boolean {
if (source === target) {
return true;
}
if (source.flags & TypeFlags.Undefined || source.flags & TypeFlags.Null && !(target.flags & TypeFlags.Undefined)) {
return true;
}
if (source.flags & TypeFlags.ObjectLiteral && target.flags & TypeFlags.ObjectType) {
return isObjectLiteralTypeDuplicateOf(<ObjectType>source, <ObjectType>target);
}
if (isArrayType(source) && isArrayType(target)) {
return isTypeDuplicateOf((<TypeReference>source).typeArguments[0], (<TypeReference>target).typeArguments[0]);
}
if (isTupleType(source) && isTupleType(target)) {
return isTupleTypeDuplicateOf(<TupleType>source, <TupleType>target);
}
return isTypeIdenticalTo(source, target);
}

function isTypeDuplicateOfSomeType(candidate: Type, types: Type[]): boolean {
for (let type of types) {
if (candidate !== type && isTypeDuplicateOf(candidate, type)) {
function isSubtypeOfAny(candidate: Type, types: Type[]): boolean {
for (let i = 0, len = types.length; i < len; i++) {
if (candidate !== types[i] && isTypeSubtypeOf(candidate, types[i])) {
return true;
}
}
return false;
}

function removeDuplicateTypes(types: Type[]) {
function removeSubtypes(types: Type[]) {
let i = types.length;
while (i > 0) {
i--;
if (isTypeDuplicateOfSomeType(types[i], types)) {
if (isSubtypeOfAny(types[i], types)) {
types.splice(i, 1);
}
}
Expand All@@ -4194,12 +4118,14 @@ namespace ts {
}
}

// We always deduplicate the constituent type set based on object identity, but we'll also deduplicate
// based on the structure of the types unless the noDeduplication flag is true, which is the case when
// creating a union type from a type node and when instantiating a union type. In both of those cases,
// structural deduplication has to be deferred to properly support recursive union types. For example,
// a type of the form "type Item = string | (() => Item)" cannot be deduplicated during its declaration.
function getUnionType(types: Type[], noDeduplication?: boolean): Type {
// We reduce the constituent type set to only include types that aren't subtypes of other types, unless
// the noSubtypeReduction flag is specified, in which case we perform a simple deduplication based on
// object identity. Subtype reduction is possible only when union types are known not to circularly
// reference themselves (as is the case with union types created by expression constructs such as array
// literals and the || and ?: operators). Named types can circularly reference themselves and therefore
// cannot be deduplicated during their declaration. For example, "type Item = string | (() => Item" is
// a named type that circularly references itself.
function getUnionType(types: Type[], noSubtypeReduction?: boolean): Type {
if (types.length === 0) {
return emptyObjectType;
}
Expand All@@ -4208,12 +4134,12 @@ namespace ts {
if (containsTypeAny(typeSet)) {
return anyType;
}
if (noDeduplication) {
if (noSubtypeReduction) {
removeAllButLast(typeSet, undefinedType);
removeAllButLast(typeSet, nullType);
}
else {
removeDuplicateTypes(typeSet);
removeSubtypes(typeSet);
}
if (typeSet.length === 1) {
return typeSet[0];
Expand All@@ -4230,7 +4156,7 @@ namespace ts {
function getTypeFromUnionTypeNode(node: UnionTypeNode): Type {
let links = getNodeLinks(node);
if (!links.resolvedType) {
links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), /*noDeduplication*/ true);
links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), /*noSubtypeReduction*/ true);
}
return links.resolvedType;
}
Expand DownExpand Up@@ -4526,7 +4452,7 @@ namespace ts {
return createTupleType(instantiateList((<TupleType>type).elementTypes, mapper, instantiateType));
}
if (type.flags & TypeFlags.Union) {
return getUnionType(instantiateList((<UnionType>type).types, mapper, instantiateType), /*noDeduplication*/ true);
return getUnionType(instantiateList((<UnionType>type).types, mapper, instantiateType), /*noSubtypeReduction*/ true);
}
if (type.flags & TypeFlags.Intersection) {
return getIntersectionType(instantiateList((<IntersectionType>type).types, mapper, instantiateType));
Expand DownExpand Up@@ -4813,6 +4739,30 @@ namespace ts {
return Ternary.False;
}

// 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 and the check is for assignability, if the object type has
// index signatures, or if the property is actually declared in the object 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 {

Choose a reason for hiding this comment

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

Perhaps isExpectedProperty would better describe this.

if (type.flags & TypeFlags.ObjectType) {
const resolved = resolveStructuredTypeMembers(type);
if (relation === assignableRelation && (type === globalObjectType || resolved.properties.length === 0) ||
resolved.stringIndexType || resolved.numberIndexType || getPropertyOfType(type, name)) {
return true;
}
return false;
}
if (type.flags & TypeFlags.UnionOrIntersection) {
for (let t of (<UnionOrIntersectionType>type).types) {
if (isKnownProperty(t, name)) {
return true;
}
}
return false;
}
return true;
}

function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean {
for (let prop of getPropertiesOfObjectType(source)) {
if (!isKnownProperty(target, prop.name)) {
Expand DownExpand Up@@ -5594,7 +5544,7 @@ namespace ts {
return getWidenedTypeOfObjectLiteral(type);
}
if (type.flags & TypeFlags.Union) {
return getUnionType(map((<UnionType>type).types, getWidenedType));
return getUnionType(map((<UnionType>type).types, getWidenedType), /*noSubtypeReduction*/ true);
}
if (isArrayType(type)) {
return createArrayType(getWidenedType((<TypeReference>type).typeArguments[0]));
Expand Down
44 changes: 22 additions & 22 deletions tests/baselines/reference/arrayBestCommonTypes.types
Original file line numberDiff line numberDiff line change
Expand Up@@ -294,8 +294,8 @@ module EmptyTypes {

// Order matters here so test all the variants
var a1 = [{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }];
>a1 : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>a1 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand All@@ -313,8 +313,8 @@ module EmptyTypes {
>'a' : string

var a2 = [{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }];
>a2 : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>a2 : { x: any; y: string; }[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: anyObj, y: 'a' } : { x: any; y: string; }
>x : any
>anyObj : any
Expand All@@ -332,8 +332,8 @@ module EmptyTypes {
>'a' : string

var a3 = [{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }];
>a3 : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>a3 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand DownExpand Up@@ -639,7 +639,7 @@ module NonEmptyTypes {
>x : number
>y : base
>base : base
>[{ x: 7, y: new derived() }, { x: 5, y: new base() }] : ({ x: number; y: derived; } | { x: number; y: base; })[]
>[{ x: 7, y: new derived() }, { x: 5, y: new base() }] : { x: number; y: base; }[]
>{ x: 7, y: new derived() } : { x: number; y: derived; }
>x : number
>7 : number
Expand All@@ -658,7 +658,7 @@ module NonEmptyTypes {
>x : boolean
>y : base
>base : base
>[{ x: true, y: new derived() }, { x: false, y: new base() }] : ({ x: boolean; y: derived; } | { x: boolean; y: base; })[]
>[{ x: true, y: new derived() }, { x: false, y: new base() }] : { x: boolean; y: base; }[]
>{ x: true, y: new derived() } : { x: boolean; y: derived; }
>x : boolean
>true : boolean
Expand DownExpand Up@@ -697,8 +697,8 @@ module NonEmptyTypes {

// Order matters here so test all the variants
var a1 = [{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }];
>a1 : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>a1 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand All@@ -716,8 +716,8 @@ module NonEmptyTypes {
>'a' : string

var a2 = [{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }];
>a2 : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>a2 : { x: any; y: string; }[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: anyObj, y: 'a' } : { x: any; y: string; }
>x : any
>anyObj : any
Expand All@@ -735,8 +735,8 @@ module NonEmptyTypes {
>'a' : string

var a3 = [{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }];
>a3 : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>a3 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand DownExpand Up@@ -769,29 +769,29 @@ module NonEmptyTypes {
>base2 : typeof base2

var b1 = [baseObj, base2Obj, ifaceObj];
>b1 : (base | base2 | iface)[]
>[baseObj, base2Obj, ifaceObj] : (base | base2 | iface)[]
>b1 : iface[]
>[baseObj, base2Obj, ifaceObj] : iface[]
>baseObj : base
>base2Obj : base2
>ifaceObj : iface

var b2 = [base2Obj, baseObj, ifaceObj];
>b2 : (base2 | base | iface)[]
>[base2Obj, baseObj, ifaceObj] : (base2 | base | iface)[]
>b2 : iface[]
>[base2Obj, baseObj, ifaceObj] : iface[]
>base2Obj : base2
>baseObj : base
>ifaceObj : iface

var b3 = [baseObj, ifaceObj, base2Obj];
>b3 : (base | iface | base2)[]
>[baseObj, ifaceObj, base2Obj] : (base | iface | base2)[]
>b3 : iface[]
>[baseObj, ifaceObj, base2Obj] : iface[]
>baseObj : base
>ifaceObj : iface
>base2Obj : base2

var b4 = [ifaceObj, baseObj, base2Obj];
>b4 : (iface | base | base2)[]
>[ifaceObj, baseObj, base2Obj] : (iface | base | base2)[]
>b4 : iface[]
>[ifaceObj, baseObj, base2Obj] : iface[]
>ifaceObj : iface
>baseObj : base
>base2Obj : base2
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,8 +36,8 @@ var cs = [a, b, c]; // { x: number; y?: number };[]
>c : { x: number; a?: number; }

var ds = [(x: Object) => 1, (x: string) => 2]; // { (x:Object) => number }[]
>ds : (((x: Object) => number) | ((x: string) => number))[]
>[(x: Object) => 1, (x: string) => 2] : (((x: Object) => number) | ((x: string) => number))[]
>ds : ((x: Object) => number)[]
>[(x: Object) => 1, (x: string) => 2] : ((x: Object) => number)[]
>(x: Object) => 1 : (x: Object) => number
>x : Object
>Object : Object
Expand All@@ -47,8 +47,8 @@ var ds = [(x: Object) => 1, (x: string) => 2]; // { (x:Object) => number }[]
>2 : number

var es = [(x: string) => 2, (x: Object) => 1]; // { (x:string) => number }[]
>es : (((x: string) => number) | ((x: Object) => number))[]
>[(x: string) => 2, (x: Object) => 1] : (((x: string) => number) | ((x: Object) => number))[]
>es : ((x: string) => number)[]
>[(x: string) => 2, (x: Object) => 1] : ((x: string) => number)[]
>(x: string) => 2 : (x: string) => number
>x : string
>2 : number
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -77,8 +77,8 @@ var myDerivedList: DerivedList<number>;
>DerivedList : DerivedList<U>

var as = [list, myDerivedList]; // List<number>[]
>as : (List<number> | DerivedList<number>)[]
>[list, myDerivedList] : (List<number> | DerivedList<number>)[]
>as : List<number>[]
>[list, myDerivedList] : List<number>[]
>list : List<number>
>myDerivedList : DerivedList<number>

Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
136 changes: 43 additions & 93 deletions src/compiler/checker.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3119,7 +3119,7 @@ namespace ts {
}

function resolveTupleTypeMembers(type: TupleType) {
let arrayType = resolveStructuredTypeMembers(createArrayType(getUnionType(type.elementTypes, /*noDeduplication*/ true)));
let arrayType = resolveStructuredTypeMembers(createArrayType(getUnionType(type.elementTypes, /*noSubtypeReduction*/ true)));
let members = createTupleTypeMemberSymbols(type.elementTypes);
addInheritedMembers(members, arrayType.properties);
setObjectTypeMembers(type, members, arrayType.callSignatures, arrayType.constructSignatures, arrayType.stringIndexType, arrayType.numberIndexType);
Expand DownExpand Up@@ -3451,29 +3451,6 @@ 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 && type !== globalObjectType) {
const resolved = resolveStructuredTypeMembers(type);
return !!(resolved.properties.length === 0 ||
resolved.stringIndexType ||
resolved.numberIndexType ||
getPropertyOfType(type, name));
}
if (type.flags & TypeFlags.UnionOrIntersection) {
for (let t of (<UnionOrIntersectionType>type).types) {
if (isKnownProperty(t, name)) {
return true;
}
}
return false;
}
return true;
}

function getSignaturesOfStructuredType(type: Type, kind: SignatureKind): Signature[] {
if (type.flags & TypeFlags.StructuredType) {
let resolved = resolveStructuredTypeMembers(<ObjectType>type);
Expand DownExpand Up@@ -4103,73 +4080,20 @@ namespace ts {
}
}

function isObjectLiteralTypeDuplicateOf(source: ObjectType, target: ObjectType): boolean {
let sourceProperties = getPropertiesOfObjectType(source);
let targetProperties = getPropertiesOfObjectType(target);
if (sourceProperties.length !== targetProperties.length) {
return false;
}
for (let sourceProp of sourceProperties) {
let targetProp = getPropertyOfObjectType(target, sourceProp.name);
if (!targetProp ||
getDeclarationFlagsFromSymbol(targetProp) & (NodeFlags.Private | NodeFlags.Protected) ||
!isTypeDuplicateOf(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp))) {
return false;
}
}
return true;
}

function isTupleTypeDuplicateOf(source: TupleType, target: TupleType): boolean {
let sourceTypes = source.elementTypes;
let targetTypes = target.elementTypes;
if (sourceTypes.length !== targetTypes.length) {
return false;
}
for (var i = 0; i < sourceTypes.length; i++) {
if (!isTypeDuplicateOf(sourceTypes[i], targetTypes[i])) {
return false;
}
}
return true;
}

// Returns true if the source type is a duplicate of the target type. A source type is a duplicate of
// a target type if the the two are identical, with the exception that the source type may have null or
// undefined in places where the target type doesn't. This is by design an asymmetric relationship.
function isTypeDuplicateOf(source: Type, target: Type): boolean {
if (source === target) {
return true;
}
if (source.flags & TypeFlags.Undefined || source.flags & TypeFlags.Null && !(target.flags & TypeFlags.Undefined)) {
return true;
}
if (source.flags & TypeFlags.ObjectLiteral && target.flags & TypeFlags.ObjectType) {
return isObjectLiteralTypeDuplicateOf(<ObjectType>source, <ObjectType>target);
}
if (isArrayType(source) && isArrayType(target)) {
return isTypeDuplicateOf((<TypeReference>source).typeArguments[0], (<TypeReference>target).typeArguments[0]);
}
if (isTupleType(source) && isTupleType(target)) {
return isTupleTypeDuplicateOf(<TupleType>source, <TupleType>target);
}
return isTypeIdenticalTo(source, target);
}

function isTypeDuplicateOfSomeType(candidate: Type, types: Type[]): boolean {
for (let type of types) {
if (candidate !== type && isTypeDuplicateOf(candidate, type)) {
function isSubtypeOfAny(candidate: Type, types: Type[]): boolean {
for (let i = 0, len = types.length; i < len; i++) {
if (candidate !== types[i] && isTypeSubtypeOf(candidate, types[i])) {
return true;
}
}
return false;
}

function removeDuplicateTypes(types: Type[]) {
function removeSubtypes(types: Type[]) {
let i = types.length;
while (i > 0) {
i--;
if (isTypeDuplicateOfSomeType(types[i], types)) {
if (isSubtypeOfAny(types[i], types)) {
types.splice(i, 1);
}
}
Expand All@@ -4194,12 +4118,14 @@ namespace ts {
}
}

// We always deduplicate the constituent type set based on object identity, but we'll also deduplicate
// based on the structure of the types unless the noDeduplication flag is true, which is the case when
// creating a union type from a type node and when instantiating a union type. In both of those cases,
// structural deduplication has to be deferred to properly support recursive union types. For example,
// a type of the form "type Item = string | (() => Item)" cannot be deduplicated during its declaration.
function getUnionType(types: Type[], noDeduplication?: boolean): Type {
// We reduce the constituent type set to only include types that aren't subtypes of other types, unless
// the noSubtypeReduction flag is specified, in which case we perform a simple deduplication based on
// object identity. Subtype reduction is possible only when union types are known not to circularly
// reference themselves (as is the case with union types created by expression constructs such as array
// literals and the || and ?: operators). Named types can circularly reference themselves and therefore
// cannot be deduplicated during their declaration. For example, "type Item = string | (() => Item" is
// a named type that circularly references itself.
function getUnionType(types: Type[], noSubtypeReduction?: boolean): Type {
if (types.length === 0) {
return emptyObjectType;
}
Expand All@@ -4208,12 +4134,12 @@ namespace ts {
if (containsTypeAny(typeSet)) {
return anyType;
}
if (noDeduplication) {
if (noSubtypeReduction) {
removeAllButLast(typeSet, undefinedType);
removeAllButLast(typeSet, nullType);
}
else {
removeDuplicateTypes(typeSet);
removeSubtypes(typeSet);
}
if (typeSet.length === 1) {
return typeSet[0];
Expand All@@ -4230,7 +4156,7 @@ namespace ts {
function getTypeFromUnionTypeNode(node: UnionTypeNode): Type {
let links = getNodeLinks(node);
if (!links.resolvedType) {
links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), /*noDeduplication*/ true);
links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), /*noSubtypeReduction*/ true);
}
return links.resolvedType;
}
Expand DownExpand Up@@ -4526,7 +4452,7 @@ namespace ts {
return createTupleType(instantiateList((<TupleType>type).elementTypes, mapper, instantiateType));
}
if (type.flags & TypeFlags.Union) {
return getUnionType(instantiateList((<UnionType>type).types, mapper, instantiateType), /*noDeduplication*/ true);
return getUnionType(instantiateList((<UnionType>type).types, mapper, instantiateType), /*noSubtypeReduction*/ true);
}
if (type.flags & TypeFlags.Intersection) {
return getIntersectionType(instantiateList((<IntersectionType>type).types, mapper, instantiateType));
Expand DownExpand Up@@ -4813,6 +4739,30 @@ namespace ts {
return Ternary.False;
}

// 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 and the check is for assignability, if the object type has
// index signatures, or if the property is actually declared in the object 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 {

Choose a reason for hiding this comment

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

Perhaps isExpectedProperty would better describe this.

if (type.flags & TypeFlags.ObjectType) {
const resolved = resolveStructuredTypeMembers(type);
if (relation === assignableRelation && (type === globalObjectType || resolved.properties.length === 0) ||
resolved.stringIndexType || resolved.numberIndexType || getPropertyOfType(type, name)) {
return true;
}
return false;
}
if (type.flags & TypeFlags.UnionOrIntersection) {
for (let t of (<UnionOrIntersectionType>type).types) {
if (isKnownProperty(t, name)) {
return true;
}
}
return false;
}
return true;
}

function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean {
for (let prop of getPropertiesOfObjectType(source)) {
if (!isKnownProperty(target, prop.name)) {
Expand DownExpand Up@@ -5594,7 +5544,7 @@ namespace ts {
return getWidenedTypeOfObjectLiteral(type);
}
if (type.flags & TypeFlags.Union) {
return getUnionType(map((<UnionType>type).types, getWidenedType));
return getUnionType(map((<UnionType>type).types, getWidenedType), /*noSubtypeReduction*/ true);
}
if (isArrayType(type)) {
return createArrayType(getWidenedType((<TypeReference>type).typeArguments[0]));
Expand Down
44 changes: 22 additions & 22 deletions tests/baselines/reference/arrayBestCommonTypes.types
Original file line numberDiff line numberDiff line change
Expand Up@@ -294,8 +294,8 @@ module EmptyTypes {

// Order matters here so test all the variants
var a1 = [{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }];
>a1 : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>a1 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand All@@ -313,8 +313,8 @@ module EmptyTypes {
>'a' : string

var a2 = [{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }];
>a2 : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>a2 : { x: any; y: string; }[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: anyObj, y: 'a' } : { x: any; y: string; }
>x : any
>anyObj : any
Expand All@@ -332,8 +332,8 @@ module EmptyTypes {
>'a' : string

var a3 = [{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }];
>a3 : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>a3 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand DownExpand Up@@ -639,7 +639,7 @@ module NonEmptyTypes {
>x : number
>y : base
>base : base
>[{ x: 7, y: new derived() }, { x: 5, y: new base() }] : ({ x: number; y: derived; } | { x: number; y: base; })[]
>[{ x: 7, y: new derived() }, { x: 5, y: new base() }] : { x: number; y: base; }[]
>{ x: 7, y: new derived() } : { x: number; y: derived; }
>x : number
>7 : number
Expand All@@ -658,7 +658,7 @@ module NonEmptyTypes {
>x : boolean
>y : base
>base : base
>[{ x: true, y: new derived() }, { x: false, y: new base() }] : ({ x: boolean; y: derived; } | { x: boolean; y: base; })[]
>[{ x: true, y: new derived() }, { x: false, y: new base() }] : { x: boolean; y: base; }[]
>{ x: true, y: new derived() } : { x: boolean; y: derived; }
>x : boolean
>true : boolean
Expand DownExpand Up@@ -697,8 +697,8 @@ module NonEmptyTypes {

// Order matters here so test all the variants
var a1 = [{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }];
>a1 : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
>a1 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand All@@ -716,8 +716,8 @@ module NonEmptyTypes {
>'a' : string

var a2 = [{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }];
>a2 : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
>a2 : { x: any; y: string; }[]
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: anyObj, y: 'a' } : { x: any; y: string; }
>x : any
>anyObj : any
Expand All@@ -735,8 +735,8 @@ module NonEmptyTypes {
>'a' : string

var a3 = [{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }];
>a3 : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
>a3 : { x: any; y: string; }[]
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
>{ x: 0, y: 'a' } : { x: number; y: string; }
>x : number
>0 : number
Expand DownExpand Up@@ -769,29 +769,29 @@ module NonEmptyTypes {
>base2 : typeof base2

var b1 = [baseObj, base2Obj, ifaceObj];
>b1 : (base | base2 | iface)[]
>[baseObj, base2Obj, ifaceObj] : (base | base2 | iface)[]
>b1 : iface[]
>[baseObj, base2Obj, ifaceObj] : iface[]
>baseObj : base
>base2Obj : base2
>ifaceObj : iface

var b2 = [base2Obj, baseObj, ifaceObj];
>b2 : (base2 | base | iface)[]
>[base2Obj, baseObj, ifaceObj] : (base2 | base | iface)[]
>b2 : iface[]
>[base2Obj, baseObj, ifaceObj] : iface[]
>base2Obj : base2
>baseObj : base
>ifaceObj : iface

var b3 = [baseObj, ifaceObj, base2Obj];
>b3 : (base | iface | base2)[]
>[baseObj, ifaceObj, base2Obj] : (base | iface | base2)[]
>b3 : iface[]
>[baseObj, ifaceObj, base2Obj] : iface[]
>baseObj : base
>ifaceObj : iface
>base2Obj : base2

var b4 = [ifaceObj, baseObj, base2Obj];
>b4 : (iface | base | base2)[]
>[ifaceObj, baseObj, base2Obj] : (iface | base | base2)[]
>b4 : iface[]
>[ifaceObj, baseObj, base2Obj] : iface[]
>ifaceObj : iface
>baseObj : base
>base2Obj : base2
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,8 +36,8 @@ var cs = [a, b, c]; // { x: number; y?: number };[]
>c : { x: number; a?: number; }

var ds = [(x: Object) => 1, (x: string) => 2]; // { (x:Object) => number }[]
>ds : (((x: Object) => number) | ((x: string) => number))[]
>[(x: Object) => 1, (x: string) => 2] : (((x: Object) => number) | ((x: string) => number))[]
>ds : ((x: Object) => number)[]
>[(x: Object) => 1, (x: string) => 2] : ((x: Object) => number)[]
>(x: Object) => 1 : (x: Object) => number
>x : Object
>Object : Object
Expand All@@ -47,8 +47,8 @@ var ds = [(x: Object) => 1, (x: string) => 2]; // { (x:Object) => number }[]
>2 : number

var es = [(x: string) => 2, (x: Object) => 1]; // { (x:string) => number }[]
>es : (((x: string) => number) | ((x: Object) => number))[]
>[(x: string) => 2, (x: Object) => 1] : (((x: string) => number) | ((x: Object) => number))[]
>es : ((x: string) => number)[]
>[(x: string) => 2, (x: Object) => 1] : ((x: string) => number)[]
>(x: string) => 2 : (x: string) => number
>x : string
>2 : number
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -77,8 +77,8 @@ var myDerivedList: DerivedList<number>;
>DerivedList : DerivedList<U>

var as = [list, myDerivedList]; // List<number>[]
>as : (List<number> | DerivedList<number>)[]
>[list, myDerivedList] : (List<number> | DerivedList<number>)[]
>as : List<number>[]
>[list, myDerivedList] : List<number>[]
>list : List<number>
>myDerivedList : DerivedList<number>

Loading