Skip to content

Contextually type inherited properties (WIP) #10610

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

Closed
wants to merge 9 commits into from
56 changes: 45 additions & 11 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3051,6 +3051,14 @@ namespace ts {
return addOptionality(getTypeFromTypeNode(declaration.type), /*optional*/ declaration.questionToken && includeOptionality);
}

// use type from base class' property if present
if (declaration.kind === SyntaxKind.PropertyDeclaration) {
const type = getTypeOfBasePropertyDeclaration(<PropertyDeclaration>declaration);
if (type) {
return type;
}
}

if (declaration.kind === SyntaxKind.Parameter) {
const func = <FunctionLikeDeclaration>declaration.parent;
// For a parameter of a set accessor, use the type of the get accessor if one is present
Expand Down Expand Up @@ -3434,6 +3442,20 @@ namespace ts {
return unknownType;
}

function getTypeOfBasePropertyDeclaration(declaration: PropertyDeclaration) {
if (declaration.parent.kind === SyntaxKind.ClassDeclaration) {
const parent = <ClassLikeDeclaration>declaration.parent;
const baseClasses = getBaseTypes(<InterfaceType>getDeclaredTypeOfSymbol(getSymbolOfNode(parent)));
const implementsNode = getClassImplementsHeritageClauseElements(parent) || ([] as NodeArray<ExpressionWithTypeArguments>);
const allBases = getIntersectionType(baseClasses.concat(map(implementsNode, getTypeFromTypeReference)));
const baseProperty = getPropertyOfType(allBases, declaration.symbol.name);
if (baseProperty) {
return getTypeOfSymbol(baseProperty);
}
}
return undefined;
}

function getTargetType(type: ObjectType): Type {
return type.flags & TypeFlags.Reference ? (<TypeReference>type).target : type;
}
Expand Down Expand Up @@ -5900,7 +5922,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 @@ -5933,8 +5955,8 @@ namespace ts {
return !node.typeParameters && areAllParametersUntyped && !isNullaryArrow;
}

function isContextSensitiveFunctionOrObjectLiteralMethod(func: Node): func is FunctionExpression | MethodDeclaration {
return (isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) && isContextSensitiveFunctionLikeDeclaration(func);
function isContextSensitiveFunctionOrMethod(func: Node): func is FunctionExpression | MethodDeclaration {
return (isFunctionExpressionOrArrowFunction(func) || isMethod(func)) && isContextSensitiveFunctionLikeDeclaration(func);
}

function getTypeWithoutSignatures(type: Type): Type {
Expand Down Expand Up @@ -9376,7 +9398,7 @@ namespace ts {
}

function getContextualThisParameter(func: FunctionLikeDeclaration): Symbol {
if (isContextSensitiveFunctionOrObjectLiteralMethod(func) && func.kind !== SyntaxKind.ArrowFunction) {
if (isContextSensitiveFunctionOrMethod(func) && func.kind !== SyntaxKind.ArrowFunction) {
const contextualSignature = getContextualSignature(func);
if (contextualSignature) {
return contextualSignature.thisParameter;
Expand All @@ -9389,7 +9411,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 (isContextSensitiveFunctionOrObjectLiteralMethod(func)) {
if (isContextSensitiveFunctionOrMethod(func)) {
const iife = getImmediatelyInvokedFunctionExpression(func);
if (iife) {
const indexOfParameter = indexOf(func.parameters, parameter);
Expand Down Expand Up @@ -9443,6 +9465,12 @@ namespace ts {
if (declaration.type) {
return getTypeFromTypeNode(declaration.type);
}
if (declaration.kind === SyntaxKind.PropertyDeclaration) {
const type = getTypeOfBasePropertyDeclaration(<PropertyDeclaration>declaration);
if (type) {
return type;
}
}
if (declaration.kind === SyntaxKind.Parameter) {
const type = getContextuallyTypedParameterType(<ParameterDeclaration>declaration);
if (type) {
Expand Down Expand Up @@ -9816,16 +9844,22 @@ namespace ts {
}

function getContextualSignatureForFunctionLikeDeclaration(node: FunctionLikeDeclaration): Signature {
// Only function expressions, arrow functions, and object literal methods are contextually typed.
return isFunctionExpressionOrArrowFunction(node) || isObjectLiteralMethod(node)
// Only function expressions, arrow functions, and methods are contextually typed.
return isFunctionExpressionOrArrowFunction(node) || isMethod(node)
? getContextualSignature(<FunctionExpression>node)
: undefined;
}

function getContextualTypeForFunctionLikeDeclaration(node: FunctionExpression | MethodDeclaration) {
return isObjectLiteralMethod(node) ?
getContextualTypeForObjectLiteralMethod(node) :
getApparentTypeOfContextualType(node);
if (isFunctionExpressionOrArrowFunction(node)) {
return getApparentTypeOfContextualType(node);
}
else if (isObjectLiteralMethod(node)) {
return getContextualTypeForObjectLiteralMethod(node);
}
else if (isMethod(node)) {
return getTypeOfBasePropertyDeclaration(node);
}
}

// Return the contextual signature for a given expression node. A contextual type provides a
Expand All @@ -9834,7 +9868,7 @@ 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));
Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isMethod(node));
const type = getContextualTypeForFunctionLikeDeclaration(node);
if (!type) {
return undefined;
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,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
20 changes: 6 additions & 14 deletions tests/baselines/reference/abstractPropertyNegative.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,11 @@ tests/cases/compiler/abstractPropertyNegative.ts(13,7): error TS2515: Non-abstra
tests/cases/compiler/abstractPropertyNegative.ts(15,5): error TS1244: Abstract methods can only appear within an abstract class.
tests/cases/compiler/abstractPropertyNegative.ts(16,37): error TS1005: '{' expected.
tests/cases/compiler/abstractPropertyNegative.ts(19,1): error TS2450: Left-hand side of assignment expression cannot be a constant or a read-only property.
tests/cases/compiler/abstractPropertyNegative.ts(24,7): error TS2415: Class 'WrongTypePropertyImpl' incorrectly extends base class 'WrongTypeProperty'.
Types of property 'num' are incompatible.
Type 'string' is not assignable to type 'number'.
tests/cases/compiler/abstractPropertyNegative.ts(25,5): error TS2322: Type 'string' is not assignable to type 'number'.
tests/cases/compiler/abstractPropertyNegative.ts(30,7): error TS2415: Class 'WrongTypeAccessorImpl' incorrectly extends base class 'WrongTypeAccessor'.
Types of property 'num' are incompatible.
Type 'string' is not assignable to type 'number'.
tests/cases/compiler/abstractPropertyNegative.ts(33,7): error TS2415: Class 'WrongTypeAccessorImpl2' incorrectly extends base class 'WrongTypeAccessor'.
Types of property 'num' are incompatible.
Type 'string' is not assignable to type 'number'.
tests/cases/compiler/abstractPropertyNegative.ts(34,5): error TS2322: Type 'string' is not assignable to type 'number'.
tests/cases/compiler/abstractPropertyNegative.ts(38,18): error TS2676: Accessors must both be abstract or non-abstract.
tests/cases/compiler/abstractPropertyNegative.ts(39,9): error TS2676: Accessors must both be abstract or non-abstract.
tests/cases/compiler/abstractPropertyNegative.ts(40,9): error TS2676: Accessors must both be abstract or non-abstract.
Expand Down Expand Up @@ -65,11 +61,9 @@ tests/cases/compiler/abstractPropertyNegative.ts(41,18): error TS2676: Accessors
abstract num: number;
}
class WrongTypePropertyImpl extends WrongTypeProperty {
~~~~~~~~~~~~~~~~~~~~~
!!! error TS2415: Class 'WrongTypePropertyImpl' incorrectly extends base class 'WrongTypeProperty'.
!!! error TS2415: Types of property 'num' are incompatible.
!!! error TS2415: Type 'string' is not assignable to type 'number'.
num = "nope, wrong";
~~~~~~~~~~~~~~~~~~~~
!!! error TS2322: Type 'string' is not assignable to type 'number'.
}
abstract class WrongTypeAccessor {
abstract get num(): number;
Expand All @@ -82,11 +76,9 @@ tests/cases/compiler/abstractPropertyNegative.ts(41,18): error TS2676: Accessors
get num() { return "nope, wrong"; }
}
class WrongTypeAccessorImpl2 extends WrongTypeAccessor {
~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2415: Class 'WrongTypeAccessorImpl2' incorrectly extends base class 'WrongTypeAccessor'.
!!! error TS2415: Types of property 'num' are incompatible.
!!! error TS2415: Type 'string' is not assignable to type 'number'.
num = "nope, wrong";
~~~~~~~~~~~~~~~~~~~~
!!! error TS2322: Type 'string' is not assignable to type 'number'.
}

abstract class AbstractAccessorMismatch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class D extends C<IHasVisualizationModel> {
>IHasVisualizationModel : IHasVisualizationModel

x = moduleA;
>x : typeof moduleA
>x : IHasVisualizationModel
>moduleA : typeof moduleA
}
=== tests/cases/compiler/aliasUsageInTypeArgumentOfExtendsClause_backbone.ts ===
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/bestCommonTypeOfTuple2.types
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ class F extends C { f }
class C1 implements base1 { i = "foo"; c }
>C1 : C1
>base1 : base1
>i : string
>i : any
>"foo" : string
>c : any

class D1 extends C1 { i = "bar"; d }
>D1 : D1
>C1 : C1
>i : string
>i : any
>"bar" : string
>d : any

Expand Down
5 changes: 4 additions & 1 deletion tests/baselines/reference/convertKeywordsYes.errors.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
tests/cases/compiler/convertKeywordsYes.ts(175,5): error TS2322: Type 'number' is not assignable to type 'Function'.
tests/cases/compiler/convertKeywordsYes.ts(292,11): error TS1213: Identifier expected. 'implements' is a reserved word in strict mode. Class definitions are automatically in strict mode.
tests/cases/compiler/convertKeywordsYes.ts(293,11): error TS1213: Identifier expected. 'interface' is a reserved word in strict mode. Class definitions are automatically in strict mode.
tests/cases/compiler/convertKeywordsYes.ts(294,11): error TS1213: Identifier expected. 'let' is a reserved word in strict mode. Class definitions are automatically in strict mode.
Expand All @@ -9,7 +10,7 @@ tests/cases/compiler/convertKeywordsYes.ts(301,11): error TS1213: Identifier exp
tests/cases/compiler/convertKeywordsYes.ts(303,11): error TS1213: Identifier expected. 'yield' is a reserved word in strict mode. Class definitions are automatically in strict mode.


==== tests/cases/compiler/convertKeywordsYes.ts (9 errors) ====
==== tests/cases/compiler/convertKeywordsYes.ts (10 errors) ====
// reserved ES5 future in strict mode

var constructor = 0;
Expand Down Expand Up @@ -185,6 +186,8 @@ tests/cases/compiler/convertKeywordsYes.ts(303,11): error TS1213: Identifier exp

class bigClass {
public "constructor" = 0;
~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2322: Type 'number' is not assignable to type 'Function'.
public any = 0;
public boolean = 0;
public implements = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,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; z: any; }' is not assignable to type '{ x: any; y: any; c: any; }'.
Property 'c' is missing in type '{ x: any; y: any; z: 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 @@ -159,17 +154,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; z: any; }' is not assignable to type '{ x: any; y: any; c: any; }'.
!!! error TS2420: Property 'c' is missing in type '{ x: any; y: any; z: 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,77 @@
tests/cases/conformance/expressions/contextualTyping/implementedPropertyContextualTyping1.ts(25,9): error TS2322: Type 'number' is not assignable to type 'string'.
tests/cases/conformance/expressions/contextualTyping/implementedPropertyContextualTyping1.ts(28,9): error TS2322: Type 'number' is not assignable to type 'string'.
tests/cases/conformance/expressions/contextualTyping/implementedPropertyContextualTyping1.ts(32,9): error TS2322: Type 'number' is not assignable to type 'string'.
tests/cases/conformance/expressions/contextualTyping/implementedPropertyContextualTyping1.ts(35,9): error TS2322: Type 'number' is not assignable to type 'string'.
tests/cases/conformance/expressions/contextualTyping/implementedPropertyContextualTyping1.ts(54,7): error TS7006: Parameter 'n' implicitly has an 'any' type.


==== tests/cases/conformance/expressions/contextualTyping/implementedPropertyContextualTyping1.ts (5 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;
}
interface StringLiteral {
literal(): "A";
literals: "A" | "B";
}

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

class Alarm extends Watcher implements Listener, Ringer, StringLiteral {
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;
}
literal() {
return "A"; // ok: "A" is assignable to "A"
}
literals = "A"; // ok: "A" is assignable to "A" | "B"
}

interface A {
q(n: string): void;
}
interface B {
q(n: number): void;
}
class C {
r: number;
}
class Multiple extends C implements A, B {
q(n) { // error, n is implicitly any because A.q and B.q exist
~
!!! error TS7006: Parameter 'n' implicitly has an 'any' type.
n.length; // and the unioned type has no signature
n.toFixed; // (even though the constituent types each do)
}
}

Loading