From 8199b0b2363c15d22406040f20e22fe1c5040f12 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 29 Jul 2014 18:21:16 -0700 Subject: [PATCH 1/6] Pull model for contextual types. New baselines reflect a couple of unrelated bug fixes. --- src/compiler/checker.ts | 453 ++++++++++++------ src/compiler/types.ts | 19 +- .../functionImplementationErrors.errors.txt | 4 +- .../parserCastVersusArrowFunction1.errors.txt | 16 +- .../reference/parserRealSource8.errors.txt | 4 +- .../parserVariableDeclaration3.errors.txt | 4 +- 6 files changed, 342 insertions(+), 158 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5b4ada0ac86..fd89c889f9e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -41,6 +41,9 @@ module ts { var anyFunctionType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); var noConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); + var anySignature = createSignature(undefined, undefined, emptyArray, anyType, 0, false, false); + var unknownSignature = createSignature(undefined, undefined, emptyArray, unknownType, 0, false, false); + var globals: SymbolTable = {}; var globalObjectType: ObjectType; @@ -1179,23 +1182,28 @@ module ts { var links = getSymbolLinks(symbol); if (!links.type) { if (symbol.flags & SymbolFlags.Prototype) { - links.type = getTypeOfPrototypeProperty(symbol); + return links.type = getTypeOfPrototypeProperty(symbol); } - else { - links.type = resolvingType; - var type = getTypeOfVariableDeclaration(symbol.valueDeclaration); - if (links.type === resolvingType) { - links.type = type; + var declaration = symbol.valueDeclaration; + if (declaration.kind === SyntaxKind.Parameter && !declaration.type) { + var parent = declaration.parent; + if (parent.kind === SyntaxKind.FunctionExpression || parent.kind === SyntaxKind.ArrowFunction) { + assignContextualTypes(parent); + if (links.type) { + return links.type; + } } } + links.type = resolvingType; + var type = getTypeOfVariableDeclaration(symbol.valueDeclaration); + if (links.type === resolvingType) { + links.type = type; + } } - else if (links.type === resolvingType) { links.type = anyType; } - return links.type; - } function getSetAccessorTypeAnnotationNode(accessor: AccessorDeclaration): TypeNode { @@ -2269,15 +2277,17 @@ module ts { } function instantiateType(type: Type, mapper: TypeMapper): Type { - if (type.flags & TypeFlags.TypeParameter) { - return mapper(type); - } - if (type.flags & TypeFlags.Anonymous) { - return type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) ? - instantiateAnonymousType(type, mapper) : type; - } - if (type.flags & TypeFlags.Reference) { - return createTypeReference((type).target, instantiateList((type).typeArguments, mapper, instantiateType)); + if (mapper !== identityMapper) { + if (type.flags & TypeFlags.TypeParameter) { + return mapper(type); + } + if (type.flags & TypeFlags.Anonymous) { + return type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) ? + instantiateAnonymousType(type, mapper) : type; + } + if (type.flags & TypeFlags.Reference) { + return createTypeReference((type).target, instantiateList((type).typeArguments, mapper, instantiateType)); + } } return type; } @@ -3434,22 +3444,157 @@ module ts { return unknownType; } + function getTypeOfExpression(node: Expression): Type { + // TODO: Return type cached in NodeLinks + return checkExpression(node); + } + + function getContextualTypeForInitializerExpression(node: Expression): Type { + var declaration = node.parent; + if (node === declaration.initializer) { + if (declaration.type) { + return getTypeFromTypeNode(declaration.type); + } + if (declaration.kind === SyntaxKind.Parameter) { + var func = declaration.parent; + if (func.kind === SyntaxKind.FunctionExpression || func.kind === SyntaxKind.ArrowFunction) { + if (isContextSensitiveExpression(func)) { + var type = getContextualType(func); + if (type) { + var signature = getContextualSignature(type); + if (signature) { + return getTypeAtPosition(signature, indexOf(func.parameters, declaration)); + } + } + } + } + } + } + return undefined; + } + + function getContextualTypeForReturnExpression(node: Expression): Type { + var func = getContainingFunction(node); + if (func) { + // If the containing function has a return type annotation, is a constructor, or is a get accessor whose + // corresponding set accessor has a type annotation, return statements in the function are contextually typed + if (func.type || func.kind === SyntaxKind.Constructor || func.kind === SyntaxKind.GetAccessor && getSetAccessorTypeAnnotationNode(getDeclarationOfKind(func.symbol, SyntaxKind.SetAccessor))) { + return getReturnTypeOfSignature(getSignatureFromDeclaration(func)); + } + // Otherwise, if the containing function is contextually typed by a function type with exactly one call signature + // and that call signature is non-generic, return statements are contextually typed by the return type of the signature + var type = getContextualType(func); + if (type) { + var signature = getContextualSignature(type); + if (signature) { + return getReturnTypeOfSignature(signature); + } + } + } + return undefined; + } + + function getContextualTypeForArgument(node: Expression): Type { + var callExpression = node.parent; + var argIndex = indexOf(callExpression.arguments, node); + if (argIndex >= 0) { + var signature = getResolvedSignature(callExpression); + return getTypeAtPosition(signature, argIndex); + } + return undefined; + } + + function getContextualTypeForBinaryOperand(node: Expression): Type { + var binaryExpression = node.parent; + var operator = binaryExpression.operator; + if (operator >= SyntaxKind.FirstAssignment && operator <= SyntaxKind.LastAssignment) { + if (node === binaryExpression.right) { + return getTypeOfExpression(binaryExpression.left); + } + } + else if (operator === SyntaxKind.BarBarToken) { + var type = getContextualType(binaryExpression); + if (!type && node === binaryExpression.right) { + type = getTypeOfExpression(binaryExpression.left); + } + return type; + } + return undefined; + } + + function getContextualTypeForPropertyExpression(node: Expression): Type { + var declaration = node.parent; + var objectLiteral = declaration.parent; + var type = getContextualType(objectLiteral); + var name = declaration.name.text; + if (type && name) { + var prop = getPropertyOfType(type, name); + if (prop) { + return getTypeOfSymbol(prop); + } + return isNumericName(name) && getIndexTypeOfType(type, IndexKind.Number) || getIndexTypeOfType(type, IndexKind.String); + } + return undefined; + } + + function getContextualTypeForElementExpression(node: Expression): Type { + var arrayLiteral = node.parent; + var type = getContextualType(arrayLiteral); + return type ? getIndexTypeOfType(type, IndexKind.Number) : undefined; + } + + function getContextualTypeForConditionalOperand(node: Expression): Type { + var conditional = node.parent; + return node === conditional.whenTrue || node === conditional.whenFalse ? getContextualType(conditional) : undefined; + } + + function getContextualType(node: Expression): Type { + if (node.contextualType) { + return node.contextualType; + } + var parent = node.parent; + switch (parent.kind) { + case SyntaxKind.VariableDeclaration: + case SyntaxKind.Parameter: + case SyntaxKind.Property: + return getContextualTypeForInitializerExpression(node); + case SyntaxKind.ArrowFunction: + case SyntaxKind.ReturnStatement: + return getContextualTypeForReturnExpression(node); + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + return getContextualTypeForArgument(node); + case SyntaxKind.TypeAssertion: + return getTypeFromTypeNode((parent).type); + case SyntaxKind.BinaryExpression: + return getContextualTypeForBinaryOperand(node); + case SyntaxKind.PropertyAssignment: + return getContextualTypeForPropertyExpression(node); + case SyntaxKind.ArrayLiteral: + return getContextualTypeForElementExpression(node); + case SyntaxKind.ConditionalExpression: + return getContextualTypeForConditionalOperand(node); + } + return undefined; + } + // Presence of a contextual type mapper indicates inferential typing, except the identityMapper object is // used as a special marker for other purposes. function isInferentialContext(mapper: TypeMapper) { return mapper && mapper !== identityMapper; } - function checkArrayLiteral(node: ArrayLiteral, contextualType?: Type, contextualMapper?: TypeMapper): Type { - var contextualElementType = contextualType && getIndexTypeOfType(contextualType, IndexKind.Number); + function checkArrayLiteral(node: ArrayLiteral, contextualMapper?: TypeMapper): Type { var elementTypes: Type[] = []; forEach(node.elements, element => { if (element.kind !== SyntaxKind.OmittedExpression) { - var type = checkExpression(element, contextualElementType, contextualMapper); + var type = checkExpression(element, contextualMapper); if (!contains(elementTypes, type)) elementTypes.push(type); } }); - var elementType = getBestCommonType(elementTypes, isInferentialContext(contextualMapper) ? undefined : contextualElementType, true); + var contextualType = isInferentialContext(contextualMapper) ? undefined : getContextualType(node); + var contextualElementType = contextualType && getIndexTypeOfType(contextualType, IndexKind.Number); + var elementType = getBestCommonType(elementTypes, contextualElementType, true); if (!elementType) elementType = elementTypes.length ? emptyObjectType : undefinedType; return createArrayType(elementType); } @@ -3458,22 +3603,16 @@ module ts { return !isNaN(name); } - function getContextualTypeForProperty(type: Type, name: string) { - var prop = getPropertyOfType(type, name); - if (prop) return getTypeOfSymbol(prop); - return isNumericName(name) && getIndexTypeOfType(type, IndexKind.Number) || getIndexTypeOfType(type, IndexKind.String); - } - - function checkObjectLiteral(node: ObjectLiteral, contextualType?: Type, contextualMapper?: TypeMapper): Type { + function checkObjectLiteral(node: ObjectLiteral, contextualMapper?: TypeMapper): Type { var members = node.symbol.members; var properties: SymbolTable = {}; + var contextualType = getContextualType(node); for (var id in members) { if (hasProperty(members, id)) { var member = members[id]; if (member.flags & SymbolFlags.Property) { - var contextualPropType = contextualType && getContextualTypeForProperty(contextualType, member.name); - var type = checkExpression((member.declarations[0]).initializer, contextualPropType, contextualMapper); + var type = checkExpression((member.declarations[0]).initializer, contextualMapper); var prop = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, member.name); prop.declarations = member.declarations; prop.parent = member.parent; @@ -3500,11 +3639,11 @@ module ts { properties[member.name] = member; } } - var stringIndexType = getIndexType(properties, IndexKind.String); - var numberIndexType = getIndexType(properties, IndexKind.Number); + var stringIndexType = getIndexType(IndexKind.String); + var numberIndexType = getIndexType(IndexKind.Number); return createAnonymousType(node.symbol, properties, emptyArray, emptyArray, stringIndexType, numberIndexType); - function getIndexType(properties: SymbolTable, kind: IndexKind) { + function getIndexType(kind: IndexKind) { if (contextualType) { var indexType = getIndexTypeOfType(contextualType, kind); if (indexType) { @@ -3517,7 +3656,6 @@ module ts { } } } - return getBestCommonType(propTypes, isInferentialContext(contextualMapper) ? undefined : indexType); } } @@ -3632,14 +3770,16 @@ module ts { return unknownType; } - function checkUntypedCall(node: CallExpression): Type { - forEach(node.arguments, argument => { checkExpression(argument); }); - return anyType; + function resolveUntypedCall(node: CallExpression): Signature { + forEach(node.arguments, argument => { + checkExpression(argument); + }); + return anySignature; } - function checkErrorCall(node: CallExpression): Type { - checkUntypedCall(node); - return unknownType; + function resolveErrorCall(node: CallExpression): Signature { + resolveUntypedCall(node); + return unknownSignature; } function isCandidateSignature(node: CallExpression, signature: Signature) { @@ -3698,7 +3838,7 @@ module ts { // Inferentially type an expression by a contextual parameter type (section 4.12.2 in TypeScript spec) function inferentiallyTypeExpession(expr: Expression, contextualType: Type, contextualMapper: TypeMapper): Type { - var type = checkExpression(expr, contextualType, contextualMapper); + var type = checkExpressionWithContextualType(expr, contextualType, contextualMapper); var signature = getSingleCallSignature(type); if (signature && signature.typeParameters) { var contextualSignature = getSingleCallSignature(contextualType); @@ -3755,7 +3895,7 @@ module ts { // String literals get string literal types unless we're reporting errors var argType = arg.kind === SyntaxKind.StringLiteral && !reportErrors ? getStringLiteralType(arg) : - checkExpression(arg, paramType, excludeArgument && excludeArgument[i] ? identityMapper : undefined); + checkExpressionWithContextualType(arg, paramType, excludeArgument && excludeArgument[i] ? identityMapper : undefined); // Use argument expression as error location when reporting errors var isValidArgument = checkTypeRelatedTo(argType, paramType, relation, reportErrors ? arg : undefined, Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1, @@ -3768,12 +3908,12 @@ module ts { return true; } - function checkCall(node: CallExpression, signatures: Signature[]): Type { + function resolveCall(node: CallExpression, signatures: Signature[]): Signature { forEach(node.typeArguments, checkSourceElement); var candidates = collectCandidates(node, signatures); if (!candidates.length) { error(node, Diagnostics.Supplied_parameters_do_not_match_any_signature_of_call_target); - return checkErrorCall(node); + return resolveErrorCall(node); } var args = node.arguments || emptyArray; var excludeArgument: boolean[]; @@ -3799,7 +3939,7 @@ module ts { } var index = excludeArgument ? indexOf(excludeArgument, true) : -1; if (index < 0) { - return getReturnTypeOfSignature(candidate); + return candidate; } excludeArgument[index] = false; } @@ -3812,35 +3952,29 @@ module ts { // No signatures were applicable. Now report errors based on the last applicable signature with // no arguments excluded from assignability checks. checkApplicableSignature(node, candidate, relation, undefined, /*reportErrors*/ true); - return checkErrorCall(node); + return resolveErrorCall(node); } - function checkCallExpression(node: CallExpression): Type { + function resolveCallExpression(node: CallExpression): Signature { if (node.func.kind === SyntaxKind.SuperKeyword) { var superType = checkSuperExpression(node.func, true); if (superType !== unknownType) { - checkCall(node, getSignaturesOfType(superType, SignatureKind.Construct)); + return resolveCall(node, getSignaturesOfType(superType, SignatureKind.Construct)); } - else { - checkUntypedCall(node); - } - - // TS 1.0 spec: 4.8.1 - // The type of a super call expression is Void. - return voidType; + return resolveUntypedCall(node); } var funcType = checkExpression(node.func); if (funcType === unknownType) { // Another error has already been reported - return checkErrorCall(node); + return resolveErrorCall(node); } var apparentType = getApparentType(funcType) if (apparentType === unknownType) { // handler cases when funcType is type parameter with invalid constraint // Another error was already reported - return checkErrorCall(node); + return resolveErrorCall(node); } // Technically, this signatures list may be incomplete. We are taking the apparent type, @@ -3859,7 +3993,7 @@ module ts { if (node.typeArguments) { error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments); } - return checkUntypedCall(node); + return resolveUntypedCall(node); } // If FuncExpr's apparent type(section 3.8.1) is a function type, the call is a typed function call. // TypeScript employs overload resolution in typed function calls in order to support functions @@ -3871,16 +4005,16 @@ module ts { else { error(node, Diagnostics.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature); } - return checkErrorCall(node); + return resolveErrorCall(node); } - return checkCall(node, callSignatures); + return resolveCall(node, callSignatures); } - function checkNewExpression(node: NewExpression): Type { + function resolveNewExpression(node: NewExpression): Signature { var expressionType = checkExpression(node.func); if (expressionType === unknownType) { // Another error has already been reported - return checkErrorCall(node); + return resolveErrorCall(node); } // TS 1.0 spec: 4.11 // If ConstructExpr is of type Any, Args can be any argument @@ -3890,7 +4024,7 @@ module ts { error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments); } - return checkUntypedCall(node); + return resolveUntypedCall(node); } // If ConstructExpr's apparent type(section 3.8.1) is an object type with one or @@ -3902,7 +4036,7 @@ module ts { if (expressionType === unknownType) { // handler cases when original expressionType is a type parameter with invalid constraint // another error has already been reported - return checkErrorCall(node); + return resolveErrorCall(node); } // Technically, this signatures list may be incomplete. We are taking the apparent type, @@ -3911,7 +4045,7 @@ module ts { // that the user will not add any. var constructSignatures = getSignaturesOfType(expressionType, SignatureKind.Construct); if (constructSignatures.length) { - return checkCall(node, constructSignatures); + return resolveCall(node, constructSignatures); } // If ConstructExpr's apparent type is an object type with no construct signatures but @@ -3920,31 +4054,52 @@ module ts { // operation is Any. var callSignatures = getSignaturesOfType(expressionType, SignatureKind.Call); if (callSignatures.length) { - var type = checkCall(node, callSignatures); - - if (type !== voidType) { + var signature = resolveCall(node, callSignatures); + if (getReturnTypeOfSignature(signature) !== voidType) { error(node, Diagnostics.Only_a_void_function_can_be_called_with_the_new_keyword); } - - // Since we found no constructor signature, we (implicitly) return any. - if (program.getCompilerOptions().noImplicitAny) { - error(node, Diagnostics.new_expression_whose_target_lacks_a_construct_signature_implicitly_has_an_any_type); - } - - return anyType; + return signature; } error(node, Diagnostics.Cannot_use_new_with_an_expression_whose_type_lacks_a_call_or_construct_signature); - return checkErrorCall(node); + return resolveErrorCall(node); + } + + function getResolvedSignature(node: CallExpression): Signature { + var links = getNodeLinks(node); + if (!links.resolvedSignature) { + links.resolvedSignature = anySignature; + links.resolvedSignature = node.kind === SyntaxKind.CallExpression ? resolveCallExpression(node) : resolveNewExpression(node); + } + return links.resolvedSignature; + } + + function checkCallExpression(node: CallExpression): Type { + var signature = getResolvedSignature(node); + if (node.func.kind === SyntaxKind.SuperKeyword) { + return voidType; + } + if (node.kind === SyntaxKind.NewExpression) { + var declaration = signature.declaration; + if (declaration && (declaration.kind !== SyntaxKind.Constructor && declaration.kind !== SyntaxKind.ConstructSignature)) { + // When resolved signature is a call signature (and not a construct signature) the result type is any + if (program.getCompilerOptions().noImplicitAny) { + error(node, Diagnostics.new_expression_whose_target_lacks_a_construct_signature_implicitly_has_an_any_type); + } + return anyType; + } + } + return getReturnTypeOfSignature(signature); } function checkTypeAssertion(node: TypeAssertion): Type { + var exprType = checkExpression(node.operand); var targetType = getTypeFromTypeNode(node.type); - if (targetType === unknownType) return unknownType; - var exprType = checkExpression(node.operand, targetType); - var widenedType = getWidenedType(exprType); - if (!(isTypeAssignableTo(exprType, targetType) || isTypeAssignableTo(targetType, widenedType))) { - checkTypeAssignableTo(targetType, widenedType, node, Diagnostics.Neither_type_0_nor_type_1_is_assignable_to_the_other_Colon, Diagnostics.Neither_type_0_nor_type_1_is_assignable_to_the_other); + if (targetType !== unknownType) { + var widenedType = getWidenedType(exprType); + if (!(isTypeAssignableTo(exprType, targetType) || isTypeAssignableTo(targetType, widenedType))) { + checkTypeAssignableTo(targetType, widenedType, node, Diagnostics.Neither_type_0_nor_type_1_is_assignable_to_the_other_Colon, Diagnostics.Neither_type_0_nor_type_1_is_assignable_to_the_other); + } } return targetType; } @@ -3967,6 +4122,29 @@ module ts { pos < signature.parameters.length ? getTypeOfSymbol(signature.parameters[pos]) : anyType; } + function assignContextualTypes(node: FunctionExpression, contextualMapper?: TypeMapper) { + var links = getNodeLinks(node); + if (!(links.flags & NodeCheckFlags.ContextAssigned)) { + var contextualSignature = getContextualSignature(getContextualType(node)); + if (!(links.flags & NodeCheckFlags.ContextAssigned)) { + links.flags |= NodeCheckFlags.ContextAssigned; + if (contextualSignature) { + var signature = getSignaturesOfType(getTypeOfSymbol(node.symbol), SignatureKind.Call)[0]; + if (!node.typeParameters && !forEach(node.parameters, p => p.type)) { + assignContextualParameterTypes(signature, contextualSignature, contextualMapper || identityMapper); + } + if (!node.type) { + signature.resolvedReturnType = resolvingType; + var returnType = getReturnTypeFromBody(node, contextualMapper); + if (signature.resolvedReturnType === resolvingType) { + signature.resolvedReturnType = returnType; + } + } + } + } + } + } + function assignContextualParameterTypes(signature: Signature, context: Signature, mapper: TypeMapper) { var len = signature.parameters.length - (signature.hasRestParameter ? 1 : 0); for (var i = 0; i < len; i++) { @@ -3985,9 +4163,9 @@ module ts { } } - function getReturnTypeFromBody(func: FunctionDeclaration, contextualType?: Type, contextualMapper?: TypeMapper): Type { + function getReturnTypeFromBody(func: FunctionDeclaration, contextualMapper?: TypeMapper): Type { if (func.body.kind !== SyntaxKind.FunctionBlock) { - var unwidenedType = checkAndMarkExpression(func.body, contextualType, contextualMapper); + var unwidenedType = checkAndMarkExpression(func.body, contextualMapper); var widenedType = getWidenedType(unwidenedType); if (program.getCompilerOptions().noImplicitAny && widenedType !== unwidenedType && getInnermostTypeOfNestedArrayTypes(widenedType) === anyType) { @@ -3998,7 +4176,7 @@ module ts { } // Aggregate the types of expressions within all the return statements. - var types = checkAndAggregateReturnExpressionTypes(func.body, contextualType, contextualMapper); + var types = checkAndAggregateReturnExpressionTypes(func.body, contextualMapper); // Try to return the best common type if we have any return expressions. if (types.length > 0) { @@ -4061,13 +4239,13 @@ module ts { } /// Returns a set of types relating to every return expression relating to a function block. - function checkAndAggregateReturnExpressionTypes(body: Block, contextualType?: Type, contextualMapper?: TypeMapper): Type[] { + function checkAndAggregateReturnExpressionTypes(body: Block, contextualMapper?: TypeMapper): Type[] { var aggregatedTypes: Type[] = []; forEachReturnStatement(body, returnStatement => { var expr = returnStatement.expression; if (expr) { - var type = checkAndMarkExpression(expr, contextualType, contextualMapper); + var type = checkAndMarkExpression(expr, contextualMapper); if (!contains(aggregatedTypes, type)) { aggregatedTypes.push(type); } @@ -4120,42 +4298,26 @@ module ts { error(func.type, Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value_or_consist_of_a_single_throw_statement); } - function checkFunctionExpression(node: FunctionExpression, contextualType?: Type, contextualMapper?: TypeMapper): Type { + function checkFunctionExpression(node: FunctionExpression, contextualMapper?: TypeMapper): Type { // The identityMapper object is used to indicate that function expressions are wildcards if (contextualMapper === identityMapper) { return anyFunctionType; } - var type = getTypeOfSymbol(node.symbol); var links = getNodeLinks(node); - if (!(links.flags & NodeCheckFlags.TypeChecked)) { - var signature = getSignaturesOfType(type, SignatureKind.Call)[0]; - var contextualSignature = getContextualSignature(contextualType); - if (contextualSignature) { - if (!node.typeParameters && !forEach(node.parameters, p => p.type)) { - assignContextualParameterTypes(signature, contextualSignature, contextualMapper || identityMapper); - } - if (!node.type) { - signature.resolvedReturnType = resolvingType; - var returnType = getReturnTypeFromBody(node, getReturnTypeOfSignature(contextualSignature), contextualMapper); - if (signature.resolvedReturnType === resolvingType) { - signature.resolvedReturnType = returnType; - } - } - else { - checkIfNonVoidFunctionHasReturnExpressionsOrSingleThrowStatment(node, getTypeFromTypeNode(node.type)); - } - } + assignContextualTypes(node, contextualMapper); checkSignatureDeclaration(node); + if (node.type) { + checkIfNonVoidFunctionHasReturnExpressionsOrSingleThrowStatment(node, getTypeFromTypeNode(node.type)); + } if (node.body.kind === SyntaxKind.FunctionBlock) { checkSourceElement(node.body); } else { - var returnType = getReturnTypeOfSignature(signature); + var exprType = checkExpression(node.body); if (node.type) { - // Use default error messages for this check - checkTypeAssignableTo(checkExpression(node.body, returnType), returnType, node.body, /*chainedMessage*/ undefined, /*terminalMessage*/ undefined); + checkTypeAssignableTo(exprType, getTypeFromTypeNode(node.type), node.body, undefined, undefined); } } links.flags |= NodeCheckFlags.TypeChecked; @@ -4285,13 +4447,10 @@ module ts { return booleanType; } - function checkBinaryExpression(node: BinaryExpression, contextualType?: Type, contextualMapper?: TypeMapper) { + function checkBinaryExpression(node: BinaryExpression, contextualMapper?: TypeMapper) { var operator = node.operator; - var leftContextualType = operator === SyntaxKind.BarBarToken ? contextualType : undefined - var leftType = checkExpression(node.left, leftContextualType, contextualMapper); - var rightContextualType = operator >= SyntaxKind.FirstAssignment && operator <= SyntaxKind.LastAssignment ? leftType : - operator === SyntaxKind.BarBarToken ? contextualType || leftType : undefined; - var rightType = checkExpression(node.right, rightContextualType, contextualMapper); + var leftType = checkExpression(node.left, contextualMapper); + var rightType = checkExpression(node.right, contextualMapper); switch (operator) { case SyntaxKind.AsteriskToken: case SyntaxKind.AsteriskEqualsToken: @@ -4383,7 +4542,7 @@ module ts { case SyntaxKind.AmpersandAmpersandToken: return rightType; case SyntaxKind.BarBarToken: - return getBestCommonType([leftType, rightType], isInferentialContext(contextualMapper) ? undefined : contextualType); + return getBestCommonType([leftType, rightType], isInferentialContext(contextualMapper) ? undefined : getContextualType(node)); case SyntaxKind.EqualsToken: checkAssignmentOperator(rightType); return rightType; @@ -4413,40 +4572,45 @@ module ts { } } - function checkConditionalExpression(node: ConditionalExpression, contextualType?: Type, contextualMapper?: TypeMapper): Type { + function checkConditionalExpression(node: ConditionalExpression, contextualMapper?: TypeMapper): Type { checkExpression(node.condition); - var type1 = checkExpression(node.whenTrue, contextualType, contextualMapper); - var type2 = checkExpression(node.whenFalse, contextualType, contextualMapper); - var resultType = getBestCommonType([type1, type2], isInferentialContext(contextualMapper) ? undefined : contextualType, true); + var type1 = checkExpression(node.whenTrue, contextualMapper); + var type2 = checkExpression(node.whenFalse, contextualMapper); + var contextualType = isInferentialContext(contextualMapper) ? undefined : getContextualType(node); + var resultType = getBestCommonType([type1, type2], contextualType, true); if (!resultType) { - - if (contextualType && !isInferentialContext(contextualMapper)) { + if (contextualType) { error(node, Diagnostics.No_best_common_type_exists_between_0_1_and_2, typeToString(contextualType), typeToString(type1), typeToString(type2)); } else { error(node, Diagnostics.No_best_common_type_exists_between_0_and_1, typeToString(type1), typeToString(type2)); } - resultType = emptyObjectType; } return resultType; } - function checkAndMarkExpression(node: Expression, contextualType?: Type, contextualMapper?: TypeMapper): Type { - var result = checkExpression(node, contextualType, contextualMapper); + function checkExpressionWithContextualType(node: Expression, contextualType: Type, contextualMapper?: TypeMapper): Type { + node.contextualType = contextualType; + var result = checkExpression(node, contextualMapper); + node.contextualType = undefined; + return result; + } + + function checkAndMarkExpression(node: Expression, contextualMapper?: TypeMapper): Type { + var result = checkExpression(node, contextualMapper); getNodeLinks(node).flags |= NodeCheckFlags.TypeChecked; return result; } - // Checks an expression and returns its type. The contextualType parameter provides a contextual type for - // the check or is undefined if there is no contextual type. The contextualMapper parameter serves two - // purposes: When contextualMapper is not undefined and not equal to the identityMapper function object - // it provides a type mapper to use during inferential typing (the contextual type is then a generic type). - // When contextualMapper is equal to the identityMapper function object, it serves as an indicator that all - // contained function and arrow expressions should be considered to have the wildcard function type; this - // form of type check is used during overload resolution to exclude contextually typed function and arrow - // expressions in the initial phase. - function checkExpression(node: Expression, contextualType?: Type, contextualMapper?: TypeMapper): Type { + // Checks an expression and returns its type. The contextualMapper parameter serves two purposes: When + // contextualMapper is not undefined and not equal to the identityMapper function object it indicates that the + // expression is being inferentially typed (section 4.12.2 in spec) and provides the type mapper to use in + // conjuction with the generic contextual type. When contextualMapper is equal to the identityMapper function + // object, it serves as an indicator that all contained function and arrow expressions should be considered to + // have the wildcard function type; this form of type check is used during overload resolution to exclude + // contextually typed function and arrow expressions in the initial phase. + function checkExpression(node: Expression, contextualMapper?: TypeMapper): Type { switch (node.kind) { case SyntaxKind.Identifier: return checkIdentifier(node); @@ -4468,32 +4632,31 @@ module ts { case SyntaxKind.QualifiedName: return checkPropertyAccess(node); case SyntaxKind.ArrayLiteral: - return checkArrayLiteral(node, contextualType, contextualMapper); + return checkArrayLiteral(node, contextualMapper); case SyntaxKind.ObjectLiteral: - return checkObjectLiteral(node, contextualType, contextualMapper); + return checkObjectLiteral(node, contextualMapper); case SyntaxKind.PropertyAccess: return checkPropertyAccess(node); case SyntaxKind.IndexedAccess: return checkIndexedAccess(node); case SyntaxKind.CallExpression: - return checkCallExpression(node); case SyntaxKind.NewExpression: - return checkNewExpression(node); + return checkCallExpression(node); case SyntaxKind.TypeAssertion: return checkTypeAssertion(node); case SyntaxKind.ParenExpression: return checkExpression((node).expression); case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: - return checkFunctionExpression(node, contextualType, contextualMapper); + return checkFunctionExpression(node, contextualMapper); case SyntaxKind.PrefixOperator: return checkPrefixExpression(node); case SyntaxKind.PostfixOperator: return checkPostfixExpression(node); case SyntaxKind.BinaryExpression: - return checkBinaryExpression(node, contextualType, contextualMapper); + return checkBinaryExpression(node, contextualMapper); case SyntaxKind.ConditionalExpression: - return checkConditionalExpression(node, contextualType, contextualMapper); + return checkConditionalExpression(node, contextualMapper); } return unknownType; } @@ -5170,7 +5333,7 @@ module ts { if (node.initializer) { if (!(getNodeLinks(node.initializer).flags & NodeCheckFlags.TypeChecked)) { // Use default messages - checkTypeAssignableTo(checkAndMarkExpression(node.initializer, type), type, node, /*chainedMessage*/ undefined, /*terminalMessage*/ undefined); + checkTypeAssignableTo(checkAndMarkExpression(node.initializer), type, node, /*chainedMessage*/ undefined, /*terminalMessage*/ undefined); } } @@ -5291,12 +5454,12 @@ module ts { func.type || (func.kind === SyntaxKind.GetAccessor && getSetAccessorTypeAnnotationNode(getDeclarationOfKind(func.symbol, SyntaxKind.SetAccessor))); if (checkAssignability) { - checkTypeAssignableTo(checkExpression(node.expression, returnType), returnType, node.expression, /*chainedMessage*/ undefined, /*terminalMessage*/ undefined); + checkTypeAssignableTo(checkExpression(node.expression), returnType, node.expression, /*chainedMessage*/ undefined, /*terminalMessage*/ undefined); } else if (func.kind == SyntaxKind.Constructor) { // constructor doesn't have explicit return type annontation and yet its return type is known - declaring type // handle constructors and issue specialized error message for them. - if (!isTypeAssignableTo(checkExpression(node.expression, returnType), returnType)) { + if (!isTypeAssignableTo(checkExpression(node.expression), returnType)) { error(node.expression, Diagnostics.Return_type_of_constructor_signature_must_be_assignable_to_the_instance_type_of_the_class); } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 9d15e453334..5f9f02e5475 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -319,7 +319,9 @@ module ts { text: string; } - export interface Expression extends Node { } + export interface Expression extends Node { + contextualType?: Type; + } export interface UnaryExpression extends Expression { operator: SyntaxKind; @@ -730,17 +732,18 @@ module ts { } export enum NodeCheckFlags { - TypeChecked = 0x00000001, // Node has been type checked - LexicalThis = 0x00000002, // Lexical 'this' reference - CaptureThis = 0x00000004, // Lexical 'this' used in body - EmitExtends = 0x00000008, // Emit __extends - SuperInstance = 0x00000010, // Instance 'super' reference - SuperStatic = 0x00000020, // Static 'super' reference + TypeChecked = 0x00000001, // Node has been type checked + LexicalThis = 0x00000002, // Lexical 'this' reference + CaptureThis = 0x00000004, // Lexical 'this' used in body + EmitExtends = 0x00000008, // Emit __extends + SuperInstance = 0x00000010, // Instance 'super' reference + SuperStatic = 0x00000020, // Static 'super' reference + ContextAssigned = 0x00000040, // Contextual types have been assigned } export interface NodeLinks { resolvedType?: Type; // Cached type of type node - resolvedSignature?: Signature; // Cached signature of signature node + resolvedSignature?: Signature; // Cached signature of signature node or call expression resolvedSymbol?: Symbol; // Cached name resolution result flags?: NodeCheckFlags; // Set of flags specific to Node enumMemberValue?: number; // Constant value of enum member diff --git a/tests/baselines/reference/functionImplementationErrors.errors.txt b/tests/baselines/reference/functionImplementationErrors.errors.txt index 3da56bd6818..5c2d678840e 100644 --- a/tests/baselines/reference/functionImplementationErrors.errors.txt +++ b/tests/baselines/reference/functionImplementationErrors.errors.txt @@ -1,4 +1,4 @@ -==== tests/cases/conformance/functions/functionImplementationErrors.ts (7 errors) ==== +==== tests/cases/conformance/functions/functionImplementationErrors.ts (8 errors) ==== // FunctionExpression with no return type annotation with multiple return statements with unrelated types var f1 = function () { ~~~~~~~~~~~~~ @@ -68,6 +68,8 @@ // FunctionExpression with non -void return type annotation with a throw, no return, and other code // Should be error but isn't undefined === function (): number { + ~~~~~~ +!!! A function whose declared type is neither 'void' nor 'any' must return a value or consist of a single 'throw' statement. throw undefined; var x = 4; }; diff --git a/tests/baselines/reference/parserCastVersusArrowFunction1.errors.txt b/tests/baselines/reference/parserCastVersusArrowFunction1.errors.txt index b9ae061ed69..2686c59da36 100644 --- a/tests/baselines/reference/parserCastVersusArrowFunction1.errors.txt +++ b/tests/baselines/reference/parserCastVersusArrowFunction1.errors.txt @@ -1,8 +1,10 @@ -==== tests/cases/conformance/parser/ecmascript5/Generics/parserCastVersusArrowFunction1.ts (7 errors) ==== +==== tests/cases/conformance/parser/ecmascript5/Generics/parserCastVersusArrowFunction1.ts (13 errors) ==== var v = () => 1; var v = a; ~ !!! Cannot find name 'T'. + ~ +!!! Cannot find name 'a'. var v = (a) => 1; ~ @@ -17,9 +19,19 @@ var v = (a); ~ !!! Cannot find name 'T'. + ~ +!!! Cannot find name 'a'. var v = (a, b); ~ !!! Cannot find name 'T'. + ~ +!!! Cannot find name 'a'. + ~ +!!! Cannot find name 'b'. var v = (a = 1, b = 2); ~ -!!! Cannot find name 'T'. \ No newline at end of file +!!! Cannot find name 'T'. + ~ +!!! Cannot find name 'a'. + ~ +!!! Cannot find name 'b'. \ No newline at end of file diff --git a/tests/baselines/reference/parserRealSource8.errors.txt b/tests/baselines/reference/parserRealSource8.errors.txt index 42dca350919..d4f6467fc10 100644 --- a/tests/baselines/reference/parserRealSource8.errors.txt +++ b/tests/baselines/reference/parserRealSource8.errors.txt @@ -1,4 +1,4 @@ -==== tests/cases/conformance/parser/ecmascript5/parserRealSource8.ts (133 errors) ==== +==== tests/cases/conformance/parser/ecmascript5/parserRealSource8.ts (134 errors) ==== // Copyright (c) Microsoft. All rights reserved. Licensed under the Apache License, Version 2.0. // See LICENSE.txt in the project root for complete license information. @@ -383,6 +383,8 @@ outerFnc.type.members = ((container).type.memberScope).valueMembers; ~~~~~~~~~~~~~~~~~~ !!! Cannot find name 'SymbolScopeBuilder'. + ~~~~~~~~~~ +!!! Cannot find name 'TypeSymbol'. } funcScope = context.scopeChain.fnc.type.memberScope; outerFnc.innerStaticFuncs[outerFnc.innerStaticFuncs.length] = funcDecl; diff --git a/tests/baselines/reference/parserVariableDeclaration3.errors.txt b/tests/baselines/reference/parserVariableDeclaration3.errors.txt index b2dfcb1c4e9..8d10b4ba5de 100644 --- a/tests/baselines/reference/parserVariableDeclaration3.errors.txt +++ b/tests/baselines/reference/parserVariableDeclaration3.errors.txt @@ -1,4 +1,4 @@ -==== tests/cases/conformance/parser/ecmascript5/VariableDeclarations/parserVariableDeclaration3.ts (3 errors) ==== +==== tests/cases/conformance/parser/ecmascript5/VariableDeclarations/parserVariableDeclaration3.ts (4 errors) ==== function runTests() { var outfile = new Harness.Compiler.WriterAggregator() ~~~~~~~ @@ -8,6 +8,8 @@ !!! Cannot find name 'Harness'. , compiler = new TypeScript.TypeScriptCompiler(outerr) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! Cannot find name 'TypeScript'. + ~~~~~~~~~~ !!! Cannot find name 'TypeScript'. , code; } \ No newline at end of file From bd97ba443af70f23bf7238eac26e55b3eb168b6f Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 31 Jul 2014 15:48:03 -0700 Subject: [PATCH 2/6] Restructuring and cleanup. Changed binder to record catch clause instead of catch variable as symbol declaration. Restructuring of getTypeOfVariableDeclaration and getTypeOfVariableOrParameterOrProperty methods. Restructuring of checkFunctionExpression method. --- src/compiler/binder.ts | 2 +- src/compiler/checker.ts | 247 +++++++++++++++++----------------------- src/compiler/types.ts | 14 +-- 3 files changed, 115 insertions(+), 148 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index ec67b496db6..e0164514de0 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -250,7 +250,7 @@ module ts { function bindCatchVariableDeclaration(node: CatchBlock) { var symbol = createSymbol(SymbolFlags.Variable, node.variable.text || "__missing"); - addDeclarationToSymbol(symbol, node.variable, SymbolFlags.Variable); + addDeclarationToSymbol(symbol, node, SymbolFlags.Variable); var saveParent = parent; parent = node; forEachChild(node, bind); diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fd89c889f9e..ef6dff1414e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -305,7 +305,7 @@ module ts { case SyntaxKind.CatchBlock: var id = (location).variable; if (name === id.text) { - return returnResolvedSymbol((location).variable.symbol); + return returnResolvedSymbol(location.symbol); } break; } @@ -1080,122 +1080,87 @@ module ts { } function getTypeOfVariableDeclaration(declaration: VariableDeclaration): Type { - var type: Type; - - if (declaration.parent.kind === SyntaxKind.CatchBlock || declaration.parent.kind === SyntaxKind.ForInStatement) { - type = anyType; + // A variable declared in a for..in statement is always of type any + if (declaration.parent.kind === SyntaxKind.ForInStatement) { + return anyType; } - else if (declaration.type) { - type = getTypeFromTypeNode(declaration.type); + // Use type from type annotation if one is present + if (declaration.type) { + return getTypeFromTypeNode(declaration.type); } - else { - // TypeScript 1.0 spec (April 2014): - // If only one accessor includes a type annotation, the other behaves as if it had the same type annotation. - // If neither accessor includes a type annotation, the inferred return type of the get accessor becomes the parameter type of the set accessor. - if (declaration.kind === SyntaxKind.Parameter && declaration.parent.kind === SyntaxKind.SetAccessor) { + if (declaration.kind === SyntaxKind.Parameter) { + var func = declaration.parent; + // For a parameter of a set accessor, use the type of the get accessor if one is present + if (func.kind === SyntaxKind.SetAccessor) { var getter = getDeclarationOfKind(declaration.parent.symbol, SyntaxKind.GetAccessor); if (getter) { - //getReturnTypeOfSignature will check both type annotation and return type inferred from body - type = getReturnTypeOfSignature(getSignatureFromDeclaration(getter)); + return getReturnTypeOfSignature(getSignatureFromDeclaration(getter)); } } - - var unwidenedType: Type; - - if (!type) { - if (declaration.initializer) { - unwidenedType = checkAndMarkExpression(declaration.initializer); - type = getWidenedType(unwidenedType); - } - else if (declaration.flags & NodeFlags.Rest) { - type = createArrayType(anyType); - } - else { - type = anyType; - } - } - - if (program.getCompilerOptions().noImplicitAny && shouldReportNoImplicitAnyOnVariableOrParameterOrProperty(declaration, type, unwidenedType)) { - reportNoImplicitAnyOnVariableOrParameterOrProperty(declaration, type); + // Use contextual parameter type if one is available + var type = getContextuallyTypedParameterType(declaration); + if (type) { + return type; } } + // Use the type of the initializer expression if one is present + if (declaration.initializer) { + var unwidenedType = checkAndMarkExpression(declaration.initializer); + var type = getWidenedType(unwidenedType); + return type !== unwidenedType ? checkImplicitAny(type) : type; + } + // Rest parameter defaults to type any[] + if (declaration.flags & NodeFlags.Rest) { + return checkImplicitAny(createArrayType(anyType)); + } + // Other parameters default to type any + return checkImplicitAny(anyType); - return type; - - function shouldReportNoImplicitAnyOnVariableOrParameterOrProperty(declaration: VariableDeclaration, type: Type, unwidenedType: Type): boolean { - // If we attempted to widen, the resulting type has to be a different. - if (type === unwidenedType) { - return false; + function checkImplicitAny(type: Type): Type { + if (!program.getCompilerOptions().noImplicitAny) { + return type; } - // We need to have ended up with 'any', 'any[]', 'any[][]', etc. if (getInnermostTypeOfNestedArrayTypes(type) !== anyType) { - return false; + return type; } - // Ignore privates within ambient contexts; they exist purely for documentative purposes to avoid name clashing. // (e.g. privates within .d.ts files do not expose type information) if (isPrivateWithinAmbient(declaration) || (declaration.kind === SyntaxKind.Parameter && isPrivateWithinAmbient(declaration.parent))) { - return false; + return type; } - - return true; - } - - function reportNoImplicitAnyOnVariableOrParameterOrProperty(declaration: VariableDeclaration, type: Type): void { - var varName = identifierToString(declaration.name); - var typeName = typeToString(type); - switch (declaration.kind) { - case SyntaxKind.VariableDeclaration: - error(declaration, Diagnostics.Variable_0_implicitly_has_an_1_type, varName, typeName) - break; - case SyntaxKind.Property: - error(declaration, Diagnostics.Member_0_implicitly_has_an_1_type, varName, typeName) + var diagnostic = Diagnostics.Member_0_implicitly_has_an_1_type; break; - case SyntaxKind.Parameter: - var funcDeclaration = declaration.parent; - - // If this is a rest parameter, we should have widened specifically to 'any[]'. - if (declaration.flags & NodeFlags.Rest) { - error(declaration, Diagnostics.Rest_parameter_0_implicitly_has_an_any_type, varName) - } - else { - error(declaration, Diagnostics.Parameter_0_implicitly_has_an_1_type, varName, typeName) - } - + var diagnostic = declaration.flags & NodeFlags.Rest ? + Diagnostics.Rest_parameter_0_implicitly_has_an_any_type : + Diagnostics.Parameter_0_implicitly_has_an_1_type; break; - default: - Debug.fail("Received a '" + SyntaxKind[declaration.kind] + "', but expected '" + - SyntaxKind[SyntaxKind.VariableDeclaration] + "', '" + - SyntaxKind[SyntaxKind.Property] + "', or '" + - SyntaxKind[SyntaxKind.Parameter] + "'.\r\n"); + var diagnostic = Diagnostics.Variable_0_implicitly_has_an_1_type; } + error(declaration, diagnostic, identifierToString(declaration.name), typeToString(type)); + return type; } - } function getTypeOfVariableOrParameterOrProperty(symbol: Symbol): Type { var links = getSymbolLinks(symbol); if (!links.type) { + // Handle prototype property if (symbol.flags & SymbolFlags.Prototype) { return links.type = getTypeOfPrototypeProperty(symbol); } - var declaration = symbol.valueDeclaration; - if (declaration.kind === SyntaxKind.Parameter && !declaration.type) { - var parent = declaration.parent; - if (parent.kind === SyntaxKind.FunctionExpression || parent.kind === SyntaxKind.ArrowFunction) { - assignContextualTypes(parent); - if (links.type) { - return links.type; - } - } + // Handle catch clause variables + var declaration = symbol.valueDeclaration; + if (declaration.kind === SyntaxKind.CatchBlock) { + return links.type = anyType; } + // Handle variable, parameter or property links.type = resolvingType; - var type = getTypeOfVariableDeclaration(symbol.valueDeclaration); + var type = getTypeOfVariableDeclaration(declaration); if (links.type === resolvingType) { links.type = type; } @@ -1207,7 +1172,7 @@ module ts { } function getSetAccessorTypeAnnotationNode(accessor: AccessorDeclaration): TypeNode { - return accessor && accessor.parameters.length > 0 && accessor.parameters[0].type; + return accessor && accessor.parameters.length > 0 && accessor.parameters[0].type; } function getAnnotatedAccessorType(accessor: AccessorDeclaration): Type { @@ -3445,10 +3410,24 @@ module ts { } function getTypeOfExpression(node: Expression): Type { - // TODO: Return type cached in NodeLinks + // TODO: Optimize by caching type in NodeLinks? return checkExpression(node); } + // Return contextual type of parameter or undefined if no contextual type is available + function getContextuallyTypedParameterType(parameter: VariableDeclaration): Type { + var func = parameter.parent; + if (func.kind === SyntaxKind.FunctionExpression || func.kind === SyntaxKind.ArrowFunction) { + if (isContextSensitiveExpression(func)) { + var signature = getContextualSignature(func); + if (signature) { + return getTypeAtPosition(signature, indexOf(func.parameters, parameter)); + } + } + } + return undefined; + } + function getContextualTypeForInitializerExpression(node: Expression): Type { var declaration = node.parent; if (node === declaration.initializer) { @@ -3456,18 +3435,7 @@ module ts { return getTypeFromTypeNode(declaration.type); } if (declaration.kind === SyntaxKind.Parameter) { - var func = declaration.parent; - if (func.kind === SyntaxKind.FunctionExpression || func.kind === SyntaxKind.ArrowFunction) { - if (isContextSensitiveExpression(func)) { - var type = getContextualType(func); - if (type) { - var signature = getContextualSignature(type); - if (signature) { - return getTypeAtPosition(signature, indexOf(func.parameters, declaration)); - } - } - } - } + return getContextuallyTypedParameterType(declaration); } } return undefined; @@ -3483,12 +3451,9 @@ module ts { } // Otherwise, if the containing function is contextually typed by a function type with exactly one call signature // and that call signature is non-generic, return statements are contextually typed by the return type of the signature - var type = getContextualType(func); - if (type) { - var signature = getContextualSignature(type); - if (signature) { - return getReturnTypeOfSignature(signature); - } + var signature = getContextualSignature(func); + if (signature) { + return getReturnTypeOfSignature(signature); } } return undefined; @@ -3578,6 +3543,20 @@ module ts { return undefined; } + function getContextualSignature(node: Expression): Signature { + var type = getContextualType(node); + if (type) { + var signatures = getSignaturesOfType(type, SignatureKind.Call); + if (signatures.length === 1) { + var signature = signatures[0]; + if (!signature.typeParameters) { + return signature; + } + } + } + return undefined; + } + // Presence of a contextual type mapper indicates inferential typing, except the identityMapper object is // used as a special marker for other purposes. function isInferentialContext(mapper: TypeMapper) { @@ -4104,47 +4083,12 @@ module ts { return targetType; } - function getContextualSignature(contextualType: Type) { - if (contextualType) { - var signatures = getSignaturesOfType(contextualType, SignatureKind.Call); - if (signatures.length === 1) { - var signature = signatures[0]; - if (!signature.typeParameters) { - return signature; - } - } - } - } - function getTypeAtPosition(signature: Signature, pos: number): Type { return signature.hasRestParameter ? pos < signature.parameters.length - 1 ? getTypeOfSymbol(signature.parameters[pos]) : getRestTypeOfSignature(signature) : pos < signature.parameters.length ? getTypeOfSymbol(signature.parameters[pos]) : anyType; } - function assignContextualTypes(node: FunctionExpression, contextualMapper?: TypeMapper) { - var links = getNodeLinks(node); - if (!(links.flags & NodeCheckFlags.ContextAssigned)) { - var contextualSignature = getContextualSignature(getContextualType(node)); - if (!(links.flags & NodeCheckFlags.ContextAssigned)) { - links.flags |= NodeCheckFlags.ContextAssigned; - if (contextualSignature) { - var signature = getSignaturesOfType(getTypeOfSymbol(node.symbol), SignatureKind.Call)[0]; - if (!node.typeParameters && !forEach(node.parameters, p => p.type)) { - assignContextualParameterTypes(signature, contextualSignature, contextualMapper || identityMapper); - } - if (!node.type) { - signature.resolvedReturnType = resolvingType; - var returnType = getReturnTypeFromBody(node, contextualMapper); - if (signature.resolvedReturnType === resolvingType) { - signature.resolvedReturnType = returnType; - } - } - } - } - } - } - function assignContextualParameterTypes(signature: Signature, context: Signature, mapper: TypeMapper) { var len = signature.parameters.length - (signature.hasRestParameter ? 1 : 0); for (var i = 0; i < len; i++) { @@ -4303,10 +4247,32 @@ module ts { if (contextualMapper === identityMapper) { return anyFunctionType; } - var type = getTypeOfSymbol(node.symbol); var links = getNodeLinks(node); + var type = getTypeOfSymbol(node.symbol); + // Check if function expression is contextually typed and assign parameter types if so + if (!(links.flags & NodeCheckFlags.ContextChecked)) { + var contextualSignature = getContextualSignature(node); + // If a type check is started at a function expression that is an argument of a function call, obtaining the + // contextual type may recursively get back to here during overload resolution of the call. If so, we will have + // already assigned contextual types. + if (!(links.flags & NodeCheckFlags.ContextChecked)) { + links.flags |= NodeCheckFlags.ContextChecked; + if (contextualSignature) { + var signature = getSignaturesOfType(type, SignatureKind.Call)[0]; + if (isContextSensitiveExpression(node)) { + assignContextualParameterTypes(signature, contextualSignature, contextualMapper || identityMapper); + } + if (!node.type) { + signature.resolvedReturnType = resolvingType; + var returnType = getReturnTypeFromBody(node, contextualMapper); + if (signature.resolvedReturnType === resolvingType) { + signature.resolvedReturnType = returnType; + } + } + } + } + } if (!(links.flags & NodeCheckFlags.TypeChecked)) { - assignContextualTypes(node, contextualMapper); checkSignatureDeclaration(node); if (node.type) { checkIfNonVoidFunctionHasReturnExpressionsOrSingleThrowStatment(node, getTypeFromTypeNode(node.type)); @@ -4591,9 +4557,10 @@ module ts { } function checkExpressionWithContextualType(node: Expression, contextualType: Type, contextualMapper?: TypeMapper): Type { + var saveContextualType = node.contextualType; node.contextualType = contextualType; var result = checkExpression(node, contextualMapper); - node.contextualType = undefined; + node.contextualType = saveContextualType; return result; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 5f9f02e5475..78f462230f3 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -732,13 +732,13 @@ module ts { } export enum NodeCheckFlags { - TypeChecked = 0x00000001, // Node has been type checked - LexicalThis = 0x00000002, // Lexical 'this' reference - CaptureThis = 0x00000004, // Lexical 'this' used in body - EmitExtends = 0x00000008, // Emit __extends - SuperInstance = 0x00000010, // Instance 'super' reference - SuperStatic = 0x00000020, // Static 'super' reference - ContextAssigned = 0x00000040, // Contextual types have been assigned + TypeChecked = 0x00000001, // Node has been type checked + LexicalThis = 0x00000002, // Lexical 'this' reference + CaptureThis = 0x00000004, // Lexical 'this' used in body + EmitExtends = 0x00000008, // Emit __extends + SuperInstance = 0x00000010, // Instance 'super' reference + SuperStatic = 0x00000020, // Static 'super' reference + ContextChecked = 0x00000040, // Contextual types have been assigned } export interface NodeLinks { From 352a44df3fde32e86a7e76dd07039c822bdb8b86 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 31 Jul 2014 15:56:00 -0700 Subject: [PATCH 3/6] Accepting new baselines. Previous commit fixes overly aggressive -noImplictAny reporting. In the test case the source of the error is the 'getAndSet' property that implicitly gets type any. The fact that the setter then gets type any isn't actually an error. --- .../implicitAnyGetAndSetAccessorWithAnyReturnType.errors.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/baselines/reference/implicitAnyGetAndSetAccessorWithAnyReturnType.errors.txt b/tests/baselines/reference/implicitAnyGetAndSetAccessorWithAnyReturnType.errors.txt index d6439688597..c3484202590 100644 --- a/tests/baselines/reference/implicitAnyGetAndSetAccessorWithAnyReturnType.errors.txt +++ b/tests/baselines/reference/implicitAnyGetAndSetAccessorWithAnyReturnType.errors.txt @@ -1,4 +1,4 @@ -==== tests/cases/compiler/implicitAnyGetAndSetAccessorWithAnyReturnType.ts (9 errors) ==== +==== tests/cases/compiler/implicitAnyGetAndSetAccessorWithAnyReturnType.ts (8 errors) ==== // these should be errors class GetAndSet { getAndSet = null; // error at "getAndSet" @@ -14,8 +14,6 @@ public set haveGetAndSet(value) { // error at "value" ~~~~~~~~~~~~~ !!! Accessors are only available when targeting ECMAScript 5 and higher. - ~~~~~ -!!! Parameter 'value' implicitly has an 'any' type. this.getAndSet = value; } } From 5d25821cff34f65e001ab5579fd4f4fbaf33c6eb Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 1 Aug 2014 08:13:38 -0700 Subject: [PATCH 4/6] Introduced fullTypeCheck flag. checkFunctionExpression only type checks function body if fullTypeCheck is true. --- src/compiler/checker.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ef6dff1414e..99b4d776b43 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -56,6 +56,7 @@ module ts { var stringLiteralTypes: Map = {}; + var fullTypeCheck = false; var emitExtends = false; var mergedSymbols: Symbol[] = []; @@ -4272,7 +4273,7 @@ module ts { } } } - if (!(links.flags & NodeCheckFlags.TypeChecked)) { + if (fullTypeCheck && !(links.flags & NodeCheckFlags.TypeChecked)) { checkSignatureDeclaration(node); if (node.type) { checkIfNonVoidFunctionHasReturnExpressionsOrSingleThrowStatment(node, getTypeFromTypeNode(node.type)); @@ -6019,9 +6020,12 @@ module ts { } } + // Fully type check a source file and collect the relevant diagnostics. The fullTypeCheck flag is true during this + // operation, but otherwise is false when the compiler is used by the language service. function checkSourceFile(node: SourceFile) { var links = getNodeLinks(node); if (!(links.flags & NodeCheckFlags.TypeChecked)) { + fullTypeCheck = true; emitExtends = false; potentialThisCollisions.length = 0; forEach(node.statements, checkSourceElement); @@ -6038,6 +6042,7 @@ module ts { } if (emitExtends) links.flags |= NodeCheckFlags.EmitExtends; links.flags |= NodeCheckFlags.TypeChecked; + fullTypeCheck = false; } } From 116401b2c859c1248ea2afe8c1a0cf361bf8ca19 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 3 Aug 2014 15:53:06 -0700 Subject: [PATCH 5/6] Addressing CR feedback. checkImplicitAny now returns void. --- src/compiler/checker.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 99b4d776b43..36ab4a7b142 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1108,27 +1108,28 @@ module ts { if (declaration.initializer) { var unwidenedType = checkAndMarkExpression(declaration.initializer); var type = getWidenedType(unwidenedType); - return type !== unwidenedType ? checkImplicitAny(type) : type; + if (type !== unwidenedType) { + checkImplicitAny(type); + } + return type; } - // Rest parameter defaults to type any[] - if (declaration.flags & NodeFlags.Rest) { - return checkImplicitAny(createArrayType(anyType)); - } - // Other parameters default to type any - return checkImplicitAny(anyType); + // Rest parameters default to type any[], other parameters default to type any + var type = declaration.flags & NodeFlags.Rest ? createArrayType(anyType) : anyType; + checkImplicitAny(type); + return type; - function checkImplicitAny(type: Type): Type { + function checkImplicitAny(type: Type) { if (!program.getCompilerOptions().noImplicitAny) { - return type; + return; } // We need to have ended up with 'any', 'any[]', 'any[][]', etc. if (getInnermostTypeOfNestedArrayTypes(type) !== anyType) { - return type; + return; } // Ignore privates within ambient contexts; they exist purely for documentative purposes to avoid name clashing. // (e.g. privates within .d.ts files do not expose type information) if (isPrivateWithinAmbient(declaration) || (declaration.kind === SyntaxKind.Parameter && isPrivateWithinAmbient(declaration.parent))) { - return type; + return; } switch (declaration.kind) { case SyntaxKind.Property: @@ -1143,7 +1144,6 @@ module ts { var diagnostic = Diagnostics.Variable_0_implicitly_has_an_1_type; } error(declaration, diagnostic, identifierToString(declaration.name), typeToString(type)); - return type; } } From 2b26e3797642c396a1e70c787dd3ffe2b0e2cc06 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 4 Aug 2014 11:40:56 -0700 Subject: [PATCH 6/6] Addressing CR feedback. --- src/compiler/checker.ts | 2 +- src/compiler/types.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 36ab4a7b142..e9370005df7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3416,7 +3416,7 @@ module ts { } // Return contextual type of parameter or undefined if no contextual type is available - function getContextuallyTypedParameterType(parameter: VariableDeclaration): Type { + function getContextuallyTypedParameterType(parameter: ParameterDeclaration): Type { var func = parameter.parent; if (func.kind === SyntaxKind.FunctionExpression || func.kind === SyntaxKind.ArrowFunction) { if (isContextSensitiveExpression(func)) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 78f462230f3..359896c9e4b 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -320,7 +320,7 @@ module ts { } export interface Expression extends Node { - contextualType?: Type; + contextualType?: Type; // Used to temporarily assign a contextual type during overload resolution } export interface UnaryExpression extends Expression {