Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Infer contextual types from return types through generic functions
  • Loading branch information
ahejlsberg committed Jan 17, 2019
commit 1b4bce236eeb2dc6ed0f3a5467b15f5bedeb2dfa
46 changes: 30 additions & 16 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14591,18 +14591,18 @@ namespace ts {
}

function inferFromProperties(source: Type, target: Type) {
if (isTupleType(source)) {
if (isArrayType(source) || isTupleType(source)) {
if (isTupleType(target)) {
const sourceLength = getLengthOfTupleType(source);
const sourceLength = isTupleType(source) ? getLengthOfTupleType(source) : 0;
const targetLength = getLengthOfTupleType(target);
const sourceRestType = getRestTypeOfTupleType(source);
const sourceRestType = isTupleType(source) ? getRestTypeOfTupleType(source) : getElementTypeOfArrayType(source);
const targetRestType = getRestTypeOfTupleType(target);
const fixedLength = targetLength < sourceLength || sourceRestType ? targetLength : sourceLength;
for (let i = 0; i < fixedLength; i++) {
inferFromTypes(i < sourceLength ? source.typeArguments![i] : sourceRestType!, target.typeArguments![i]);
inferFromTypes(i < sourceLength ? (<TypeReference>source).typeArguments![i] : sourceRestType!, target.typeArguments![i]);
}
if (targetRestType) {
const types = fixedLength < sourceLength ? source.typeArguments!.slice(fixedLength, sourceLength) : [];
const types = fixedLength < sourceLength ? (<TypeReference>source).typeArguments!.slice(fixedLength, sourceLength) : [];
if (sourceRestType) {
types.push(sourceRestType);
}
Expand Down Expand Up @@ -17694,14 +17694,28 @@ namespace ts {
// Return the contextual type for a given expression node. During overload resolution, a contextual type may temporarily
// be "pushed" onto a node using the contextualType property.
function getApparentTypeOfContextualType(node: Expression): Type | undefined {
let contextualType = getContextualType(node);
contextualType = contextualType && mapType(contextualType, getApparentType);
if (contextualType && contextualType.flags & TypeFlags.Union) {
if (isObjectLiteralExpression(node)) {
return discriminateContextualTypeByObjectMembers(node, contextualType as UnionType);
const contextualType = instantiateContextualType(getContextualType(node), node);
if (contextualType) {
const apparentType = mapType(contextualType, getApparentType);
if (apparentType.flags & TypeFlags.Union) {
if (isObjectLiteralExpression(node)) {
return discriminateContextualTypeByObjectMembers(node, apparentType as UnionType);
}
else if (isJsxAttributes(node)) {
return discriminateContextualTypeByJSXAttributes(node, apparentType as UnionType);
}
}
else if (isJsxAttributes(node)) {
return discriminateContextualTypeByJSXAttributes(node, contextualType as UnionType);
return apparentType;
}
}

// If the given contextual type constains instantiable types and if a mapper representing

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

constains -> contains

// return type inferences is available, instantiate those types using that mapper.
function instantiateContextualType(contextualType: Type | undefined, node: Expression): Type | undefined {
if (contextualType && maybeTypeOfKind(contextualType, TypeFlags.Instantiable)) {
const returnMapper = (<InferenceContext>getContextualMapper(node)).returnMapper;
if (returnMapper) {
return mapType(contextualType, t => t.flags & TypeFlags.Instantiable ? instantiateType(t, returnMapper) : t);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This mapType call should probably have it's third argument set since we're in contextual typing and preserving string | "x"'s literaliness is desirable.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Also: I'm wondering why bother with the map type/instantiate flag check at all - instantiateType is only going to instantiate instantiable types anyway.

@jack-williams jack-williams Jan 18, 2019

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If you change the mapType I think you can go ahead and close #29174.

(or ping me and I’ll close it)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@weswigham Agreed, we should make sure we use UnionReduction.None. The reason we don't just call instantiateType is that getContextualMapper isn't a super cheap operation and also that we don't want to instantiate contained object types as it is unnecessary and can be expensive.

@jack-williams I'll fix it so we don't need #29174.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@ahejlsberg Grand, closing the PR. Might be worth adding the example from #29168 to this PR, just to make sure it’s fixed.

}
}
return contextualType;
Expand Down Expand Up @@ -19847,6 +19861,9 @@ namespace ts {
const inferenceTargetType = getReturnTypeOfSignature(signature);
// Inferences made from return types have lower priority than all other inferences.
inferTypes(context.inferences, inferenceSourceType, inferenceTargetType, InferencePriority.ReturnType);
// Create a type mapper for instantiating generic contextual types using the inferences made
// from the return type.
context.returnMapper = cloneTypeMapper(context);
}
}

Expand Down Expand Up @@ -23022,12 +23039,9 @@ namespace ts {
}

function checkExpressionForMutableLocation(node: Expression, checkMode: CheckMode | undefined, contextualType?: Type, forceTuple?: boolean): Type {
if (arguments.length === 2) {
contextualType = getContextualType(node);
}
const type = checkExpression(node, checkMode, forceTuple);
return isTypeAssertion(node) ? type :
getWidenedLiteralLikeTypeForContextualType(type, contextualType);
getWidenedLiteralLikeTypeForContextualType(type, instantiateContextualType(arguments.length === 2 ? getContextualType(node) : contextualType, node));
}

function checkPropertyAssignment(node: PropertyAssignment, checkMode?: CheckMode): Type {
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4364,6 +4364,7 @@ namespace ts {
inferences: InferenceInfo[]; // Inferences made for each type parameter
flags: InferenceFlags; // Inference flags
compareTypes: TypeComparer; // Type comparer function
returnMapper?: TypeMapper; // Type mapper for inferences from return types (if any)
}

/* @internal */
Expand Down