Compare commits

...

8 commits

Author SHA1 Message Date
Nathan Shively-Sanders 17173a6e6c Add test for split type/contextual type annotation 2017-02-13 12:38:18 -08:00
Nathan Shively-Sanders 32e75f71a3 Separate specfication of type and contextual type
This is basically a bad idea, but it kind of works and might provide
ideas for future work.
2017-02-13 11:04:40 -08:00
Nathan Shively-Sanders f3a12d104f Check return types 2017-02-09 11:37:19 -08:00
Nathan Shively-Sanders 6fd5e1bab8 Merge branch 'master' into vue-hacks-WIP 2017-02-09 11:10:12 -08:00
Nathan Shively-Sanders 56d18353c6 Parse return types and add them to the Vue test 2017-02-09 09:23:45 -08:00
Nathan Shively-Sanders 613aead278 Add mini-Vue test 2017-02-08 16:03:09 -08:00
Nathan Shively-Sanders 9558c210f4 Defer checking:this param in object literal method
This is completely ad-hoc, but it allows you to write `this: typeof o`
in an object literal and get the entire type.
2017-02-08 15:09:29 -08:00
Nathan Shively-Sanders 99743dfdd5 Add basic test case 2017-02-07 12:52:48 -08:00
7 changed files with 352 additions and 17 deletions

View file

@ -3504,7 +3504,12 @@ namespace ts {
return isPrivateWithinAmbient(memberDeclaration);
}
function getTypeOfVariableOrParameterOrProperty(symbol: Symbol): Type {
function getTypeOfVariableOrParameterOrProperty(symbol: Symbol, crazy?: boolean): Type {
const declaration = symbol.valueDeclaration;
if (crazy && declaration.kind === SyntaxKind.Parameter && (declaration as ParameterDeclaration).contextualType) {
// TODO: Doesn't widen of course
return getTypeFromTypeNode((declaration as ParameterDeclaration).contextualType);
}
const links = getSymbolLinks(symbol);
if (!links.type) {
// Handle prototype property
@ -3512,7 +3517,6 @@ namespace ts {
return links.type = getTypeOfPrototypeProperty(symbol);
}
// Handle catch clause variables
const declaration = symbol.valueDeclaration;
if (isCatchClauseVariableDeclarationOrBindingElement(declaration)) {
return links.type = anyType;
}
@ -3695,13 +3699,16 @@ namespace ts {
return links.type;
}
function getTypeOfInstantiatedSymbol(symbol: Symbol): Type {
function getTypeOfInstantiatedSymbol(symbol: Symbol, crazy?: boolean): Type {
const links = getSymbolLinks(symbol);
if (crazy) {
return instantiateType(getTypeOfSymbol(links.target, crazy), links.mapper);
}
if (!links.type) {
if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) {
return unknownType;
}
let type = instantiateType(getTypeOfSymbol(links.target), links.mapper);
let type = instantiateType(getTypeOfSymbol(links.target, crazy), links.mapper);
if (!popTypeResolution()) {
type = reportCircularityError(symbol);
}
@ -3725,12 +3732,12 @@ namespace ts {
return anyType;
}
function getTypeOfSymbol(symbol: Symbol): Type {
function getTypeOfSymbol(symbol: Symbol, crazy?: boolean): Type {
if (symbol.flags & SymbolFlags.Instantiated) {
return getTypeOfInstantiatedSymbol(symbol);
return getTypeOfInstantiatedSymbol(symbol, crazy);
}
if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) {
return getTypeOfVariableOrParameterOrProperty(symbol);
return getTypeOfVariableOrParameterOrProperty(symbol, crazy);
}
if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) {
return getTypeOfFuncClassEnumModule(symbol);
@ -4937,6 +4944,10 @@ namespace ts {
return type.resolvedApparentType || (type.resolvedApparentType = getTypeWithThisArgument(type, type));
}
function getApparentTypeOfReturnType(type: ReturnType) {
return type.resolvedApparentType || (type.resolvedApparentType = getReturnType(getApparentType(type.type)));
}
/**
* For a type parameter, return the base constraint of the type parameter. For the string, number,
* boolean, and symbol primitive types, return the corresponding object types. Otherwise return the
@ -4945,6 +4956,7 @@ namespace ts {
function getApparentType(type: Type): Type {
const t = type.flags & TypeFlags.TypeVariable ? getBaseConstraintOfType(<TypeVariable>type) || emptyObjectType : type;
return t.flags & TypeFlags.Intersection ? getApparentTypeOfIntersectionType(<IntersectionType>t) :
t.flags & TypeFlags.Return ? getApparentTypeOfReturnType(<ReturnType>t) :
t.flags & TypeFlags.StringLike ? globalStringType :
t.flags & TypeFlags.NumberLike ? globalNumberType :
t.flags & TypeFlags.BooleanLike ? globalBooleanType :
@ -6188,6 +6200,29 @@ namespace ts {
return links.resolvedType;
}
function getTypeFromReturnOperatorNode(node: ReturnOperatorNode) {
const links = getNodeLinks(node);
if (!links.resolvedType) {
links.resolvedType = getReturnType(getTypeFromTypeNode(node.type));
}
return links.resolvedType;
}
function getReturnType(type: Type): Type {
if (maybeTypeOfKind(type, TypeFlags.TypeVariable)) {
return getReturnTypeForGenericType(type as TypeVariable | UnionOrIntersectionType);
}
return getUnionType(map(getSignaturesOfType(type, SignatureKind.Call), getReturnTypeOfSignature));
}
function getReturnTypeForGenericType(type: TypeVariable | UnionOrIntersectionType): Type {
if (!type.resolvedReturnType) {
type.resolvedReturnType = <ReturnType>createType(TypeFlags.Return);
type.resolvedReturnType.type = type;
}
return type.resolvedReturnType;
}
function createIndexedAccessType(objectType: Type, indexType: Type) {
const type = <IndexedAccessType>createType(TypeFlags.IndexedAccess);
type.objectType = objectType;
@ -6595,7 +6630,9 @@ namespace ts {
case SyntaxKind.JSDocFunctionType:
return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node);
case SyntaxKind.TypeOperator:
return getTypeFromTypeOperatorNode(<TypeOperatorNode>node);
return getTypeFromTypeOperatorNode(node as TypeOperatorNode);
case SyntaxKind.ReturnOperator:
return getTypeFromReturnOperatorNode(node as ReturnOperatorNode);
case SyntaxKind.IndexedAccessType:
return getTypeFromIndexedAccessTypeNode(<IndexedAccessTypeNode>node);
case SyntaxKind.MappedType:
@ -6962,6 +6999,9 @@ namespace ts {
if (type.flags & TypeFlags.Index) {
return getIndexType(instantiateType((<IndexType>type).type, mapper));
}
if (type.flags & TypeFlags.Return) {
return getReturnType(instantiateType((<ReturnType>type).type, mapper));
}
if (type.flags & TypeFlags.IndexedAccess) {
return getIndexedAccessType(instantiateType((<IndexedAccessType>type).objectType, mapper), instantiateType((<IndexedAccessType>type).indexType, mapper));
}
@ -9040,7 +9080,7 @@ namespace ts {
// We're inferring from some source type S to a mapped type { [P in T]: X }, where T is a type
// parameter. Infer from 'keyof S' to T and infer from a union of each property type in S to X.
inferFromTypes(getIndexType(source), constraintType);
inferFromTypes(getUnionType(map(getPropertiesOfType(source), getTypeOfSymbol)), getTemplateTypeFromMappedType(<MappedType>target));
inferFromTypes(getUnionType(map(getPropertiesOfType(source), p => getTypeOfSymbol(p))), getTemplateTypeFromMappedType(<MappedType>target));
return;
}
}
@ -11229,7 +11269,7 @@ namespace ts {
const argIndex = indexOf(args, arg);
if (argIndex >= 0) {
const signature = getResolvedOrAnySignature(callTarget);
return getTypeAtPosition(signature, argIndex);
return getSpecialContextualTypeAtPosition(signature, argIndex);
}
return undefined;
}
@ -13064,7 +13104,7 @@ namespace ts {
// If the effective argument is 'undefined', then it is an argument that is present but is synthetic.
if (arg === undefined || arg.kind !== SyntaxKind.OmittedExpression) {
// Check spread elements against rest type (from arity check we know spread argument corresponds to a rest parameter)
const paramType = getTypeAtPosition(signature, i);
const paramType = getSpecialContextualTypeAtPosition(signature, i);
let argType = getEffectiveArgumentType(node, i);
// If the effective argument type is 'undefined', there is no synthetic type
@ -14088,8 +14128,8 @@ namespace ts {
}
}
function getTypeOfParameter(symbol: Symbol) {
const type = getTypeOfSymbol(symbol);
function getTypeOfParameter(symbol: Symbol, crazy?: boolean) {
const type = getTypeOfSymbol(symbol, crazy);
if (strictNullChecks) {
const declaration = symbol.valueDeclaration;
if (declaration && (<VariableLikeDeclaration>declaration).initializer) {
@ -14099,6 +14139,12 @@ namespace ts {
return type;
}
function getSpecialContextualTypeAtPosition(signature: Signature, pos: number): Type {
return signature.hasRestParameter ?
pos < signature.parameters.length - 1 ? getTypeOfParameter(signature.parameters[pos], /*crazy*/ true) : getRestTypeOfSignature(signature) :
pos < signature.parameters.length ? getTypeOfParameter(signature.parameters[pos], /*crazy*/ true) : anyType;
}
function getTypeAtPosition(signature: Signature, pos: number): Type {
return signature.hasRestParameter ?
pos < signature.parameters.length - 1 ? getTypeOfParameter(signature.parameters[pos]) : getRestTypeOfSignature(signature) :
@ -15751,7 +15797,18 @@ namespace ts {
checkTypeParameters(node.typeParameters);
forEach(node.parameters, checkParameter);
const isObjectLiteralMethodWithThis = isObjectLiteralMethod(node) &&
node.parameters &&
node.parameters.length &&
parameterIsThisKeyword(node.parameters[0]);
if (isObjectLiteralMethodWithThis) {
// defer checking on first one
checkNodeDeferred(node.parameters[0]); // .. this will surely work ..
forEach(node.parameters.slice(1), checkParameter);
}
else {
forEach(node.parameters, checkParameter);
}
if (node.type) {
checkSourceElement(node.type);

View file

@ -89,6 +89,7 @@ namespace ts {
visitNode(cbNode, (<VariableLikeDeclaration>node).name) ||
visitNode(cbNode, (<VariableLikeDeclaration>node).questionToken) ||
visitNode(cbNode, (<VariableLikeDeclaration>node).type) ||
visitNode(cbNode, (<ParameterDeclaration>node).contextualType) ||
visitNode(cbNode, (<VariableLikeDeclaration>node).initializer);
case SyntaxKind.FunctionType:
case SyntaxKind.ConstructorType:
@ -138,6 +139,8 @@ namespace ts {
case SyntaxKind.ParenthesizedType:
case SyntaxKind.TypeOperator:
return visitNode(cbNode, (<ParenthesizedTypeNode | TypeOperatorNode>node).type);
case SyntaxKind.ReturnOperator:
return visitNode(cbNode, (<ReturnOperatorNode>node).type);
case SyntaxKind.IndexedAccessType:
return visitNode(cbNode, (<IndexedAccessTypeNode>node).objectType) ||
visitNode(cbNode, (<IndexedAccessTypeNode>node).indexType);
@ -2115,7 +2118,6 @@ namespace ts {
if (parseOptional(SyntaxKind.ColonToken)) {
return parseType();
}
return undefined;
}
@ -2152,6 +2154,9 @@ namespace ts {
node.questionToken = parseOptionalToken(SyntaxKind.QuestionToken);
node.type = parseParameterType();
if (parseOptional(SyntaxKind.AsKeyword)) {
node.contextualType = parseType();
}
node.initializer = parseBindingElementInitializer(/*inParameter*/ true);
// Do not check for initializers in an ambient context for parameters. This is not
@ -2611,7 +2616,23 @@ namespace ts {
return type;
}
function parseTypeOperator(operator: SyntaxKind.KeyOfKeyword) {
function parseReturnOperator(operator: SyntaxKind.ReturnKeyword): TypeNode {
const node = <ReturnOperatorNode>createNode(SyntaxKind.ReturnOperator);
parseExpected(operator);
node.operator = operator;
node.type = parseReturnOperatorOrHigher();
return finishNode(node);
}
function parseReturnOperatorOrHigher(): TypeNode {
switch (token()) {
case SyntaxKind.ReturnKeyword:
return parseReturnOperator(SyntaxKind.ReturnKeyword);
}
return parseTypeOperatorOrHigher();
}
function parseTypeOperator(operator: SyntaxKind.KeyOfKeyword) {
const node = <TypeOperatorNode>createNode(SyntaxKind.TypeOperator);
parseExpected(operator);
node.operator = operator;
@ -2644,7 +2665,7 @@ namespace ts {
}
function parseIntersectionTypeOrHigher(): TypeNode {
return parseUnionOrIntersectionType(SyntaxKind.IntersectionType, parseTypeOperatorOrHigher, SyntaxKind.AmpersandToken);
return parseUnionOrIntersectionType(SyntaxKind.IntersectionType, parseReturnOperatorOrHigher, SyntaxKind.AmpersandToken);
}
function parseUnionTypeOrHigher(): TypeNode {

View file

@ -238,6 +238,7 @@
ParenthesizedType,
ThisType,
TypeOperator,
ReturnOperator,
IndexedAccessType,
MappedType,
LiteralType,
@ -665,6 +666,7 @@
name: BindingName; // Declared parameter name
questionToken?: QuestionToken; // Present on optional parameter
type?: TypeNode; // Optional type annotation
contextualType?: TypeNode; // Super-optional contextual type annotation
initializer?: Expression; // Optional initializer
}
@ -917,6 +919,12 @@
type: TypeNode;
}
export interface ReturnOperatorNode extends TypeNode {
kind: SyntaxKind.ReturnOperator;
operator: SyntaxKind.ReturnKeyword;
type: TypeNode;
}
export interface IndexedAccessTypeNode extends TypeNode {
kind: SyntaxKind.IndexedAccessType;
objectType: TypeNode;
@ -2823,6 +2831,7 @@
/* @internal */
ContainsAnyFunctionType = 1 << 23, // Type is or contains object literal type
NonPrimitive = 1 << 24, // intrinsic object type
Return = 1 << 25, // Return type of callable (never for uncallable types)
/* @internal */
Nullable = Undefined | Null,
@ -2963,6 +2972,8 @@
/* @internal */
resolvedIndexType: IndexType;
/* @internal */
resolvedReturnType: ReturnType;
/* @internal */
resolvedBaseConstraint: Type;
/* @internal */
couldContainTypeVariables: boolean;
@ -3030,6 +3041,8 @@
resolvedBaseConstraint: Type;
/* @internal */
resolvedIndexType: IndexType;
/* @internal */
resolvedReturnType: ReturnType;
}
// Type parameters (TypeFlags.TypeParameter)
@ -3056,6 +3069,13 @@
type: TypeVariable | UnionOrIntersectionType;
}
// return T types (TypeFlags.Return)
export interface ReturnType extends Type {
type: TypeVariable | UnionOrIntersectionType;
/* @internal */
resolvedApparentType: Type;
}
export const enum SignatureKind {
Call,
Construct,

View file

@ -0,0 +1,33 @@
//// [recursion.ts]
let o = {
p: 12,
m(this: typeof o) {
let x = this.m(); // x: number
let y = this.p; // y: number
return this.p;
},
m2() {
return this.m() // this: any since it has no annotation
}
}
let x = o.m() // x: number
let y = o.m2() // y: any
let p = o.p // p: number
//// [recursion.js]
var o = {
p: 12,
m: function () {
var x = this.m(); // x: number
var y = this.p; // y: number
return this.p;
},
m2: function () {
return this.m(); // this: any since it has no annotation
}
};
var x = o.m(); // x: number
var y = o.m2(); // y: any
var p = o.p; // p: number

View file

@ -0,0 +1,55 @@
=== tests/cases/compiler/recursion.ts ===
let o = {
>o : Symbol(o, Decl(recursion.ts, 0, 3))
p: 12,
>p : Symbol(p, Decl(recursion.ts, 0, 9))
m(this: typeof o) {
>m : Symbol(m, Decl(recursion.ts, 1, 10))
>this : Symbol(this, Decl(recursion.ts, 2, 6))
>o : Symbol(o, Decl(recursion.ts, 0, 3))
let x = this.m(); // x: number
>x : Symbol(x, Decl(recursion.ts, 3, 11))
>this.m : Symbol(m, Decl(recursion.ts, 1, 10))
>this : Symbol(this, Decl(recursion.ts, 2, 6))
>m : Symbol(m, Decl(recursion.ts, 1, 10))
let y = this.p; // y: number
>y : Symbol(y, Decl(recursion.ts, 4, 11))
>this.p : Symbol(p, Decl(recursion.ts, 0, 9))
>this : Symbol(this, Decl(recursion.ts, 2, 6))
>p : Symbol(p, Decl(recursion.ts, 0, 9))
return this.p;
>this.p : Symbol(p, Decl(recursion.ts, 0, 9))
>this : Symbol(this, Decl(recursion.ts, 2, 6))
>p : Symbol(p, Decl(recursion.ts, 0, 9))
},
m2() {
>m2 : Symbol(m2, Decl(recursion.ts, 6, 6))
return this.m() // this: any since it has no annotation
}
}
let x = o.m() // x: number
>x : Symbol(x, Decl(recursion.ts, 12, 3))
>o.m : Symbol(m, Decl(recursion.ts, 1, 10))
>o : Symbol(o, Decl(recursion.ts, 0, 3))
>m : Symbol(m, Decl(recursion.ts, 1, 10))
let y = o.m2() // y: any
>y : Symbol(y, Decl(recursion.ts, 13, 3))
>o.m2 : Symbol(m2, Decl(recursion.ts, 6, 6))
>o : Symbol(o, Decl(recursion.ts, 0, 3))
>m2 : Symbol(m2, Decl(recursion.ts, 6, 6))
let p = o.p // p: number
>p : Symbol(p, Decl(recursion.ts, 14, 3))
>o.p : Symbol(p, Decl(recursion.ts, 0, 9))
>o : Symbol(o, Decl(recursion.ts, 0, 3))
>p : Symbol(p, Decl(recursion.ts, 0, 9))

View file

@ -0,0 +1,64 @@
=== tests/cases/compiler/recursion.ts ===
let o = {
>o : { p: number; m(this: any): number; m2(): any; }
>{ p: 12, m(this: typeof o) { let x = this.m(); // x: number let y = this.p; // y: number return this.p; }, m2() { return this.m() // this: any since it has no annotation }} : { p: number; m(this: any): number; m2(): any; }
p: 12,
>p : number
>12 : 12
m(this: typeof o) {
>m : (this: { p: number; m(this: any): number; m2(): any; }) => number
>this : { p: number; m(this: any): number; m2(): any; }
>o : { p: number; m(this: any): number; m2(): any; }
let x = this.m(); // x: number
>x : number
>this.m() : number
>this.m : (this: { p: number; m(this: any): number; m2(): any; }) => number
>this : { p: number; m(this: any): number; m2(): any; }
>m : (this: { p: number; m(this: any): number; m2(): any; }) => number
let y = this.p; // y: number
>y : number
>this.p : number
>this : { p: number; m(this: any): number; m2(): any; }
>p : number
return this.p;
>this.p : number
>this : { p: number; m(this: any): number; m2(): any; }
>p : number
},
m2() {
>m2 : () => any
return this.m() // this: any since it has no annotation
>this.m() : any
>this.m : any
>this : any
>m : any
}
}
let x = o.m() // x: number
>x : number
>o.m() : number
>o.m : (this: { p: number; m(this: any): number; m2(): any; }) => number
>o : { p: number; m(this: any): number; m2(): any; }
>m : (this: { p: number; m(this: any): number; m2(): any; }) => number
let y = o.m2() // y: any
>y : any
>o.m2() : any
>o.m2 : () => any
>o : { p: number; m(this: any): number; m2(): any; }
>m2 : () => any
let p = o.p // p: number
>p : number
>o.p : number
>o : { p: number; m(this: any): number; m2(): any; }
>p : number

View file

@ -0,0 +1,85 @@
// TODO: Assignability error when the contextual type of data is `U & V`
declare function create<U, V, W>(
x: {data: U, methods: V, properties: W} as
{
data: U,
methods: { [s: string]: (this: U & V & Propertise<W>, ...args: any[]) => any },
properties: { [s: string]: (this: U & V & Propertise<W>, ...args: any[]) => any }
}): U & V & Propertise<W>;
let vue = create({
data: { x: 1 },
methods: {
m() { return 12 /*this.x*/ },
m2() { return this.m() },
m3() { return this.p },
},
properties: {
p() { return 'foo' /*this.x*/ },
p2() { return this.p },
p3() { return this.m() },
}
});
/**
Problem: you need to fix U and V but also make them provide a contextual type that includes the *whole* type.
But transformed, so the *whole* *transformed* type.
So here's an idea: an in and out type. One is used for type inference (to)
and the other is used for contextual typing. Plus maybe type inference from? Not sure about that one.
*/
type Propertise<T> = { [K in keyof T]: return T[K] };
type ExplicitVue<T extends { data, methods, properties }> = T['data'] & T['methods'] & Propertise<T['properties']>;
let options = {
data: {
a: 12,
},
methods: {
m1(this: ExplicitVue<typeof options>) {
this.a;
this.m2();
return this.a + this.p.length;
},
m2(this: ExplicitVue<typeof options>) {
return this.m1();
}
},
properties: {
p() { return 'foo' }
}
}
let app: ExplicitVue<typeof options>;
/*interface Vue<T> {
data: any,
methods: { [s: string]: (this: ExplicitVue, ...args: any[]) => any },
properties: Propertise<{ [s: string]: (this: ExplicitVue, ...args: any[]) => any }>
}
declare function create<T>(options: Vue<T>): Vue<T>;
create({
data: { a: 12 },
methods: {
},
properties: {
}
});
*/
/*
let o = {
p: 12,
m(this: typeof o) {
let x = this.m(); // x: number
let y = this.p; // y: number
return this.p;
},
m2() {
return this.m() // this: any since it has no annotation
}
}
let x = o.m() // x: number
let y = o.m2() // y: any
let p = o.p // p: number
*/