Skip to content

Commit

Permalink
Contextually type undefined/null/[]/{}
Browse files Browse the repository at this point in the history
Also:
1. Address review comments.
2. Add test cases to implementedPropertyContextualTyping.*
3. Improve some tests: bestCommonTypeOfTuple2 and
destructuringParameterDeclaration2.
4. Make some tests worse due to exposing bug 6190:
requiredInitializedParameter.*. I'll fix it separately.
  • Loading branch information
sandersn committed Dec 21, 2015
1 parent 6bf5c3f commit 055ae8c
Show file tree
Hide file tree
Showing 16 changed files with 280 additions and 229 deletions.
120 changes: 82 additions & 38 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2593,16 +2593,16 @@ namespace ts {
}
}

if (declaration.kind === SyntaxKind.PropertyDeclaration) {
const type = getTypeOfBasePropertyDeclaration(declaration);
if (type) {
return type;
}
}

// Use the type of the initializer expression if one is present
if (declaration.initializer) {
return checkExpressionCached(declaration.initializer);
let mapper: TypeMapper;
if (declaration.kind === SyntaxKind.PropertyDeclaration) {
const type = getTypeOfBasePropertyDeclaration(<PropertyDeclaration>declaration);
if (type) {
mapper = createTypeMapper([undefinedType, nullType], [type, type]);
}
}
return checkExpressionCached(declaration.initializer, mapper);
}

// If it is a short-hand property assignment, use the type of the identifier
Expand Down Expand Up @@ -2902,29 +2902,35 @@ namespace ts {
return unknownType;
}

function getTypeOfBasePropertyDeclaration(declaration: VariableLikeDeclaration) {
function getTypeOfBasePropertyDeclaration(declaration: PropertyDeclaration) {
if (declaration.parent.kind === SyntaxKind.ClassDeclaration) {
const property = getPropertyFromBaseInterfaces(<ClassLikeDeclaration>declaration.parent, declaration.symbol.name);
const property = getPropertyOfBaseTypeDeclaration(<ClassLikeDeclaration>declaration.parent, declaration.symbol.name);
if (property) {
return getTypeOfSymbol(property);
}
}
}

function getPropertyFromBaseInterfaces(declaration: ClassLikeDeclaration, propertyName: string): Symbol {
function getPropertyOfBaseTypeDeclaration(declaration: ClassLikeDeclaration, propertyName: string): Symbol {
const property = getFirstPropertyOfTypes(getBaseTypes(<InterfaceType>getTypeOfSymbol(getSymbolOfNode(declaration))), propertyName);
if (property) {
return property;
}
const implementedTypeNodes = getClassImplementsHeritageClauseElements(declaration);
if (implementedTypeNodes) {
for (const typeRefNode of implementedTypeNodes) {
const t = getTypeFromTypeReference(typeRefNode);
if (t !== unknownType) {
for (const property of getPropertiesOfType(t)) {
if (property.valueDeclaration.flags & NodeFlags.Private) {
continue;
}
if (property.name === propertyName) {
return property;
}
}
return getFirstPropertyOfTypes(map(implementedTypeNodes, getTypeFromTypeReference), propertyName);
}
}

function getFirstPropertyOfTypes(types: Type[], propertyName: string) {
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) {
return property;
}
}
}
Expand Down Expand Up @@ -4968,7 +4974,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 @@ -6929,7 +6935,7 @@ namespace ts {
return expression;
}

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 @@ -6960,7 +6966,11 @@ namespace ts {
checkCollisionWithCapturedThisVariable(node, node);
checkBlockScopedBindingCapturedInLoop(node, symbol);

return getNarrowedTypeOfSymbol(getExportSymbolOfValueSymbolIfExported(symbol), node);
const type = getNarrowedTypeOfSymbol(getExportSymbolOfValueSymbolIfExported(symbol), node);
if (type === undefinedType || type == nullType) {
return (contextualMapper || identityMapper)(type);
}
return type;
}

function isInsideFunction(node: Node, threshold: Node): boolean {
Expand Down Expand Up @@ -7205,10 +7215,14 @@ namespace ts {
}
}

function checkNullKeyword(nullNode: Node, contextualMapper: TypeMapper) {
return (contextualMapper || identityMapper)(nullType);
}

// 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 Down Expand Up @@ -7250,7 +7264,7 @@ namespace ts {
}
}
if (declaration.kind === SyntaxKind.PropertyDeclaration) {
const type = getTypeOfBasePropertyDeclaration(declaration);
const type = getTypeOfBasePropertyDeclaration(<PropertyDeclaration>declaration);
if (type) {
return type;
}
Expand Down Expand Up @@ -7585,10 +7599,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 @@ -7681,7 +7703,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 @@ -7743,7 +7765,15 @@ namespace ts {
}
}
}
return createArrayType(elementTypes.length ? getUnionType(elementTypes) : undefinedType);
if (!elementTypes.length) {
const mapper = contextualMapper || identityMapper;
const mappedType = mapper(undefinedType);
if (mappedType === undefinedType) {
return createArrayType(undefinedType);
}
elementTypes = (<TypeReference>mappedType).typeArguments;
}
return createArrayType(getUnionType(elementTypes));
}

function isNumericName(name: DeclarationName): boolean {
Expand Down Expand Up @@ -7803,7 +7833,7 @@ namespace ts {
return links.resolvedType;
}

function checkObjectLiteral(node: ObjectLiteralExpression, contextualMapper?: TypeMapper): Type {
function checkObjectLiteral(node: ObjectLiteralExpression, contextualMapper: TypeMapper): Type {
const inDestructuringPattern = isAssignmentTarget(node);
// Grammar checking
checkGrammarObjectLiteralExpression(node, inDestructuringPattern);
Expand Down Expand Up @@ -7926,7 +7956,21 @@ namespace ts {
}
}
}
const result = propTypes.length ? getUnionType(propTypes) : undefinedType;
let result: Type;
if (!propTypes.length) {
const mapper = contextualMapper || identityMapper;
const mappedType = mapper(undefinedType);
if (mappedType === undefinedType) {
result = undefinedType;
}
else {
const resolvedType = <ResolvedType>mappedType;
result = kind === IndexKind.String ? resolvedType.stringIndexType : resolvedType.numberIndexType;
}
}
else {
result = getUnionType(propTypes);
}
typeFlags |= result.flags;
return result;
}
Expand Down Expand Up @@ -10903,7 +10947,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 @@ -10978,13 +11022,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
2 changes: 1 addition & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2291,7 +2291,7 @@ namespace ts {

/* @internal */
export interface TypeMapper {
(t: TypeParameter): Type;
(t: Type): Type;
instantiations?: Type[]; // Cache of instantiations created using this type mapper.
context?: InferenceContext; // The inference context this mapper was created from.
// Only inference mappers have this set (in createInferenceMapper).
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,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 getContainingFunction(node: Node): FunctionLikeDeclaration {
while (true) {
node = node.parent;
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/bestCommonTypeOfTuple2.types
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class F extends C { f }
class C1 implements base1 { i = "foo"; c }
>C1 : C1
>base1 : base1
>i : any
>i : string
>"foo" : string
>c : any

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,8 @@ tests/cases/conformance/es6/destructuring/destructuringParameterDeclaration2.ts(
Type 'string' is not assignable to type 'number'.
tests/cases/conformance/es6/destructuring/destructuringParameterDeclaration2.ts(46,13): error TS2463: A binding pattern parameter cannot be optional in an implementation signature.
tests/cases/conformance/es6/destructuring/destructuringParameterDeclaration2.ts(47,13): error TS2463: A binding pattern parameter cannot be optional in an implementation signature.
tests/cases/conformance/es6/destructuring/destructuringParameterDeclaration2.ts(55,7): error TS2420: Class 'C4' incorrectly implements interface 'F2'.
Types of property 'd4' are incompatible.
Type '({x, y, c}: { x: any; y: any; c: any; }) => void' is not assignable to type '({x, y, z}?: { x: any; y: any; z: any; }) => any'.
Types of parameters '__0' and '__0' are incompatible.
Type '{ x: any; y: any; c: any; }' is not assignable to type '{ x: any; y: any; z: any; }'.
Property 'z' is missing in type '{ x: any; y: any; c: any; }'.
tests/cases/conformance/es6/destructuring/destructuringParameterDeclaration2.ts(56,8): error TS2463: A binding pattern parameter cannot be optional in an implementation signature.
tests/cases/conformance/es6/destructuring/destructuringParameterDeclaration2.ts(57,15): error TS2459: Type '{ x: any; y: any; z: any; }' has no property 'c' and no string index signature.
tests/cases/conformance/es6/destructuring/destructuringParameterDeclaration2.ts(65,18): error TS2300: Duplicate identifier 'number'.
tests/cases/conformance/es6/destructuring/destructuringParameterDeclaration2.ts(65,26): error TS2300: Duplicate identifier 'number'.
tests/cases/conformance/es6/destructuring/destructuringParameterDeclaration2.ts(65,34): error TS2300: Duplicate identifier 'number'.
Expand Down Expand Up @@ -175,17 +170,12 @@ tests/cases/conformance/es6/destructuring/destructuringParameterDeclaration2.ts(
}

class C4 implements F2 {
~~
!!! error TS2420: Class 'C4' incorrectly implements interface 'F2'.
!!! error TS2420: Types of property 'd4' are incompatible.
!!! error TS2420: Type '({x, y, c}: { x: any; y: any; c: any; }) => void' is not assignable to type '({x, y, z}?: { x: any; y: any; z: any; }) => any'.
!!! error TS2420: Types of parameters '__0' and '__0' are incompatible.
!!! error TS2420: Type '{ x: any; y: any; c: any; }' is not assignable to type '{ x: any; y: any; z: any; }'.
!!! error TS2420: Property 'z' is missing in type '{ x: any; y: any; c: any; }'.
d3([a, b, c]?) { } // Error, binding pattern can't be optional in implementation signature
~~~~~~~~~~
!!! error TS2463: A binding pattern parameter cannot be optional in an implementation signature.
d4({x, y, c}) { }
~
!!! error TS2459: Type '{ x: any; y: any; z: any; }' has no property 'c' and no string index signature.
e0([a, b, q]) { }
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
tests/cases/conformance/expressions/contextualTyping/implementedPropertyContextualTyping1.ts(21,3): error TS2322: Type 'number' is not assignable to type 'string'.
tests/cases/conformance/expressions/contextualTyping/implementedPropertyContextualTyping1.ts(24,3): error TS2322: Type 'number' is not assignable to type 'string'.
tests/cases/conformance/expressions/contextualTyping/implementedPropertyContextualTyping1.ts(28,3): error TS2322: Type 'number' is not assignable to type 'string'.
tests/cases/conformance/expressions/contextualTyping/implementedPropertyContextualTyping1.ts(31,3): error TS2322: Type 'number' is not assignable to type 'string'.


==== tests/cases/conformance/expressions/contextualTyping/implementedPropertyContextualTyping1.ts (4 errors) ====
interface Event {
time: number;
}
interface Base {
superHandle: (e: Event) => number;
}
interface Listener extends Base {
handle: (e: Event) => void;
}
interface Ringer {
ring: (times: number) => void;
}

abstract class Watcher {
abstract watch(e: Event): number;
}

class Alarm extends Watcher implements Listener, Ringer {
str: string;
handle = e => {
this.str = e.time; // error
~~~~~~~~
!!! error TS2322: Type 'number' is not assignable to type 'string'.
}
superHandle = e => {
this.str = e.time; // error
~~~~~~~~
!!! error TS2322: Type 'number' is not assignable to type 'string'.
return e.time;
}
ring(times) {
this.str = times; // error
~~~~~~~~
!!! error TS2322: Type 'number' is not assignable to type 'string'.
}
watch(e) {
this.str = e.time; // error
~~~~~~~~
!!! error TS2322: Type 'number' is not assignable to type 'string'.
return e.time;
}
}
Loading

0 comments on commit 055ae8c

Please sign in to comment.