Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Contextually type implemented properties #6118

Closed
wants to merge 19 commits into from
Closed
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 3 additions & 1 deletion src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,9 @@ namespace ts {

currentReachabilityState = Reachability.Reachable;
hasExplicitReturn = false;
labelStack = labelIndexMap = implicitLabels = undefined;
labelStack = undefined;
labelIndexMap = undefined;
implicitLabels = undefined;
}

if (isInJavaScriptFile(node) && node.jsDocComment) {
Expand Down
135 changes: 115 additions & 20 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3184,6 +3184,44 @@ namespace ts {
return unknownType;
}

function getTypeOfBasePropertyDeclaration(declaration: PropertyDeclaration) {
if (declaration.parent.kind === SyntaxKind.ClassDeclaration) {
const parent = <ClassLikeDeclaration>declaration.parent;
const propertyName = declaration.symbol.name;
const extendedPropertyType = getSinglePropertyTypeOfTypes(getBaseTypes(<InterfaceType>getTypeOfSymbol(getSymbolOfNode(parent))), propertyName);
if (extendedPropertyType) {
return extendedPropertyType;
}
const implementedTypeNodes = getClassImplementsHeritageClauseElements(parent);
if (implementedTypeNodes) {
return getSinglePropertyTypeOfTypes(map(implementedTypeNodes, getTypeFromTypeReference), propertyName);
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Can you use an explicit return?

Copy link
Member Author

Choose a reason for hiding this comment

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

done


return undefined;
}

function getSinglePropertyTypeOfTypes(types: Type[], propertyName: string) {
let result: Type;
for (const t of types) {
if (t !== unknownType) {
const property = getPropertyOfType(t, propertyName);
if (!property || property.valueDeclaration.flags & NodeFlags.Private) {
continue;
}
if (property.name === propertyName) {
const propertyType = getTypeOfSymbol(property);
if (result && result !== propertyType) {
// if there's more than one matching property, return undefined
return undefined;
}
result = propertyType;
}
}
}
return result;
}

function getTargetType(type: ObjectType): Type {
return type.flags & TypeFlags.Reference ? (<TypeReference>type).target : type;
}
Expand Down Expand Up @@ -5410,7 +5448,7 @@ namespace ts {
// Returns true if the given expression contains (at any level of nesting) a function or arrow expression
// that is subject to contextual typing.
function isContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElement): boolean {
Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node));
Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isMethod(node));
switch (node.kind) {
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
Expand Down Expand Up @@ -7868,7 +7906,7 @@ namespace ts {
error(reference, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol));
}

function checkIdentifier(node: Identifier): Type {
function checkIdentifier(node: Identifier, contextualMapper?: TypeMapper): Type {
const symbol = getResolvedSymbol(node);

// As noted in ECMAScript 6 language spec, arrow functions never have an arguments objects.
Expand Down Expand Up @@ -7919,11 +7957,18 @@ namespace ts {
checkCollisionWithCapturedThisVariable(node, node);
checkNestedBlockScopedBinding(node, symbol);

const type = getTypeOfSymbol(localOrExportSymbol);
let type = getTypeOfSymbol(localOrExportSymbol);
if (strictNullChecks && !isAssignmentTarget(node) && !(type.flags & TypeFlags.Any) && !(getNullableKind(type) & TypeFlags.Undefined)) {
checkVariableAssignedBefore(symbol, node);
}
return getNarrowedTypeOfReference(type, node);
type = getNarrowedTypeOfReference(type, node);
if (symbol.name === "undefined" && !isInferentialContext(contextualMapper) && !isBindingPatternDeclaration(node.parent)) {
const contextualType = getContextualType(node);
if (contextualType && (!strictNullChecks || contextualType.flags & TypeFlags.Undefined)) {
return contextualType;
}
}
return type;
}

function isInsideFunction(node: Node, threshold: Node): boolean {
Expand Down Expand Up @@ -8406,6 +8451,24 @@ namespace ts {
}
}

function isBindingPatternDeclaration(node: Node) {
if (node.kind === SyntaxKind.Parameter || node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement) {
const declaration = <ParameterDeclaration | VariableDeclaration | BindingElement>node;
return declaration.name.kind === SyntaxKind.ArrayBindingPattern || declaration.name.kind === SyntaxKind.ObjectBindingPattern;
}
}

function checkNullKeyword(nullNode: Expression, contextualMapper?: TypeMapper) {
const contextualType = getContextualType(nullNode);
if (contextualType &&
!isInferentialContext(contextualMapper) &&
!isBindingPatternDeclaration(nullNode.parent) &&
(!strictNullChecks || contextualType.flags & TypeFlags.Null)) {
return contextualType;
}
return nullType;
}

function getContextuallyTypedThisType(func: FunctionLikeDeclaration): Type {
if ((isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) &&
isContextSensitive(func) &&
Expand All @@ -8422,7 +8485,7 @@ namespace ts {
// Return contextual type of parameter or undefined if no contextual type is available
function getContextuallyTypedParameterType(parameter: ParameterDeclaration): Type {
const func = parameter.parent;
if (isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) {
if (isFunctionExpressionOrArrowFunction(func) || isMethod(func)) {
if (isContextSensitive(func)) {
const contextualSignature = getContextualSignature(func);
if (contextualSignature) {
Expand All @@ -8447,9 +8510,9 @@ namespace ts {
}

// In a variable, parameter or property declaration with a type annotation,
// the contextual type of an initializer expression is the type of the variable, parameter or property.
// Otherwise, in a parameter declaration of a contextually typed function expression,
// the contextual type of an initializer expression is the contextual type of the parameter.
// the contextual type of an initializer expression is the type of the variable, parameter or property.
// Otherwise, in a parameter declaration of a contextually typed function expression,
// the contextual type of an initializer expression is the contextual type of the parameter.
// Otherwise, in a variable or parameter declaration with a binding pattern name,
// the contextual type of an initializer expression is the type implied by the binding pattern.
// Otherwise, in a binding pattern inside a variable or parameter declaration,
Expand All @@ -8466,6 +8529,12 @@ namespace ts {
return type;
}
}
if (declaration.kind === SyntaxKind.PropertyDeclaration) {
const type = getTypeOfBasePropertyDeclaration(<PropertyDeclaration>declaration);
if (type) {
return type;
}
}
if (isBindingPattern(declaration.name)) {
return getTypeFromBindingPattern(<BindingPattern>declaration.name, /*includePatternInType*/ true);
}
Expand Down Expand Up @@ -8798,7 +8867,7 @@ namespace ts {

function getContextualSignatureForFunctionLikeDeclaration(node: FunctionLikeDeclaration): Signature {
// Only function expressions, arrow functions, and object literal methods are contextually typed.
return isFunctionExpressionOrArrowFunction(node) || isObjectLiteralMethod(node)
return isFunctionExpressionOrArrowFunction(node) || isMethod(node)
? getContextualSignature(<FunctionExpression>node)
: undefined;
}
Expand All @@ -8809,10 +8878,18 @@ namespace ts {
// all identical ignoring their return type, the result is same signature but with return type as
// union type of return types from these signatures
function getContextualSignature(node: FunctionExpression | MethodDeclaration): Signature {
Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node));
const type = isObjectLiteralMethod(node)
? getContextualTypeForObjectLiteralMethod(node)
: getApparentTypeOfContextualType(node);
Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isMethod(node));
let type: Type;
if (isFunctionExpressionOrArrowFunction(node)) {
type = getApparentTypeOfContextualType(node);
}
else if (isObjectLiteralMethod(node)) {
type = getContextualTypeForObjectLiteralMethod(node);
}
else if (isMethod(node)) {
type = getTypeOfBasePropertyDeclaration(node);
}

if (!type) {
return undefined;
}
Expand Down Expand Up @@ -8926,7 +9003,7 @@ namespace ts {
function checkArrayLiteral(node: ArrayLiteralExpression, contextualMapper?: TypeMapper): Type {
const elements = node.elements;
let hasSpreadElement = false;
const elementTypes: Type[] = [];
let elementTypes: Type[] = [];
const inDestructuringPattern = isAssignmentTarget(node);
for (const e of elements) {
if (inDestructuringPattern && e.kind === SyntaxKind.SpreadElementExpression) {
Expand Down Expand Up @@ -8988,7 +9065,14 @@ namespace ts {
}
}
}
return createArrayType(elementTypes.length ? getUnionType(elementTypes) : emptyArrayElementType);
if (!elementTypes.length) {
const contextualType = getContextualType(node);
if (isInferentialContext(contextualMapper) || !contextualType || !(<TypeReference>contextualType).typeArguments) {
return createArrayType(emptyArrayElementType);
}
elementTypes = (<TypeReference>contextualType).typeArguments;
}
return createArrayType(getUnionType(elementTypes));
}

function isNumericName(name: DeclarationName): boolean {
Expand Down Expand Up @@ -9056,10 +9140,10 @@ namespace ts {
}
}
const unionType = propTypes.length ? getUnionType(propTypes) : undefinedType;
return createIndexInfo(unionType, /*isReadonly*/ false);
return createIndexInfo(unionType, /*isReadonly*/false);
}

function checkObjectLiteral(node: ObjectLiteralExpression, contextualMapper?: TypeMapper): Type {
function checkObjectLiteral(node: ObjectLiteralExpression, contextualMapper: TypeMapper): Type {
const inDestructuringPattern = isAssignmentTarget(node);
// Grammar checking
checkGrammarObjectLiteralExpression(node, inDestructuringPattern);
Expand All @@ -9074,6 +9158,17 @@ namespace ts {
let hasComputedStringProperty = false;
let hasComputedNumberProperty = false;

// Use the contextual type only for empty object literals if the contextual type has only indexers and optional properties
if (!node.properties.length &&
contextualType &&
contextualType !== voidType &&
!isInferentialContext(contextualMapper) &&
!getSignaturesOfType(contextualType, SignatureKind.Call).length &&
!getSignaturesOfType(contextualType, SignatureKind.Construct).length &&
!forEach(getPropertiesOfType(contextualType), prop => !(prop.flags & SymbolFlags.Optional))) {
return contextualType;
}

for (const memberDecl of node.properties) {
let member = memberDecl.symbol;
if (memberDecl.kind === SyntaxKind.PropertyAssignment ||
Expand Down Expand Up @@ -12296,7 +12391,7 @@ namespace ts {
return checkExpression((<PropertyAssignment>node).initializer, contextualMapper);
}

function checkObjectLiteralMethod(node: MethodDeclaration, contextualMapper?: TypeMapper): Type {
function checkObjectLiteralMethod(node: MethodDeclaration, contextualMapper: TypeMapper): Type {
// Grammar checking
checkGrammarMethod(node);

Expand Down Expand Up @@ -12371,13 +12466,13 @@ namespace ts {
function checkExpressionWorker(node: Expression, contextualMapper: TypeMapper): Type {
switch (node.kind) {
case SyntaxKind.Identifier:
return checkIdentifier(<Identifier>node);
return checkIdentifier(<Identifier>node, contextualMapper);
case SyntaxKind.ThisKeyword:
return checkThisExpression(node);
case SyntaxKind.SuperKeyword:
return checkSuperExpression(node);
case SyntaxKind.NullKeyword:
return nullType;
return checkNullKeyword(node, contextualMapper);
case SyntaxKind.TrueKeyword:
case SyntaxKind.FalseKeyword:
return booleanType;
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,10 @@ namespace ts {
return predicate && predicate.kind === TypePredicateKind.Identifier;
}

export function isMethod(node: Node): node is MethodDeclaration {
return node && node.kind === SyntaxKind.MethodDeclaration;
}

export function isThisTypePredicate(predicate: TypePredicate): predicate is ThisTypePredicate {
return predicate && predicate.kind === TypePredicateKind.This;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ module A {
>Point : Point

return null;
>null : null
>null : Line<Point>
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@ module A {
>Point : Point

} = null;
>null : null
>null : { top: { left: Point; right: Point; }; bottom: { left: Point; right: Point; }; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ var r2 = foo({ a: <IHasVisualizationModel>null });
>a : IHasVisualizationModel
><IHasVisualizationModel>null : IHasVisualizationModel
>IHasVisualizationModel : IHasVisualizationModel
>null : null
>null : IHasVisualizationModel

=== tests/cases/compiler/aliasUsageInGenericFunction_backbone.ts ===
export class Model {
Expand Down
6 changes: 3 additions & 3 deletions tests/baselines/reference/aliasUsageInOrExpression.types
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ var e: { x: IHasVisualizationModel } = <{ x: IHasVisualizationModel }>null || {
><{ x: IHasVisualizationModel }>null : { x: IHasVisualizationModel; }
>x : IHasVisualizationModel
>IHasVisualizationModel : IHasVisualizationModel
>null : null
>null : { x: IHasVisualizationModel; }
>{ x: moduleA } : { x: typeof moduleA; }
>x : typeof moduleA
>moduleA : typeof moduleA
Expand All @@ -59,11 +59,11 @@ var f: { x: IHasVisualizationModel } = <{ x: IHasVisualizationModel }>null ? { x
><{ x: IHasVisualizationModel }>null : { x: IHasVisualizationModel; }
>x : IHasVisualizationModel
>IHasVisualizationModel : IHasVisualizationModel
>null : null
>null : { x: IHasVisualizationModel; }
>{ x: moduleA } : { x: typeof moduleA; }
>x : typeof moduleA
>moduleA : typeof moduleA
>null : null
>null : { x: IHasVisualizationModel; }

=== tests/cases/compiler/aliasUsageInOrExpression_backbone.ts ===
export class Model {
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/aliasUsedAsNameValue.types
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@ export var id: number;
export function b(a: any): any { return null; }
>b : (a: any) => any
>a : any
>null : null
>null : any

Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ paired.reduce(function (a1, a2) {
>a1.concat : any
>a1 : any
>concat : any
>{} : {}
>{} : any

} , []);
>[] : undefined[]
Expand All @@ -35,7 +35,7 @@ paired.reduce((b1, b2) => {
>b1.concat : any
>b1 : any
>concat : any
>{} : {}
>{} : any

} , []);
>[] : undefined[]
Expand All @@ -52,7 +52,7 @@ paired.reduce((b3, b4) => b3.concat({}), []);
>b3.concat : any
>b3 : any
>concat : any
>{} : {}
>{} : any
>[] : undefined[]

paired.map((c1) => c1.count);
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/arrayAssignmentTest6.types
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ module Test {
>ILineTokens : ILineTokens

return null;
>null : null
>null : ILineTokens
}
}
}
Expand Down
Loading