TS: Handle rest parameters in call signatures

This commit is contained in:
Asger F
2019-11-15 16:52:21 +00:00
parent f2c3d734ea
commit b6b8213e13
7 changed files with 209 additions and 12 deletions

View File

@@ -92,6 +92,20 @@ function isTypeofCandidateSymbol(symbol: ts.Symbol) {
const signatureKinds = [ts.SignatureKind.Call, ts.SignatureKind.Construct];
/**
* Bitmask of flags set on a signature, but not exposed in the public API.
*/
const enum InternalSignatureFlags {
HasRestParameter = 1
}
/**
* Signature interface with some internal properties exposed.
*/
interface AugmentedSignature extends ts.Signature {
flags?: InternalSignatureFlags;
}
/**
* Encodes property lookup tuples `(baseType, name, property)` as three
* staggered arrays.
@@ -902,7 +916,7 @@ export class TypeTable {
/**
* Returns a unique string for the given call/constructor signature.
*/
private getSignatureString(kind: ts.SignatureKind, signature: ts.Signature): string {
private getSignatureString(kind: ts.SignatureKind, signature: AugmentedSignature): string {
let parameters = signature.getParameters();
let numberOfTypeParameters = signature.typeParameters == null
? 0
@@ -915,11 +929,26 @@ export class TypeTable {
break;
}
}
let hasRestParam = (signature.flags & InternalSignatureFlags.HasRestParameter) !== 0;
let restParameterTag = '';
if (hasRestParam) {
if (requiredParameters === parameters.length) {
// Do not count the rest parameter as a required parameter
requiredParameters = parameters.length - 1;
}
if (parameters.length === 0) return null;
let restParameter = parameters[parameters.length - 1];
let restParameterType = this.typeChecker.getTypeOfSymbolAtLocation(restParameter, this.arbitraryAstNode);
if (restParameterType == null) return null;
let restParameterTypeId = this.getId(restParameterType, false);
if (restParameterTypeId == null) return null;
restParameterTag = '' + restParameterTypeId;
}
let returnTypeId = this.getId(signature.getReturnType(), false);
if (returnTypeId == null) {
return null;
}
let tag = `${kind};${numberOfTypeParameters};${requiredParameters};${returnTypeId}`;
let tag = `${kind};${numberOfTypeParameters};${requiredParameters};${restParameterTag};${returnTypeId}`;
for (let typeParameter of signature.typeParameters || []) {
tag += ";" + typeParameter.symbol.name;
let constraint = typeParameter.getConstraint();
@@ -930,11 +959,20 @@ export class TypeTable {
tag += ";" + constraintId;
}
}
for (let parameter of parameters) {
for (let paramIndex = 0; paramIndex < parameters.length; ++paramIndex) {
let parameter = parameters[paramIndex];
let parameterType = this.typeChecker.getTypeOfSymbolAtLocation(parameter, this.arbitraryAstNode);
if (parameterType == null) {
return null;
}
let isRestParameter = hasRestParam && (paramIndex === parameters.length - 1);
if (isRestParameter) {
// The type of the rest parameter is the array type, but we wish to extract the non-array type.
if (!isTypeReference(parameterType)) return null;
let typeArguments = parameterType.typeArguments;
if (typeArguments == null || typeArguments.length === 0) return null;
parameterType = typeArguments[0];
}
let parameterTypeId = this.getId(parameterType, false);
if (parameterTypeId == null) {
return null;

View File

@@ -201,13 +201,18 @@ public class TypeExtractor {
private void extractSignature(int index) {
// Format is:
// kind;numTypeParams;requiredParams;returnType(;paramName;paramType)*
// kind;numTypeParams;requiredParams;restParamType;returnType(;paramName;paramType)*
String[] parts = split(table.getSignatureString(index));
Label label = trapWriter.globalID("signature;" + index);
int kind = Integer.parseInt(parts[0]);
int numberOfTypeParameters = Integer.parseInt(parts[1]);
int requiredParameters = Integer.parseInt(parts[2]);
Label returnType = trapWriter.globalID("type;" + parts[3]);
String restParamTypeTag = parts[3];
if (!restParamTypeTag.isEmpty()) {
trapWriter.addTuple(
"signature_rest_parameter", label, trapWriter.globalID("type;" + restParamTypeTag));
}
Label returnType = trapWriter.globalID("type;" + parts[4]);
trapWriter.addTuple(
"signature_types",
label,
@@ -216,9 +221,9 @@ public class TypeExtractor {
numberOfTypeParameters,
requiredParameters);
trapWriter.addTuple("signature_contains_type", returnType, label, -1);
int numberOfParameters = (parts.length - 4) / 2; // includes type parameters
int numberOfParameters = (parts.length - 5) / 2; // includes type parameters
for (int i = 0; i < numberOfParameters; ++i) {
int partIndex = 4 + (2 * i);
int partIndex = 5 + (2 * i);
String paramName = parts[partIndex];
String paramTypeId = parts[partIndex + 1];
if (paramTypeId.length() > 0) { // Unconstrained type parameters have an empty type ID.

View File

@@ -2555,17 +2555,19 @@ class CallSignatureType extends @signature_type {
predicate hasTypeParameters() { getNumTypeParameter() > 0 }
/**
* Gets the type of the `n`th parameter of this signature.
* Gets the type of the `n`th parameter declared in this signature.
*
* If the `n`th parameter is a rest parameter `...T[]`, gets type `T`.
*/
Type getParameter(int n) { n >= 0 and result = getChild(n + getNumTypeParameter()) }
/**
* Gets the type of a parameter of this signature.
* Gets the type of a parameter of this signature, including the rest parameter, if any.
*/
Type getAParameter() { result = getParameter(_) }
/**
* Gets the number of parameters.
* Gets the number of parameters, including the rest parameter, if any.
*/
int getNumParameter() { result = count(int i | exists(getParameter(i))) }
@@ -2577,7 +2579,7 @@ class CallSignatureType extends @signature_type {
/**
* Gets the number of optional parameters, that is,
* parameters that are marked as optional with the `?` suffix.
* parameters that are marked as optional with the `?` suffix or is a rest parameter.
*/
int getNumOptionalParameter() { result = getNumParameter() - getNumRequiredParameter() }
@@ -2591,7 +2593,9 @@ class CallSignatureType extends @signature_type {
}
/**
* Holds if the `n`th parameter is declared optional with the `?` suffix.
* Holds if the `n`th parameter is declared optional with the `?` suffix or is the rest parameter.
*
* Note that rest parameters are not considered optional in this sense.
*/
predicate isOptionalParameter(int n) {
exists(getParameter(n)) and
@@ -2610,6 +2614,30 @@ class CallSignatureType extends @signature_type {
* Gets the name of a parameter of this signature.
*/
string getAParameterName() { result = getParameterName(_) }
/**
* Holds if this signature declares a rest parameter, such as `(x: number, ...y: string[])`.
*/
predicate hasRestParameter() { signature_rest_parameter(this, _) } // TODO
/**
* Gets the type of the rest parameter, if any.
*
* For example, for the signature `(...y: string[])`, this gets the type `string`.
*/
Type getRestParameterType() {
hasRestParameter() and
result = getParameter(getNumParameter() - 1)
}
/**
* Gets the type of the rest parameter as an array, if it exists.
*
* For example, for the signature `(...y: string[])`, this gets the type `string[]`.
*/
PlainArrayType getRestParameterArrayType() {
signature_rest_parameter(this, result)
}
}
/**

View File

@@ -721,6 +721,11 @@ signature_types (
int required_params: int ref
);
signature_rest_parameter(
unique int sig: @signature_type ref,
int rest_param_arra_type: @type ref
);
case @signature_type.kind of
0 = @function_signature_type
| 1 = @constructor_signature_type

View File

@@ -47,14 +47,39 @@ test_ExprSignature
| tst.ts:45:15:45:15 | x | string |
| tst.ts:46:3:46:25 | constru ... umber); | any |
| tst.ts:46:15:46:15 | x | number |
| tst.ts:50:3:50:36 | method( ... ing[]); | (x: number, ...y: string[]): any |
| tst.ts:50:10:50:10 | x | number |
| tst.ts:50:24:50:24 | y | string[] |
| tst.ts:51:4:51:4 | x | number |
| tst.ts:51:18:51:18 | y | string[] |
| tst.ts:52:7:52:7 | x | number |
| tst.ts:52:21:52:21 | y | string[] |
| tst.ts:54:3:54:34 | method2 ... ing[]); | (x: number, y: string[]): any |
| tst.ts:54:11:54:11 | x | number |
| tst.ts:54:22:54:22 | y | string[] |
| tst.ts:55:3:55:32 | method3 ... tring); | (x: number, y: string): any |
| tst.ts:55:11:55:11 | x | number |
| tst.ts:55:22:55:22 | y | string |
| tst.ts:59:3:59:25 | method( ... ing[]); | (...y: string[]): any |
| tst.ts:59:13:59:13 | y | string[] |
| tst.ts:60:7:60:7 | y | string[] |
| tst.ts:61:10:61:10 | y | string[] |
| tst.ts:63:3:63:23 | method2 ... ing[]); | (y: string[]): any |
| tst.ts:63:11:63:11 | y | string[] |
| tst.ts:64:3:64:21 | method3(y: string); | (y: string): any |
| tst.ts:64:11:64:11 | y | string |
test_TypeReferenceSig
| Callable | function | 0 | (x: number): string |
| Newable | constructor | 0 | new (x: number): any |
| OnlyRestParams | constructor | 0 | new (...y: string[]): any |
| OnlyRestParams | function | 0 | (...y: string[]): any |
| OverloadedCallable | function | 0 | (x: number): number |
| OverloadedCallable | function | 1 | (x: string): string |
| OverloadedCallable | function | 2 | (x: any): any |
| OverloadedNewable | constructor | 0 | new (x: number): OverloadedNewable |
| OverloadedNewable | constructor | 1 | new (x: any): any |
| WithRestParams | constructor | 0 | new (x: number, ...y: string[]): any |
| WithRestParams | function | 0 | (x: number, ...y: string[]): any |
test_FunctionCallSig
| tst.ts:2:3:2:22 | (x: number): string; | (x: number): string |
| tst.ts:6:3:6:22 | (x: number): number; | (x: number): number |
@@ -72,3 +97,62 @@ test_FunctionCallSig
| tst.ts:40:1:42:1 | functio ... oo");\\n} | (g: Generic<string>): string |
| tst.ts:45:3:45:25 | constru ... tring); | new (x: string): C |
| tst.ts:46:3:46:25 | constru ... umber); | new (x: number): C |
| tst.ts:50:3:50:36 | method( ... ing[]); | (x: number, ...y: string[]): any |
| tst.ts:51:3:51:30 | (x: num ... ing[]); | (x: number, ...y: string[]): any |
| tst.ts:52:3:52:33 | new(x: ... ing[]); | new (x: number, ...y: string[]): any |
| tst.ts:54:3:54:34 | method2 ... ing[]); | (x: number, y: string[]): any |
| tst.ts:55:3:55:32 | method3 ... tring); | (x: number, y: string): any |
| tst.ts:59:3:59:25 | method( ... ing[]); | (...y: string[]): any |
| tst.ts:60:3:60:19 | (...y: string[]); | (...y: string[]): any |
| tst.ts:61:3:61:22 | new(...y: string[]); | new (...y: string[]): any |
| tst.ts:63:3:63:23 | method2 ... ing[]); | (y: string[]): any |
| tst.ts:64:3:64:21 | method3(y: string); | (y: string): any |
test_getRestParameterType
| (...items: (string \| ConcatArray<string>)[]): string[] | string \| ConcatArray<string> |
| (...items: ConcatArray<string>[]): string[] | ConcatArray<string> |
| (...items: string[]): number | string |
| (...strings: string[]): string | string |
| (...y: string[]): any | string |
| (start: number, deleteCount: number, ...items: string[]): string[] | string |
| (substring: string, ...args: any[]): string | any |
| (x: number, ...y: string[]): any | string |
| new (...y: string[]): any | string |
| new (x: number, ...y: string[]): any | string |
test_getRestParameterArray
| (...items: (string \| ConcatArray<string>)[]): string[] | (string \| ConcatArray<string>)[] |
| (...items: ConcatArray<string>[]): string[] | ConcatArray<string>[] |
| (...items: string[]): number | string[] |
| (...strings: string[]): string | string[] |
| (...y: string[]): any | string[] |
| (start: number, deleteCount: number, ...items: string[]): string[] | string[] |
| (substring: string, ...args: any[]): string | any[] |
| (x: number, ...y: string[]): any | string[] |
| new (...y: string[]): any | string[] |
| new (x: number, ...y: string[]): any | string[] |
test_RestSig_getParameter
| (...items: (string \| ConcatArray<string>)[]): string[] | 0 | items | string \| ConcatArray<string> |
| (...items: ConcatArray<string>[]): string[] | 0 | items | ConcatArray<string> |
| (...items: string[]): number | 0 | items | string |
| (...strings: string[]): string | 0 | strings | string |
| (...y: string[]): any | 0 | y | string |
| (start: number, deleteCount: number, ...items: string[]): string[] | 0 | start | number |
| (start: number, deleteCount: number, ...items: string[]): string[] | 1 | deleteCount | number |
| (start: number, deleteCount: number, ...items: string[]): string[] | 2 | items | string |
| (substring: string, ...args: any[]): string | 0 | substring | string |
| (substring: string, ...args: any[]): string | 1 | args | any |
| (x: number, ...y: string[]): any | 0 | x | number |
| (x: number, ...y: string[]): any | 1 | y | string |
| new (...y: string[]): any | 0 | y | string |
| new (x: number, ...y: string[]): any | 0 | x | number |
| new (x: number, ...y: string[]): any | 1 | y | string |
test_RestSig_numRequiredParams
| (...items: (string \| ConcatArray<string>)[]): string[] | 0 |
| (...items: ConcatArray<string>[]): string[] | 0 |
| (...items: string[]): number | 0 |
| (...strings: string[]): string | 0 |
| (...y: string[]): any | 0 |
| (start: number, deleteCount: number, ...items: string[]): string[] | 2 |
| (substring: string, ...args: any[]): string | 1 |
| (x: number, ...y: string[]): any | 1 |
| new (...y: string[]): any | 0 |
| new (x: number, ...y: string[]): any | 1 |

View File

@@ -20,3 +20,22 @@ query predicate test_TypeReferenceSig(TypeReference type, SignatureKind kind, in
query predicate test_FunctionCallSig(Function f, CallSignatureType sig) {
sig = f.getCallSignature()
}
query Type test_getRestParameterType(CallSignatureType sig) {
result = sig.getRestParameterType()
}
query Type test_getRestParameterArray(CallSignatureType sig) {
result = sig.getRestParameterArrayType()
}
query predicate test_RestSig_getParameter(CallSignatureType sig, int n, string name, Type type) {
sig.hasRestParameter() and
name = sig.getParameterName(n) and
type = sig.getParameter(n)
}
query int test_RestSig_numRequiredParams(CallSignatureType sig) {
sig.hasRestParameter() and
result = sig.getNumRequiredParameter()
}

View File

@@ -45,3 +45,21 @@ declare class C {
constructor(x: string);
constructor(x: number);
}
interface WithRestParams {
method(x: number, ...y: string[]);
(x: number, ...y: string[]);
new(x: number, ...y: string[]);
method2(x: number, y: string[]);
method3(x: number, y: string);
}
interface OnlyRestParams {
method(...y: string[]);
(...y: string[]);
new(...y: string[]);
method2(y: string[]);
method3(y: string);
}