mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-17 00:55:32 -05:00
Merge pull request #19513 from Microsoft/strictObjectLiterals
Improved type inference for object literals
This commit is contained in:
@@ -260,6 +260,7 @@ namespace ts {
|
||||
const literalTypes = createMap<LiteralType>();
|
||||
const indexedAccessTypes = createMap<IndexedAccessType>();
|
||||
const evolvingArrayTypes: EvolvingArrayType[] = [];
|
||||
const undefinedProperties = createMap<Symbol>() as UnderscoreEscapedMap<Symbol>;
|
||||
|
||||
const unknownSymbol = createSymbol(SymbolFlags.Property, "unknown" as __String);
|
||||
const resolvingSymbol = createSymbol(0, InternalSymbolName.Resolving);
|
||||
@@ -7984,7 +7985,7 @@ namespace ts {
|
||||
const spread = createAnonymousType(undefined, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo);
|
||||
spread.flags |= propagatedFlags;
|
||||
spread.flags |= TypeFlags.FreshLiteral | TypeFlags.ContainsObjectLiteral;
|
||||
(spread as ObjectType).objectFlags |= ObjectFlags.ObjectLiteral;
|
||||
(spread as ObjectType).objectFlags |= (ObjectFlags.ObjectLiteral | ObjectFlags.ContainsSpread);
|
||||
spread.symbol = symbol;
|
||||
return spread;
|
||||
}
|
||||
@@ -9026,7 +9027,7 @@ namespace ts {
|
||||
|
||||
if (isSimpleTypeRelatedTo(source, target, relation, reportErrors ? reportError : undefined)) return Ternary.True;
|
||||
|
||||
if (getObjectFlags(source) & ObjectFlags.ObjectLiteral && source.flags & TypeFlags.FreshLiteral) {
|
||||
if (isObjectLiteralType(source) && source.flags & TypeFlags.FreshLiteral) {
|
||||
if (hasExcessProperties(<FreshObjectLiteralType>source, target, reportErrors)) {
|
||||
if (reportErrors) {
|
||||
reportRelationError(headMessage, source, target);
|
||||
@@ -9596,7 +9597,7 @@ namespace ts {
|
||||
if (relation === identityRelation) {
|
||||
return propertiesIdenticalTo(source, target);
|
||||
}
|
||||
const requireOptionalProperties = relation === subtypeRelation && !(getObjectFlags(source) & ObjectFlags.ObjectLiteral);
|
||||
const requireOptionalProperties = relation === subtypeRelation && !isObjectLiteralType(source);
|
||||
const unmatchedProperty = getUnmatchedProperty(source, target, requireOptionalProperties);
|
||||
if (unmatchedProperty) {
|
||||
if (reportErrors) {
|
||||
@@ -9604,6 +9605,19 @@ namespace ts {
|
||||
}
|
||||
return Ternary.False;
|
||||
}
|
||||
if (isObjectLiteralType(target)) {
|
||||
for (const sourceProp of getPropertiesOfType(source)) {
|
||||
if (!getPropertyOfObjectType(target, sourceProp.escapedName)) {
|
||||
const sourceType = getTypeOfSymbol(sourceProp);
|
||||
if (!(sourceType === undefinedType || sourceType === undefinedWideningType)) {
|
||||
if (reportErrors) {
|
||||
reportError(Diagnostics.Property_0_does_not_exist_on_type_1, symbolToString(sourceProp), typeToString(target));
|
||||
}
|
||||
return Ternary.False;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let result = Ternary.True;
|
||||
const properties = getPropertiesOfObjectType(target);
|
||||
for (const targetProp of properties) {
|
||||
@@ -10425,7 +10439,7 @@ namespace ts {
|
||||
* Leave signatures alone since they are not subject to the check.
|
||||
*/
|
||||
function getRegularTypeOfObjectLiteral(type: Type): Type {
|
||||
if (!(getObjectFlags(type) & ObjectFlags.ObjectLiteral && type.flags & TypeFlags.FreshLiteral)) {
|
||||
if (!(isObjectLiteralType(type) && type.flags & TypeFlags.FreshLiteral)) {
|
||||
return type;
|
||||
}
|
||||
const regularType = (<FreshObjectLiteralType>type).regularType;
|
||||
@@ -10447,18 +10461,74 @@ namespace ts {
|
||||
return regularNew;
|
||||
}
|
||||
|
||||
function getWidenedProperty(prop: Symbol): Symbol {
|
||||
function createWideningContext(parent: WideningContext, propertyName: __String, siblings: Type[]): WideningContext {
|
||||
return { parent, propertyName, siblings, resolvedPropertyNames: undefined };
|
||||
}
|
||||
|
||||
function getSiblingsOfContext(context: WideningContext): Type[] {
|
||||
if (!context.siblings) {
|
||||
const siblings: Type[] = [];
|
||||
for (const type of getSiblingsOfContext(context.parent)) {
|
||||
if (isObjectLiteralType(type)) {
|
||||
const prop = getPropertyOfObjectType(type, context.propertyName);
|
||||
if (prop) {
|
||||
forEachType(getTypeOfSymbol(prop), t => {
|
||||
siblings.push(t);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
context.siblings = siblings;
|
||||
}
|
||||
return context.siblings;
|
||||
}
|
||||
|
||||
function getPropertyNamesOfContext(context: WideningContext): __String[] {
|
||||
if (!context.resolvedPropertyNames) {
|
||||
const names = createMap<boolean>() as UnderscoreEscapedMap<boolean>;
|
||||
for (const t of getSiblingsOfContext(context)) {
|
||||
if (isObjectLiteralType(t) && !(getObjectFlags(t) & ObjectFlags.ContainsSpread)) {
|
||||
for (const prop of getPropertiesOfType(t)) {
|
||||
names.set(prop.escapedName, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
context.resolvedPropertyNames = arrayFrom(names.keys());
|
||||
}
|
||||
return context.resolvedPropertyNames;
|
||||
}
|
||||
|
||||
function getWidenedProperty(prop: Symbol, context: WideningContext): Symbol {
|
||||
const original = getTypeOfSymbol(prop);
|
||||
const widened = getWidenedType(original);
|
||||
const propContext = context && createWideningContext(context, prop.escapedName, /*siblings*/ undefined);
|
||||
const widened = getWidenedTypeWithContext(original, propContext);
|
||||
return widened === original ? prop : createSymbolWithType(prop, widened);
|
||||
}
|
||||
|
||||
function getWidenedTypeOfObjectLiteral(type: Type): Type {
|
||||
function getUndefinedProperty(name: __String) {
|
||||
const cached = undefinedProperties.get(name);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
const result = createSymbol(SymbolFlags.Property | SymbolFlags.Optional, name);
|
||||
result.type = undefinedType;
|
||||
undefinedProperties.set(name, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
function getWidenedTypeOfObjectLiteral(type: Type, context: WideningContext): Type {
|
||||
const members = createSymbolTable();
|
||||
for (const prop of getPropertiesOfObjectType(type)) {
|
||||
// Since get accessors already widen their return value there is no need to
|
||||
// widen accessor based properties here.
|
||||
members.set(prop.escapedName, prop.flags & SymbolFlags.Property ? getWidenedProperty(prop) : prop);
|
||||
members.set(prop.escapedName, prop.flags & SymbolFlags.Property ? getWidenedProperty(prop, context) : prop);
|
||||
}
|
||||
if (context) {
|
||||
for (const name of getPropertyNamesOfContext(context)) {
|
||||
if (!members.has(name)) {
|
||||
members.set(name, getUndefinedProperty(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
const stringIndexInfo = getIndexInfoOfType(type, IndexKind.String);
|
||||
const numberIndexInfo = getIndexInfoOfType(type, IndexKind.Number);
|
||||
@@ -10467,20 +10537,25 @@ namespace ts {
|
||||
numberIndexInfo && createIndexInfo(getWidenedType(numberIndexInfo.type), numberIndexInfo.isReadonly));
|
||||
}
|
||||
|
||||
function getWidenedConstituentType(type: Type): Type {
|
||||
return type.flags & TypeFlags.Nullable ? type : getWidenedType(type);
|
||||
function getWidenedType(type: Type) {
|
||||
return getWidenedTypeWithContext(type, /*context*/ undefined);
|
||||
}
|
||||
|
||||
function getWidenedType(type: Type): Type {
|
||||
function getWidenedTypeWithContext(type: Type, context: WideningContext): Type {
|
||||
if (type.flags & TypeFlags.RequiresWidening) {
|
||||
if (type.flags & TypeFlags.Nullable) {
|
||||
return anyType;
|
||||
}
|
||||
if (getObjectFlags(type) & ObjectFlags.ObjectLiteral) {
|
||||
return getWidenedTypeOfObjectLiteral(type);
|
||||
if (isObjectLiteralType(type)) {
|
||||
return getWidenedTypeOfObjectLiteral(type, context);
|
||||
}
|
||||
if (type.flags & TypeFlags.Union) {
|
||||
return getUnionType(sameMap((<UnionType>type).types, getWidenedConstituentType));
|
||||
const unionContext = context || createWideningContext(/*parent*/ undefined, /*propertyName*/ undefined, (<UnionType>type).types);
|
||||
const widenedTypes = sameMap((<UnionType>type).types, t => t.flags & TypeFlags.Nullable ? t : getWidenedTypeWithContext(t, unionContext));
|
||||
// Widening an empty object literal transitions from a highly restrictive type to
|
||||
// a highly inclusive one. For that reason we perform subtype reduction here if the
|
||||
// union includes empty object types (e.g. reducing {} | string to just {}).
|
||||
return getUnionType(widenedTypes, some(widenedTypes, isEmptyObjectType));
|
||||
}
|
||||
if (isArrayType(type) || isTupleType(type)) {
|
||||
return createTypeReference((<TypeReference>type).target, sameMap((<TypeReference>type).typeArguments, getWidenedType));
|
||||
@@ -10502,28 +10577,35 @@ namespace ts {
|
||||
*/
|
||||
function reportWideningErrorsInType(type: Type): boolean {
|
||||
let errorReported = false;
|
||||
if (type.flags & TypeFlags.Union) {
|
||||
for (const t of (<UnionType>type).types) {
|
||||
if (reportWideningErrorsInType(t)) {
|
||||
if (type.flags & TypeFlags.ContainsWideningType) {
|
||||
if (type.flags & TypeFlags.Union) {
|
||||
if (some((<UnionType>type).types, isEmptyObjectType)) {
|
||||
errorReported = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isArrayType(type) || isTupleType(type)) {
|
||||
for (const t of (<TypeReference>type).typeArguments) {
|
||||
if (reportWideningErrorsInType(t)) {
|
||||
errorReported = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (getObjectFlags(type) & ObjectFlags.ObjectLiteral) {
|
||||
for (const p of getPropertiesOfObjectType(type)) {
|
||||
const t = getTypeOfSymbol(p);
|
||||
if (t.flags & TypeFlags.ContainsWideningType) {
|
||||
if (!reportWideningErrorsInType(t)) {
|
||||
error(p.valueDeclaration, Diagnostics.Object_literal_s_property_0_implicitly_has_an_1_type, symbolName(p), typeToString(getWidenedType(t)));
|
||||
else {
|
||||
for (const t of (<UnionType>type).types) {
|
||||
if (reportWideningErrorsInType(t)) {
|
||||
errorReported = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isArrayType(type) || isTupleType(type)) {
|
||||
for (const t of (<TypeReference>type).typeArguments) {
|
||||
if (reportWideningErrorsInType(t)) {
|
||||
errorReported = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isObjectLiteralType(type)) {
|
||||
for (const p of getPropertiesOfObjectType(type)) {
|
||||
const t = getTypeOfSymbol(p);
|
||||
if (t.flags & TypeFlags.ContainsWideningType) {
|
||||
if (!reportWideningErrorsInType(t)) {
|
||||
error(p.valueDeclaration, Diagnostics.Object_literal_s_property_0_implicitly_has_an_1_type, symbolName(p), typeToString(getWidenedType(t)));
|
||||
}
|
||||
errorReported = true;
|
||||
}
|
||||
errorReported = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11029,11 +11111,28 @@ namespace ts {
|
||||
return constraint && maybeTypeOfKind(constraint, TypeFlags.Primitive | TypeFlags.Index);
|
||||
}
|
||||
|
||||
function isObjectLiteralType(type: Type) {
|
||||
return !!(getObjectFlags(type) & ObjectFlags.ObjectLiteral);
|
||||
}
|
||||
|
||||
function widenObjectLiteralCandidates(candidates: Type[]): Type[] {
|
||||
if (candidates.length > 1) {
|
||||
const objectLiterals = filter(candidates, isObjectLiteralType);
|
||||
if (objectLiterals.length) {
|
||||
const objectLiteralsType = getWidenedType(getUnionType(objectLiterals, /*subtypeReduction*/ true));
|
||||
return concatenate(filter(candidates, t => !isObjectLiteralType(t)), [objectLiteralsType]);
|
||||
}
|
||||
}
|
||||
return candidates;
|
||||
}
|
||||
|
||||
function getInferredType(context: InferenceContext, index: number): Type {
|
||||
const inference = context.inferences[index];
|
||||
let inferredType = inference.inferredType;
|
||||
if (!inferredType) {
|
||||
if (inference.candidates) {
|
||||
// Extract all object literal types and replace them with a single widened and normalized type.
|
||||
const candidates = widenObjectLiteralCandidates(inference.candidates);
|
||||
// We widen inferred literal types if
|
||||
// all inferences were made to top-level ocurrences of the type parameter, and
|
||||
// the type parameter has no constraint or its constraint includes no primitive or literal types, and
|
||||
@@ -11042,7 +11141,7 @@ namespace ts {
|
||||
const widenLiteralTypes = inference.topLevel &&
|
||||
!hasPrimitiveConstraint(inference.typeParameter) &&
|
||||
(inference.isFixed || !isTypeParameterAtTopLevel(getReturnTypeOfSignature(signature), inference.typeParameter));
|
||||
const baseCandidates = widenLiteralTypes ? sameMap(inference.candidates, getWidenedLiteralType) : inference.candidates;
|
||||
const baseCandidates = widenLiteralTypes ? sameMap(candidates, getWidenedLiteralType) : candidates;
|
||||
// If all inferences were made from contravariant positions, infer a common subtype. Otherwise, if
|
||||
// union types were requested or if all inferences were made from the return type position, infer a
|
||||
// union type. Otherwise, infer a common supertype.
|
||||
|
||||
@@ -3327,6 +3327,7 @@ namespace ts {
|
||||
ObjectLiteral = 1 << 7, // Originates in an object literal
|
||||
EvolvingArray = 1 << 8, // Evolving array type
|
||||
ObjectLiteralPatternWithComputedProperties = 1 << 9, // Object literal pattern with computed properties
|
||||
ContainsSpread = 1 << 10, // Object literal contains spread operation
|
||||
ClassOrInterface = Class | Interface
|
||||
}
|
||||
|
||||
@@ -3609,6 +3610,14 @@ namespace ts {
|
||||
compareTypes: TypeComparer; // Type comparer function
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export interface WideningContext {
|
||||
parent?: WideningContext; // Parent context
|
||||
propertyName?: __String; // Name of property in parent
|
||||
siblings?: Type[]; // Types of siblings
|
||||
resolvedPropertyNames?: __String[]; // Property names occurring in sibling object literals
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export const enum SpecialPropertyAssignmentKind {
|
||||
None,
|
||||
|
||||
Reference in New Issue
Block a user