From b24b28eaaa844b233833e99c74aa5f5e3a7925b5 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 21 Jul 2014 16:38:02 -0700 Subject: [PATCH 1/2] Added contextual signature instantiation during type inference. Fixes #88. --- src/compiler/checker.ts | 94 +++++++++++++++++++++++++++++------------ 1 file changed, 66 insertions(+), 28 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7faabfae6ab..792dc64a1b9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2978,6 +2978,33 @@ module ts { return type; } + function forEachMatchingParameterType(source: Signature, target: Signature, callback: (s: Type, t: Type) => void) { + var sourceMax = source.parameters.length; + var targetMax = target.parameters.length; + var count: number; + if (source.hasRestParameter && target.hasRestParameter) { + count = sourceMax > targetMax ? sourceMax : targetMax; + sourceMax--; + targetMax--; + } + else if (source.hasRestParameter) { + sourceMax--; + count = targetMax; + } + else if (target.hasRestParameter) { + targetMax--; + count = sourceMax; + } + else { + count = sourceMax < targetMax ? sourceMax : targetMax; + } + for (var i = 0; i < count; i++) { + var s = i < sourceMax ? getTypeOfSymbol(source.parameters[i]) : getRestTypeOfSignature(source); + var t = i < targetMax ? getTypeOfSymbol(target.parameters[i]) : getRestTypeOfSignature(target); + callback(s, t); + } + } + function createInferenceContext(typeParameters: TypeParameter[]): InferenceContext { var inferences: Type[][] = []; for (var i = 0; i < typeParameters.length; i++) inferences.push([]); @@ -3073,35 +3100,12 @@ module ts { var targetLen = targetSignatures.length; var len = sourceLen < targetLen ? sourceLen : targetLen; for (var i = 0; i < len; i++) { - inferFromParameters(getErasedSignature(sourceSignatures[sourceLen - len + i]), getErasedSignature(targetSignatures[targetLen - len + i])); + inferFromSignature(getErasedSignature(sourceSignatures[sourceLen - len + i]), getErasedSignature(targetSignatures[targetLen - len + i])); } } - function inferFromParameters(source: Signature, target: Signature) { - var sourceMax = source.parameters.length; - var targetMax = target.parameters.length; - var checkCount: number; - if (!source.hasRestParameter && !target.hasRestParameter) { - checkCount = sourceMax < targetMax ? sourceMax : targetMax; - } - else if (source.hasRestParameter) { - sourceMax--; - checkCount = targetMax; - } - else if (target.hasRestParameter) { - targetMax--; - checkCount = sourceMax; - } - else { - checkCount = sourceMax > targetMax ? sourceMax : targetMax; - sourceMax--; - targetMax--; - } - for (var i = 0; i < checkCount; i++) { - var s = i < sourceMax ? getTypeOfSymbol(source.parameters[i]) : getRestTypeOfSignature(source); - var t = i < targetMax ? getTypeOfSymbol(target.parameters[i]) : getRestTypeOfSignature(target); - inferFromTypes(s, t); - } + function inferFromSignature(source: Signature, target: Signature) { + forEachMatchingParameterType(source, target, inferFromTypes); inferFromTypes(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target)); } @@ -3655,6 +3659,40 @@ module ts { return result; } + // If type has a single call signature and no other members, return that signature. Otherwise, return undefined. + function getSingleCallSignature(type: Type): Signature { + if (type.flags & TypeFlags.ObjectType) { + var resolved = resolveObjectTypeMembers(type); + if (resolved.callSignatures.length === 1 && resolved.constructSignatures.length === 0 && + resolved.properties.length === 0 && !resolved.stringIndexType && !resolved.numberIndexType) { + return resolved.callSignatures[0]; + } + } + return undefined; + } + + // Instantiate a generic signature in the context of a non-generic signature (section 3.8.5 in TypeScript spec) + function instantiateSignatureInContextOf(signature: Signature, contextualSignature: Signature, contextualMapper: TypeMapper): Signature { + var context = createInferenceContext(signature.typeParameters); + forEachMatchingParameterType(contextualSignature, signature, (source, target) => { + inferTypes(context, instantiateType(source, contextualMapper), target); + }); + return getSignatureInstantiation(signature, getInferredTypes(context)); + } + + // 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 signature = getSingleCallSignature(type); + if (signature && signature.typeParameters) { + var contextualSignature = getSingleCallSignature(contextualType); + if (contextualSignature && !contextualSignature.typeParameters) { + type = getOrCreateTypeFromSignature(instantiateSignatureInContextOf(signature, contextualSignature, contextualMapper)); + } + } + return type; + } + function inferTypeArguments(signature: Signature, args: Expression[], excludeArgument?: boolean[]): Type[] { var typeParameters = signature.typeParameters; var context = createInferenceContext(typeParameters); @@ -3663,7 +3701,7 @@ module ts { for (var i = 0; i < args.length; i++) { if (!excludeArgument || excludeArgument[i] === undefined) { var parameterType = getTypeAtPosition(signature, i); - inferTypes(context, checkExpression(args[i], parameterType, mapper), parameterType); + inferTypes(context, inferentiallyTypeExpession(args[i], parameterType, mapper), parameterType); } } // Next, infer from those context sensitive arguments that are no longer excluded @@ -3671,7 +3709,7 @@ module ts { for (var i = 0; i < args.length; i++) { if (excludeArgument[i] === false) { var parameterType = getTypeAtPosition(signature, i); - inferTypes(context, checkExpression(args[i], parameterType, mapper), parameterType); + inferTypes(context, inferentiallyTypeExpession(args[i], parameterType, mapper), parameterType); } } } From d85df9e9faa807f8481c88d4babb77e1e7d9ed72 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 22 Jul 2014 10:54:20 -0700 Subject: [PATCH 2/2] Addressing CR feedback. Adding comment to explain source type instantiation. Adding a test case. --- src/compiler/checker.ts | 1 + .../contextualSignatureInstantiation3.js | 42 +++++++++++++++++++ .../contextualSignatureInstantiation3.ts | 22 ++++++++++ 3 files changed, 65 insertions(+) create mode 100644 tests/baselines/reference/contextualSignatureInstantiation3.js create mode 100644 tests/cases/compiler/contextualSignatureInstantiation3.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 792dc64a1b9..aa457833175 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3675,6 +3675,7 @@ module ts { function instantiateSignatureInContextOf(signature: Signature, contextualSignature: Signature, contextualMapper: TypeMapper): Signature { var context = createInferenceContext(signature.typeParameters); forEachMatchingParameterType(contextualSignature, signature, (source, target) => { + // Type parameters from outer context referenced by source type are fixed by instantiation of the source type inferTypes(context, instantiateType(source, contextualMapper), target); }); return getSignatureInstantiation(signature, getInferredTypes(context)); diff --git a/tests/baselines/reference/contextualSignatureInstantiation3.js b/tests/baselines/reference/contextualSignatureInstantiation3.js new file mode 100644 index 00000000000..44737476482 --- /dev/null +++ b/tests/baselines/reference/contextualSignatureInstantiation3.js @@ -0,0 +1,42 @@ +//// [contextualSignatureInstantiation3.ts] +function map(items: T[], f: (x: T) => U): U[]{ + return items.map(f); +} + +function identity(x: T) { + return x; +} + +function singleton(x: T) { + return [x]; +} + +var xs = [1, 2, 3]; + +// Have compiler check that we get the correct types +var v1: number[]; +var v1 = xs.map(identity); // Error if not number[] +var v1 = map(xs, identity); // Error if not number[] + +var v2: number[][]; +var v2 = xs.map(singleton); // Error if not number[][] +var v2 = map(xs, singleton); // Error if not number[][] + + +//// [contextualSignatureInstantiation3.js] +function map(items, f) { + return items.map(f); +} +function identity(x) { + return x; +} +function singleton(x) { + return [x]; +} +var xs = [1, 2, 3]; +var v1; +var v1 = xs.map(identity); +var v1 = map(xs, identity); +var v2; +var v2 = xs.map(singleton); +var v2 = map(xs, singleton); diff --git a/tests/cases/compiler/contextualSignatureInstantiation3.ts b/tests/cases/compiler/contextualSignatureInstantiation3.ts new file mode 100644 index 00000000000..a71f08d209d --- /dev/null +++ b/tests/cases/compiler/contextualSignatureInstantiation3.ts @@ -0,0 +1,22 @@ +function map(items: T[], f: (x: T) => U): U[]{ + return items.map(f); +} + +function identity(x: T) { + return x; +} + +function singleton(x: T) { + return [x]; +} + +var xs = [1, 2, 3]; + +// Have compiler check that we get the correct types +var v1: number[]; +var v1 = xs.map(identity); // Error if not number[] +var v1 = map(xs, identity); // Error if not number[] + +var v2: number[][]; +var v2 = xs.map(singleton); // Error if not number[][] +var v2 = map(xs, singleton); // Error if not number[][]