mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-20 05:17:43 -05:00
Relate a source type that is sufficiently covered by a target discriminated union
This commit is contained in:
@@ -12409,7 +12409,7 @@ namespace ts {
|
||||
if (result && isPerformingExcessPropertyChecks) {
|
||||
// Validate against excess props using the original `source`
|
||||
const discriminantType = target.flags & TypeFlags.Union ? findMatchingDiscriminantType(source, target as UnionType) : undefined;
|
||||
if (!propertiesRelatedTo(source, discriminantType || target, reportErrors)) {
|
||||
if (!propertiesRelatedTo(source, discriminantType || target, reportErrors, /*excludedProperties*/ undefined)) {
|
||||
return Ternary.False;
|
||||
}
|
||||
}
|
||||
@@ -12419,7 +12419,7 @@ namespace ts {
|
||||
result = typeRelatedToEachType(getRegularTypeOfObjectLiteral(source), target as IntersectionType, reportErrors);
|
||||
if (result && isPerformingExcessPropertyChecks) {
|
||||
// Validate against excess props using the original `source`
|
||||
if (!propertiesRelatedTo(source, target, reportErrors)) {
|
||||
if (!propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined)) {
|
||||
return Ternary.False;
|
||||
}
|
||||
}
|
||||
@@ -13097,7 +13097,7 @@ namespace ts {
|
||||
if (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) {
|
||||
// Report structural errors only if we haven't reported any errors yet
|
||||
const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo && !sourceIsPrimitive;
|
||||
result = propertiesRelatedTo(source, target, reportStructuralErrors);
|
||||
result = propertiesRelatedTo(source, target, reportStructuralErrors, /*excludedProperties*/ undefined);
|
||||
if (result) {
|
||||
result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportStructuralErrors);
|
||||
if (result) {
|
||||
@@ -13117,6 +13117,19 @@ namespace ts {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
// If S is an object type and T is a discriminated union, S may be related to T if
|
||||
// there exists a constituent of T for every combination of the discriminants of S
|
||||
// with respect to T. We do not report errors here, as we will use the existing
|
||||
// error result from checking each constituent of the union.
|
||||
if (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Union) {
|
||||
const objectOnlyTarget = extractTypesOfKind(target, TypeFlags.Object);
|
||||
if (objectOnlyTarget.flags & TypeFlags.Union) {
|
||||
const result = typeRelatedToDiscriminatedType(source, objectOnlyTarget as UnionType);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Ternary.False;
|
||||
|
||||
@@ -13180,9 +13193,185 @@ namespace ts {
|
||||
return Ternary.False;
|
||||
}
|
||||
|
||||
function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean): Ternary {
|
||||
function typeRelatedToDiscriminatedType(source: Type, target: UnionType) {
|
||||
// 1. Generate the combinations of discriminant properties & types 'source' can satisfy.
|
||||
// a. If the number of combinations is above a set limit, the comparison is too complex.
|
||||
// 2. Filter 'target' to the subset of types whose discriminants exist in the matrix.
|
||||
// a. If 'target' does not satisfy all discriminants in the matrix, 'source' is not related.
|
||||
// 3. For each type in the filtered 'target', determine if all non-discriminant properties of
|
||||
// 'target' are related to a property in 'source'.
|
||||
//
|
||||
// NOTE: See ~/tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts
|
||||
// for examples.
|
||||
|
||||
const sourceProperties = getPropertiesOfObjectType(source);
|
||||
const sourcePropertiesFiltered = findDiscriminantProperties(sourceProperties, target);
|
||||
if (!sourcePropertiesFiltered) return Ternary.False;
|
||||
|
||||
// Though we could compute the number of combinations as we generate
|
||||
// the matrix, this would incur additional memory overhead due to
|
||||
// array allocations. To reduce this overhead, we first compute
|
||||
// the number of combinations to ensure we will not surpass our
|
||||
// fixed limit before incurring the cost of any allocations:
|
||||
let numCombinations = 1;
|
||||
for (const sourceProperty of sourcePropertiesFiltered) {
|
||||
numCombinations *= countTypes(getTypeOfSymbol(sourceProperty));
|
||||
if (numCombinations > 25) {
|
||||
// We've reached the complexity limit.
|
||||
return Ternary.False;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the set of types for each discriminant property.
|
||||
const sourceDiscriminantTypes: Type[][] = new Array<Type[]>(sourcePropertiesFiltered.length);
|
||||
const excludedProperties = createUnderscoreEscapedMap<true>();
|
||||
for (let i = 0; i < sourcePropertiesFiltered.length; i++) {
|
||||
const sourceProperty = sourcePropertiesFiltered[i];
|
||||
const sourcePropertyType = getTypeOfSymbol(sourceProperty);
|
||||
sourceDiscriminantTypes[i] = sourcePropertyType.flags & TypeFlags.Union
|
||||
? (sourcePropertyType as UnionType).types
|
||||
: [sourcePropertyType];
|
||||
excludedProperties.set(sourceProperty.escapedName, true);
|
||||
}
|
||||
|
||||
// Match each combination of the cartesian product of discriminant properties to one or more
|
||||
// constituents of 'target'. If any combination does not have a match then 'source' is not relatable.
|
||||
const discriminantCombinations = cartesianProduct(sourceDiscriminantTypes);
|
||||
const matchingTypes: Type[] = [];
|
||||
for (const combination of discriminantCombinations) {
|
||||
let hasMatch = false;
|
||||
outer: for (const type of target.types) {
|
||||
for (let i = 0; i < sourcePropertiesFiltered.length; i++) {
|
||||
const sourceProperty = sourcePropertiesFiltered[i];
|
||||
const targetProperty = getPropertyOfObjectType(type, sourceProperty.escapedName);
|
||||
if (!targetProperty) continue outer;
|
||||
if (sourceProperty === targetProperty) continue;
|
||||
// We compare the source property to the target in the context of a single discriminant type.
|
||||
const related = propertyRelatedTo(source, target, sourceProperty, targetProperty, _ => combination[i], /*reportErrors*/ false);
|
||||
// If the target property could not be found, or if the properties were not related,
|
||||
// then this constituent is not a match.
|
||||
if (!related) {
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
pushIfUnique(matchingTypes, type, equateValues);
|
||||
hasMatch = true;
|
||||
}
|
||||
if (!hasMatch) {
|
||||
// We failed to match any type for this combination.
|
||||
return Ternary.False;
|
||||
}
|
||||
}
|
||||
|
||||
// Compare the remaining non-discriminant properties of each match.
|
||||
let result = Ternary.True;
|
||||
for (const type of matchingTypes) {
|
||||
result &= propertiesRelatedTo(source, type, /*reportErrors*/ false, excludedProperties);
|
||||
if (result) {
|
||||
result &= signaturesRelatedTo(source, type, SignatureKind.Call, /*reportStructuralErrors*/ false);
|
||||
if (result) {
|
||||
result &= signaturesRelatedTo(source, type, SignatureKind.Construct, /*reportStructuralErrors*/ false);
|
||||
if (result) {
|
||||
result &= indexTypesRelatedTo(source, type, IndexKind.String, /*sourceIsPrimitive*/ false, /*reportStructuralErrors*/ false);
|
||||
if (result) {
|
||||
result &= indexTypesRelatedTo(source, type, IndexKind.Number, /*sourceIsPrimitive*/ false, /*reportStructuralErrors*/ false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function excludeProperties(properties: Symbol[], excludedProperties: UnderscoreEscapedMap<true> | undefined) {
|
||||
if (!excludedProperties || properties.length === 0) return properties;
|
||||
let result: Symbol[] | undefined;
|
||||
for (let i = 0; i < properties.length; i++) {
|
||||
if (!excludedProperties.has(properties[i].escapedName)) {
|
||||
if (result) {
|
||||
result.push(properties[i]);
|
||||
}
|
||||
}
|
||||
else if (!result) {
|
||||
result = properties.slice(0, i);
|
||||
}
|
||||
}
|
||||
return result || properties;
|
||||
}
|
||||
|
||||
function propertyRelatedTo(source: Type, target: Type, sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean): Ternary {
|
||||
const sourcePropFlags = getDeclarationModifierFlagsFromSymbol(sourceProp);
|
||||
const targetPropFlags = getDeclarationModifierFlagsFromSymbol(targetProp);
|
||||
if (sourcePropFlags & ModifierFlags.Private || targetPropFlags & ModifierFlags.Private) {
|
||||
const hasDifferingDeclarations = sourceProp.valueDeclaration !== targetProp.valueDeclaration;
|
||||
if (getCheckFlags(sourceProp) & CheckFlags.ContainsPrivate && hasDifferingDeclarations) {
|
||||
if (reportErrors) {
|
||||
reportError(Diagnostics.Property_0_has_conflicting_declarations_and_is_inaccessible_in_type_1, symbolToString(sourceProp), typeToString(source));
|
||||
}
|
||||
return Ternary.False;
|
||||
}
|
||||
if (hasDifferingDeclarations) {
|
||||
if (reportErrors) {
|
||||
if (sourcePropFlags & ModifierFlags.Private && targetPropFlags & ModifierFlags.Private) {
|
||||
reportError(Diagnostics.Types_have_separate_declarations_of_a_private_property_0, symbolToString(targetProp));
|
||||
}
|
||||
else {
|
||||
reportError(Diagnostics.Property_0_is_private_in_type_1_but_not_in_type_2, symbolToString(targetProp),
|
||||
typeToString(sourcePropFlags & ModifierFlags.Private ? source : target),
|
||||
typeToString(sourcePropFlags & ModifierFlags.Private ? target : source));
|
||||
}
|
||||
}
|
||||
return Ternary.False;
|
||||
}
|
||||
}
|
||||
else if (targetPropFlags & ModifierFlags.Protected) {
|
||||
if (!isValidOverrideOf(sourceProp, targetProp)) {
|
||||
if (reportErrors) {
|
||||
reportError(Diagnostics.Property_0_is_protected_but_type_1_is_not_a_class_derived_from_2, symbolToString(targetProp),
|
||||
typeToString(getDeclaringClass(sourceProp) || source), typeToString(getDeclaringClass(targetProp) || target));
|
||||
}
|
||||
return Ternary.False;
|
||||
}
|
||||
}
|
||||
else if (sourcePropFlags & ModifierFlags.Protected) {
|
||||
if (reportErrors) {
|
||||
reportError(Diagnostics.Property_0_is_protected_in_type_1_but_public_in_type_2,
|
||||
symbolToString(targetProp), typeToString(source), typeToString(target));
|
||||
}
|
||||
return Ternary.False;
|
||||
}
|
||||
// If the target comes from a partial union prop, allow `undefined` in the target type
|
||||
const related = isRelatedTo(getTypeOfSourceProperty(sourceProp), addOptionality(getTypeOfSymbol(targetProp), !!(getCheckFlags(targetProp) & CheckFlags.Partial)), reportErrors);
|
||||
if (!related) {
|
||||
if (reportErrors) {
|
||||
reportError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(targetProp));
|
||||
}
|
||||
return Ternary.False;
|
||||
}
|
||||
// When checking for comparability, be more lenient with optional properties.
|
||||
if (relation !== comparableRelation && sourceProp.flags & SymbolFlags.Optional && !(targetProp.flags & SymbolFlags.Optional)) {
|
||||
// TypeScript 1.0 spec (April 2014): 3.8.3
|
||||
// S is a subtype of a type T, and T is a supertype of S if ...
|
||||
// S' and T are object types and, for each member M in T..
|
||||
// M is a property and S' contains a property N where
|
||||
// if M is a required property, N is also a required property
|
||||
// (M - property in T)
|
||||
// (N - property in S)
|
||||
if (reportErrors) {
|
||||
reportError(Diagnostics.Property_0_is_optional_in_type_1_but_required_in_type_2,
|
||||
symbolToString(targetProp), typeToString(source), typeToString(target));
|
||||
}
|
||||
return Ternary.False;
|
||||
}
|
||||
return related;
|
||||
}
|
||||
|
||||
function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean, excludedProperties: UnderscoreEscapedMap<true> | undefined): Ternary {
|
||||
if (relation === identityRelation) {
|
||||
return propertiesIdenticalTo(source, target);
|
||||
return propertiesIdenticalTo(source, target, excludedProperties);
|
||||
}
|
||||
const requireOptionalProperties = relation === subtypeRelation && !isObjectLiteralType(source) && !isEmptyArrayLiteralType(source) && !isTupleType(source);
|
||||
const unmatchedProperty = getUnmatchedProperty(source, target, requireOptionalProperties, /*matchDiscriminantProperties*/ false);
|
||||
@@ -13212,7 +13401,7 @@ namespace ts {
|
||||
return Ternary.False;
|
||||
}
|
||||
if (isObjectLiteralType(target)) {
|
||||
for (const sourceProp of getPropertiesOfType(source)) {
|
||||
for (const sourceProp of excludeProperties(getPropertiesOfType(source), excludedProperties)) {
|
||||
if (!getPropertyOfObjectType(target, sourceProp.escapedName)) {
|
||||
const sourceType = getTypeOfSymbol(sourceProp);
|
||||
if (!(sourceType === undefinedType || sourceType === undefinedWideningType)) {
|
||||
@@ -13255,89 +13444,30 @@ namespace ts {
|
||||
// We only call this for union target types when we're attempting to do excess property checking - in those cases, we want to get _all possible props_
|
||||
// from the target union, across all members
|
||||
const properties = target.flags & TypeFlags.Union ? getPossiblePropertiesOfUnionType(target as UnionType) : getPropertiesOfType(target);
|
||||
for (const targetProp of properties) {
|
||||
for (const targetProp of excludeProperties(properties, excludedProperties)) {
|
||||
if (!(targetProp.flags & SymbolFlags.Prototype)) {
|
||||
const sourceProp = getPropertyOfType(source, targetProp.escapedName);
|
||||
if (sourceProp && sourceProp !== targetProp) {
|
||||
if (isIgnoredJsxProperty(source, sourceProp, getTypeOfSymbol(targetProp))) {
|
||||
continue;
|
||||
}
|
||||
const sourcePropFlags = getDeclarationModifierFlagsFromSymbol(sourceProp);
|
||||
const targetPropFlags = getDeclarationModifierFlagsFromSymbol(targetProp);
|
||||
if (sourcePropFlags & ModifierFlags.Private || targetPropFlags & ModifierFlags.Private) {
|
||||
const hasDifferingDeclarations = sourceProp.valueDeclaration !== targetProp.valueDeclaration;
|
||||
if (getCheckFlags(sourceProp) & CheckFlags.ContainsPrivate && hasDifferingDeclarations) {
|
||||
if (reportErrors) {
|
||||
reportError(Diagnostics.Property_0_has_conflicting_declarations_and_is_inaccessible_in_type_1, symbolToString(sourceProp), typeToString(source));
|
||||
}
|
||||
return Ternary.False;
|
||||
}
|
||||
if (hasDifferingDeclarations) {
|
||||
if (reportErrors) {
|
||||
if (sourcePropFlags & ModifierFlags.Private && targetPropFlags & ModifierFlags.Private) {
|
||||
reportError(Diagnostics.Types_have_separate_declarations_of_a_private_property_0, symbolToString(targetProp));
|
||||
}
|
||||
else {
|
||||
reportError(Diagnostics.Property_0_is_private_in_type_1_but_not_in_type_2, symbolToString(targetProp),
|
||||
typeToString(sourcePropFlags & ModifierFlags.Private ? source : target),
|
||||
typeToString(sourcePropFlags & ModifierFlags.Private ? target : source));
|
||||
}
|
||||
}
|
||||
return Ternary.False;
|
||||
}
|
||||
}
|
||||
else if (targetPropFlags & ModifierFlags.Protected) {
|
||||
if (!isValidOverrideOf(sourceProp, targetProp)) {
|
||||
if (reportErrors) {
|
||||
reportError(Diagnostics.Property_0_is_protected_but_type_1_is_not_a_class_derived_from_2, symbolToString(targetProp),
|
||||
typeToString(getDeclaringClass(sourceProp) || source), typeToString(getDeclaringClass(targetProp) || target));
|
||||
}
|
||||
return Ternary.False;
|
||||
}
|
||||
}
|
||||
else if (sourcePropFlags & ModifierFlags.Protected) {
|
||||
if (reportErrors) {
|
||||
reportError(Diagnostics.Property_0_is_protected_in_type_1_but_public_in_type_2,
|
||||
symbolToString(targetProp), typeToString(source), typeToString(target));
|
||||
}
|
||||
return Ternary.False;
|
||||
}
|
||||
// If the target comes from a partial union prop, allow `undefined` in the target type
|
||||
const related = isRelatedTo(getTypeOfSymbol(sourceProp), addOptionality(getTypeOfSymbol(targetProp), !!(getCheckFlags(targetProp) & CheckFlags.Partial)), reportErrors);
|
||||
const related = propertyRelatedTo(source, target, sourceProp, targetProp, getTypeOfSymbol, reportErrors);
|
||||
if (!related) {
|
||||
if (reportErrors) {
|
||||
reportError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(targetProp));
|
||||
}
|
||||
return Ternary.False;
|
||||
}
|
||||
result &= related;
|
||||
// When checking for comparability, be more lenient with optional properties.
|
||||
if (relation !== comparableRelation && sourceProp.flags & SymbolFlags.Optional && !(targetProp.flags & SymbolFlags.Optional)) {
|
||||
// TypeScript 1.0 spec (April 2014): 3.8.3
|
||||
// S is a subtype of a type T, and T is a supertype of S if ...
|
||||
// S' and T are object types and, for each member M in T..
|
||||
// M is a property and S' contains a property N where
|
||||
// if M is a required property, N is also a required property
|
||||
// (M - property in T)
|
||||
// (N - property in S)
|
||||
if (reportErrors) {
|
||||
reportError(Diagnostics.Property_0_is_optional_in_type_1_but_required_in_type_2,
|
||||
symbolToString(targetProp), typeToString(source), typeToString(target));
|
||||
}
|
||||
return Ternary.False;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function propertiesIdenticalTo(source: Type, target: Type): Ternary {
|
||||
function propertiesIdenticalTo(source: Type, target: Type, excludedProperties: UnderscoreEscapedMap<true> | undefined): Ternary {
|
||||
if (!(source.flags & TypeFlags.Object && target.flags & TypeFlags.Object)) {
|
||||
return Ternary.False;
|
||||
}
|
||||
const sourceProperties = getPropertiesOfObjectType(source);
|
||||
const targetProperties = getPropertiesOfObjectType(target);
|
||||
const sourceProperties = excludeProperties(getPropertiesOfObjectType(source), excludedProperties);
|
||||
const targetProperties = excludeProperties(getPropertiesOfObjectType(target), excludedProperties);
|
||||
if (sourceProperties.length !== targetProperties.length) {
|
||||
return Ternary.False;
|
||||
}
|
||||
@@ -15844,6 +15974,10 @@ namespace ts {
|
||||
return f(type) ? type : neverType;
|
||||
}
|
||||
|
||||
function countTypes(type: Type) {
|
||||
return type.flags & TypeFlags.Union ? (type as UnionType).types.length : 1;
|
||||
}
|
||||
|
||||
// Apply a mapping function to a type and return the resulting type. If the source type
|
||||
// is a union type, the mapping function is applied to each constituent type and a union
|
||||
// of the resulting types is returned.
|
||||
|
||||
@@ -2317,4 +2317,29 @@ namespace ts {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function cartesianProduct<T>(arrays: readonly T[][]) {
|
||||
const result: T[][] = [];
|
||||
cartesianProductWorker(arrays, result, /*outer*/ undefined, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
function cartesianProductWorker<T>(arrays: readonly (readonly T[])[], result: (readonly T[])[], outer: readonly T[] | undefined, index: number) {
|
||||
for (const element of arrays[index]) {
|
||||
let inner: T[];
|
||||
if (outer) {
|
||||
inner = outer.slice();
|
||||
inner.push(element);
|
||||
}
|
||||
else {
|
||||
inner = [element];
|
||||
}
|
||||
if (index === arrays.length - 1) {
|
||||
result.push(inner);
|
||||
}
|
||||
else {
|
||||
cartesianProductWorker(arrays, result, inner, index + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user