@@ -7473,6 +7473,25 @@ namespace ts {
7473
7473
return type.resolvedProperties;
7474
7474
}
7475
7475
7476
+ function getPossiblePropertiesOfUnionType(type: UnionType): Symbol[] {
7477
+ if (type.possiblePropertyCache) {
7478
+ return type.possiblePropertyCache.size ? arrayFrom(type.possiblePropertyCache.values()) : emptyArray;
7479
+ }
7480
+ type.possiblePropertyCache = createSymbolTable();
7481
+ for (const t of type.types) {
7482
+ for (const p of getPropertiesOfType(t)) {
7483
+ if (!type.possiblePropertyCache.has(p.escapedName)) {
7484
+ const prop = getUnionOrIntersectionProperty(type, p.escapedName);
7485
+ if (prop) {
7486
+ type.possiblePropertyCache.set(p.escapedName, prop);
7487
+ }
7488
+ }
7489
+ }
7490
+ }
7491
+ // We can't simply use the normal property cache here, since that will contain cached apparent type members :(
7492
+ return type.possiblePropertyCache.size ? arrayFrom(type.possiblePropertyCache.values()) : emptyArray;
7493
+ }
7494
+
7476
7495
function getPropertiesOfType(type: Type): Symbol[] {
7477
7496
type = getApparentType(type);
7478
7497
return type.flags & TypeFlags.UnionOrIntersection ?
@@ -7830,7 +7849,7 @@ namespace ts {
7830
7849
const isUnion = containingType.flags & TypeFlags.Union;
7831
7850
const excludeModifiers = isUnion ? ModifierFlags.NonPublicAccessibilityModifier : 0;
7832
7851
// Flags we want to propagate to the result if they exist in all source symbols
7833
- let commonFlags = isUnion ? SymbolFlags.None : SymbolFlags.Optional;
7852
+ let optionalFlag = isUnion ? SymbolFlags.None : SymbolFlags.Optional;
7834
7853
let syntheticFlag = CheckFlags.SyntheticMethod;
7835
7854
let checkFlags = 0;
7836
7855
for (const current of containingType.types) {
@@ -7839,7 +7858,12 @@ namespace ts {
7839
7858
const prop = getPropertyOfType(type, name);
7840
7859
const modifiers = prop ? getDeclarationModifierFlagsFromSymbol(prop) : 0;
7841
7860
if (prop && !(modifiers & excludeModifiers)) {
7842
- commonFlags &= prop.flags;
7861
+ if (isUnion) {
7862
+ optionalFlag |= (prop.flags & SymbolFlags.Optional);
7863
+ }
7864
+ else {
7865
+ optionalFlag &= prop.flags;
7866
+ }
7843
7867
const id = "" + getSymbolId(prop);
7844
7868
if (!propSet.has(id)) {
7845
7869
propSet.set(id, prop);
@@ -7857,10 +7881,11 @@ namespace ts {
7857
7881
const indexInfo = !isLateBoundName(name) && (isNumericLiteralName(name) && getIndexInfoOfType(type, IndexKind.Number) || getIndexInfoOfType(type, IndexKind.String));
7858
7882
if (indexInfo) {
7859
7883
checkFlags |= indexInfo.isReadonly ? CheckFlags.Readonly : 0;
7884
+ checkFlags |= CheckFlags.WritePartial;
7860
7885
indexTypes = append(indexTypes, isTupleType(type) ? getRestTypeOfTupleType(type) || undefinedType : indexInfo.type);
7861
7886
}
7862
7887
else {
7863
- checkFlags |= CheckFlags.Partial ;
7888
+ checkFlags |= CheckFlags.ReadPartial ;
7864
7889
}
7865
7890
}
7866
7891
}
@@ -7869,7 +7894,7 @@ namespace ts {
7869
7894
return undefined;
7870
7895
}
7871
7896
const props = arrayFrom(propSet.values());
7872
- if (props.length === 1 && !(checkFlags & CheckFlags.Partial ) && !indexTypes) {
7897
+ if (props.length === 1 && !(checkFlags & CheckFlags.ReadPartial ) && !indexTypes) {
7873
7898
return props[0];
7874
7899
}
7875
7900
let declarations: Declaration[] | undefined;
@@ -7900,7 +7925,7 @@ namespace ts {
7900
7925
propTypes.push(type);
7901
7926
}
7902
7927
addRange(propTypes, indexTypes);
7903
- const result = createSymbol(SymbolFlags.Property | commonFlags , name, syntheticFlag | checkFlags);
7928
+ const result = createSymbol(SymbolFlags.Property | optionalFlag , name, syntheticFlag | checkFlags);
7904
7929
result.containingType = containingType;
7905
7930
if (!hasNonUniformValueDeclaration && firstValueDeclaration) {
7906
7931
result.valueDeclaration = firstValueDeclaration;
@@ -7937,7 +7962,7 @@ namespace ts {
7937
7962
function getPropertyOfUnionOrIntersectionType(type: UnionOrIntersectionType, name: __String): Symbol | undefined {
7938
7963
const property = getUnionOrIntersectionProperty(type, name);
7939
7964
// We need to filter out partial properties in union types
7940
- return property && !(getCheckFlags(property) & CheckFlags.Partial ) ? property : undefined;
7965
+ return property && !(getCheckFlags(property) & CheckFlags.ReadPartial ) ? property : undefined;
7941
7966
}
7942
7967
7943
7968
/**
@@ -12276,25 +12301,6 @@ namespace ts {
12276
12301
return true;
12277
12302
}
12278
12303
12279
- function isUnionOrIntersectionTypeWithoutNullableConstituents(type: Type): boolean {
12280
- if (!(type.flags & TypeFlags.UnionOrIntersection)) {
12281
- return false;
12282
- }
12283
- // at this point we know that this is union or intersection type possibly with nullable constituents.
12284
- // check if we still will have compound type if we ignore nullable components.
12285
- let seenNonNullable = false;
12286
- for (const t of (<UnionOrIntersectionType>type).types) {
12287
- if (t.flags & TypeFlags.Nullable) {
12288
- continue;
12289
- }
12290
- if (seenNonNullable) {
12291
- return true;
12292
- }
12293
- seenNonNullable = true;
12294
- }
12295
- return false;
12296
- }
12297
-
12298
12304
/**
12299
12305
* Compare two types and return
12300
12306
* * Ternary.True if they are related with no assumptions,
@@ -12349,21 +12355,15 @@ namespace ts {
12349
12355
isSimpleTypeRelatedTo(source, target, relation, reportErrors ? reportError : undefined)) return Ternary.True;
12350
12356
12351
12357
const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes);
12352
- if (isObjectLiteralType(source) && getObjectFlags(source) & ObjectFlags.FreshLiteral) {
12358
+ const isPerformingExcessPropertyChecks = (isObjectLiteralType(source) && getObjectFlags(source) & ObjectFlags.FreshLiteral);
12359
+ if (isPerformingExcessPropertyChecks) {
12353
12360
const discriminantType = target.flags & TypeFlags.Union ? findMatchingDiscriminantType(source, target as UnionType) : undefined;
12354
12361
if (hasExcessProperties(<FreshObjectLiteralType>source, target, discriminantType, reportErrors)) {
12355
12362
if (reportErrors) {
12356
12363
reportRelationError(headMessage, source, target);
12357
12364
}
12358
12365
return Ternary.False;
12359
12366
}
12360
- // Above we check for excess properties with respect to the entire target type. When union
12361
- // and intersection types are further deconstructed on the target side, we don't want to
12362
- // make the check again (as it might fail for a partial target type). Therefore we obtain
12363
- // the regular source type and proceed with that.
12364
- if (isUnionOrIntersectionTypeWithoutNullableConstituents(target) && !discriminantType) {
12365
- source = getRegularTypeOfObjectLiteral(source);
12366
- }
12367
12367
}
12368
12368
12369
12369
if (relation !== comparableRelation && !isApparentIntersectionConstituent &&
@@ -12399,11 +12399,24 @@ namespace ts {
12399
12399
}
12400
12400
else {
12401
12401
if (target.flags & TypeFlags.Union) {
12402
- result = typeRelatedToSomeType(source, <UnionType>target, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive));
12402
+ result = typeRelatedToSomeType(getRegularTypeOfObjectLiteral(source), <UnionType>target, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive));
12403
+ if (result && isPerformingExcessPropertyChecks) {
12404
+ // Validate against excess props using the original `source`
12405
+ const discriminantType = target.flags & TypeFlags.Union ? findMatchingDiscriminantType(source, target as UnionType) : undefined;
12406
+ if (!propertiesRelatedTo(source, discriminantType || target, reportErrors)) {
12407
+ return Ternary.False;
12408
+ }
12409
+ }
12403
12410
}
12404
12411
else if (target.flags & TypeFlags.Intersection) {
12405
12412
isIntersectionConstituent = true; // set here to affect the following trio of checks
12406
- result = typeRelatedToEachType(source, target as IntersectionType, reportErrors);
12413
+ result = typeRelatedToEachType(getRegularTypeOfObjectLiteral(source), target as IntersectionType, reportErrors);
12414
+ if (result && isPerformingExcessPropertyChecks) {
12415
+ // Validate against excess props using the original `source`
12416
+ if (!propertiesRelatedTo(source, target, reportErrors)) {
12417
+ return Ternary.False;
12418
+ }
12419
+ }
12407
12420
}
12408
12421
else if (source.flags & TypeFlags.Intersection) {
12409
12422
// Check to see if any constituents of the intersection are immediately related to the target.
@@ -12506,7 +12519,7 @@ namespace ts {
12506
12519
// check excess properties against discriminant type only, not the entire union
12507
12520
return hasExcessProperties(source, discriminant, /*discriminant*/ undefined, reportErrors);
12508
12521
}
12509
- for (const prop of getPropertiesOfObjectType (source)) {
12522
+ for (const prop of getPropertiesOfType (source)) {
12510
12523
if (shouldCheckAsExcessProperty(prop, source.symbol) && !isKnownProperty(target, prop.escapedName, isComparingJsxAttributes)) {
12511
12524
if (reportErrors) {
12512
12525
// Report error in terms of object types in the target as those are the only ones
@@ -13233,7 +13246,9 @@ namespace ts {
13233
13246
}
13234
13247
}
13235
13248
}
13236
- const properties = getPropertiesOfObjectType(target);
13249
+ // 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_
13250
+ // from the target union, across all members
13251
+ const properties = target.flags & TypeFlags.Union ? getPossiblePropertiesOfUnionType(target as UnionType) : getPropertiesOfType(target);
13237
13252
for (const targetProp of properties) {
13238
13253
if (!(targetProp.flags & SymbolFlags.Prototype)) {
13239
13254
const sourceProp = getPropertyOfType(source, targetProp.escapedName);
@@ -13281,7 +13296,8 @@ namespace ts {
13281
13296
}
13282
13297
return Ternary.False;
13283
13298
}
13284
- const related = isRelatedTo(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp), reportErrors);
13299
+ // If the target comes from a partial union prop, allow `undefined` in the target type
13300
+ const related = isRelatedTo(getTypeOfSymbol(sourceProp), addOptionality(getTypeOfSymbol(targetProp), !!(getCheckFlags(targetProp) & CheckFlags.Partial)), reportErrors);
13285
13301
if (!related) {
13286
13302
if (reportErrors) {
13287
13303
reportError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(targetProp));
@@ -14627,9 +14643,9 @@ namespace ts {
14627
14643
}
14628
14644
14629
14645
function* getUnmatchedProperties(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean) {
14630
- const properties = target.flags & TypeFlags.Intersection ? getPropertiesOfUnionOrIntersectionType(<IntersectionType> target) : getPropertiesOfObjectType (target);
14646
+ const properties = target.flags & TypeFlags.Union ? getPossiblePropertiesOfUnionType( target as UnionType ) : getPropertiesOfType (target);
14631
14647
for (const targetProp of properties) {
14632
- if (requireOptionalProperties || !(targetProp.flags & SymbolFlags.Optional)) {
14648
+ if (requireOptionalProperties || !(targetProp.flags & SymbolFlags.Optional || getCheckFlags(targetProp) & CheckFlags.Partial )) {
14633
14649
const sourceProp = getPropertyOfType(source, targetProp.escapedName);
14634
14650
if (!sourceProp) {
14635
14651
yield targetProp;
0 commit comments