Skip to content

type-level function application #17961

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
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
080b391
add type-level function application
KiaraGrouwstra Aug 22, 2017
312b69e
type calls: add failing test
KiaraGrouwstra Aug 22, 2017
c98fe79
disambiguate type argument lists with empty <>
KiaraGrouwstra Aug 23, 2017
636b4ac
fix function name
KiaraGrouwstra Aug 23, 2017
a76ba38
show issues: overload selection (#17471), composition
KiaraGrouwstra Aug 24, 2017
187b6b5
minimum chaining repro + checks, errors
KiaraGrouwstra Aug 25, 2017
ee110a3
allow errors on artificial nodes, empty type args
KiaraGrouwstra Aug 25, 2017
7a0ec45
Merge branch 'master' into 6606-type-level-function-application
KiaraGrouwstra Aug 26, 2017
fa39af3
switch parent linking to built-in fn.. how to import though?
KiaraGrouwstra Aug 26, 2017
6591541
fix some tests
KiaraGrouwstra Sep 3, 2017
4b2e8a1
give type calls own type, fixes eager eval
KiaraGrouwstra Sep 6, 2017
2f2e800
fix other tests
KiaraGrouwstra Sep 8, 2017
c54eea7
drop type arg support, works fine but not worth it
KiaraGrouwstra Sep 9, 2017
0e4c359
resolve type call, generalize functions to fix synthetic node workaround
KiaraGrouwstra Sep 9, 2017
c70ea67
factor out function
KiaraGrouwstra Sep 9, 2017
f730a07
drop index type... not needed here unlike for gcnew's `!`?
KiaraGrouwstra Sep 9, 2017
9bbae94
add tests
KiaraGrouwstra Sep 9, 2017
28aece0
tests: reduce/map WIP
KiaraGrouwstra Sep 9, 2017
4d14c9f
add use-case: type substraction / assertion (`!`)
KiaraGrouwstra Sep 9, 2017
a8fc55a
use-case: `promised` (WIP, breaks on unions)
KiaraGrouwstra Sep 9, 2017
ccc55d1
add tests
KiaraGrouwstra Sep 11, 2017
a7882c2
add error tests in own file
KiaraGrouwstra Sep 11, 2017
e67a82b
simplify out unused bits
KiaraGrouwstra Sep 11, 2017
5decbbc
directly pass arg types for type calls, fixes compose
KiaraGrouwstra Sep 11, 2017
d602415
union-proof type calls, fixes `awaited` use-case from #17077
KiaraGrouwstra Sep 11, 2017
e131686
fix heterogeneous map test (add type param)
KiaraGrouwstra Sep 11, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
resolve type call, generalize functions to fix synthetic node workaround
  • Loading branch information
KiaraGrouwstra committed Sep 9, 2017
commit 0e4c3592d3295a6549e3984c7b79383a871ce8f6
47 changes: 20 additions & 27 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7628,10 +7628,6 @@ namespace ts {
return links.resolvedType;
}

// null! as type
function typeNodeToExpression(type: TypeNode): Expression {
return createAsExpression(createNonNullExpression(createNull()), type);
}

function createIndexedAccessType(objectType: Type, indexType: Type) {
const type = <IndexedAccessType>createType(TypeFlags.IndexedAccess);
Expand Down Expand Up @@ -7826,7 +7822,7 @@ namespace ts {
if (!links.resolvedType) {
// Deferred resolution of members is handled by resolveObjectTypeMembers
const aliasSymbol = getAliasSymbolForTypeNode(node);
if (node.symbol.members.size === 0 && !aliasSymbol) {
if (!node.symbol || node.symbol.members.size === 0 && !aliasSymbol) {
links.resolvedType = emptyTypeLiteralType;
}
else {
Expand All @@ -7843,7 +7839,7 @@ namespace ts {
}

function getAliasSymbolForTypeNode(node: TypeNode) {
return node.parent.kind === SyntaxKind.TypeAliasDeclaration ? getSymbolOfNode(node.parent) : undefined;
return node.parent && node.parent.kind === SyntaxKind.TypeAliasDeclaration ? getSymbolOfNode(node.parent) : undefined;
}

function getAliasTypeArgumentsForTypeNode(node: TypeNode) {
Expand Down Expand Up @@ -8449,7 +8445,7 @@ namespace ts {
if (type.flags & TypeFlags.TypeCall) {
return getTypeCallType(
instantiateType((<TypeCallType>type).function, mapper),
(<TypeCallType>type).arguments
instantiateTypes((<TypeCallType>type).arguments, mapper)
);
}
return type;
Expand Down Expand Up @@ -13134,7 +13130,7 @@ namespace ts {

// In a typed function call, an argument or substitution expression is contextually typed by the type of the corresponding parameter.
function getContextualTypeForArgument(callTarget: CallLikeExpression, arg: Expression): Type {
const args = getEffectiveCallArguments(callTarget);
const args = <ReadonlyArray<Expression>>getEffectiveCallArguments(callTarget);
const argIndex = indexOf(args, arg);
if (argIndex >= 0) {
// If we're already in the process of resolving the given signature, don't resolve again as
Expand Down Expand Up @@ -15205,7 +15201,7 @@ namespace ts {
}
}

function getSpreadArgumentIndex(args: ReadonlyArray<Expression>): number {
function getSpreadArgumentIndex(args: Arguments): number {
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg && arg.kind === SyntaxKind.SpreadElement) {
Expand All @@ -15215,7 +15211,7 @@ namespace ts {
return -1;
}

function hasCorrectArity(node: CallLike, args: ReadonlyArray<Expression>, signature: Signature, signatureHelpTrailingComma = false) {
function hasCorrectArity(node: CallLike, args: Arguments, signature: Signature, signatureHelpTrailingComma = false) {
let argCount: number; // Apparent number of arguments we will have in this call
let typeArguments: NodeArray<TypeNode>; // Type arguments (undefined if none)
let callIsIncomplete: boolean; // In incomplete call we want to be lenient when we have too few arguments
Expand Down Expand Up @@ -15326,7 +15322,7 @@ namespace ts {
return getSignatureInstantiation(signature, getInferredTypes(context));
}

function inferTypeArguments(node: CallLike, signature: Signature, args: ReadonlyArray<Expression>, excludeArgument: boolean[], context: InferenceContext): Type[] {
function inferTypeArguments(node: CallLike, signature: Signature, args: Arguments, excludeArgument: boolean[], context: InferenceContext): Type[] {
// Clear out all the inference results from the last time inferTypeArguments was called on this context
for (const inference of context.inferences) {
// As an optimization, we don't have to clear (and later recompute) inferred types
Expand Down Expand Up @@ -15379,17 +15375,17 @@ namespace ts {
for (let i = 0; i < argCount; i++) {
const arg = getEffectiveArgument(node, args, i);
// If the effective argument is 'undefined', then it is an argument that is present but is synthetic.
if (arg === undefined || arg.kind !== SyntaxKind.OmittedExpression) {
if (isTypeCallTypeNode(node) || arg === undefined || arg.kind !== SyntaxKind.OmittedExpression) {
const paramType = getTypeAtPosition(signature, i);
let argType = getEffectiveArgumentType(node, i);

// If the effective argument type is 'undefined', there is no synthetic type
// for the argument. In that case, we should check the argument.
if (argType === undefined) {
if (!isTypeCallTypeNode(node) && argType === undefined) {
// For context sensitive arguments we pass the identityMapper, which is a signal to treat all
// context sensitive function expressions as wildcards
const mapper = excludeArgument && excludeArgument[i] !== undefined ? identityMapper : context;
argType = checkExpressionWithContextualType(arg, paramType, mapper);
argType = checkExpressionWithContextualType(<Expression>arg, paramType, mapper);
}

inferTypes(context.inferences, argType, paramType);
Expand All @@ -15401,13 +15397,13 @@ namespace ts {
// as we construct types for contextually typed parameters)
// Decorators will not have `excludeArgument`, as their arguments cannot be contextually typed.
// Tagged template expressions will always have `undefined` for `excludeArgument[0]`.
if (excludeArgument) {
if (!isTypeCallTypeNode(node) && excludeArgument) {
for (let i = 0; i < argCount; i++) {
// No need to check for omitted args and template expressions, their exclusion value is always undefined
if (excludeArgument[i] === false) {
const arg = args[i];
const paramType = getTypeAtPosition(signature, i);
inferTypes(context.inferences, checkExpressionWithContextualType(arg, paramType, context), paramType);
inferTypes(context.inferences, checkExpressionWithContextualType(<Expression>arg, paramType, context), paramType);
}
}
}
Expand Down Expand Up @@ -15480,7 +15476,7 @@ namespace ts {

function checkApplicableSignature(
node: CallLike,
args: ReadonlyArray<Expression>,
args: Arguments,
signature: Signature,
relation: Map<RelationComparisonResult>,
excludeArgument: boolean[],
Expand All @@ -15504,9 +15500,9 @@ namespace ts {
const headMessage = Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1;
const argCount = getEffectiveArgumentCount(node, args, signature);
for (let i = 0; i < argCount; i++) {
const arg = getEffectiveArgument(node, args, i);
const arg = <Expression>getEffectiveArgument(node, args, i);
// If the effective argument is 'undefined', then it is an argument that is present but is synthetic.
if (arg === undefined || arg.kind !== SyntaxKind.OmittedExpression) {
if (!isTypeCallTypeNode(node) && 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);
// If the effective argument type is undefined, there is no synthetic type for the argument.
Expand Down Expand Up @@ -15552,7 +15548,7 @@ namespace ts {
* If 'node' is a Decorator, the argument list will be `undefined`, and its arguments and types
* will be supplied from calls to `getEffectiveArgumentCount` and `getEffectiveArgumentType`.
*/
function getEffectiveCallArguments(node: CallLike): ReadonlyArray<Expression> {
function getEffectiveCallArguments(node: CallLike): Arguments {
if (node.kind === SyntaxKind.TaggedTemplateExpression) {
const template = (<TaggedTemplateExpression>node).template;
const args: Expression[] = [undefined];
Expand All @@ -15572,9 +15568,6 @@ namespace ts {
else if (isJsxOpeningLikeElement(node)) {
return node.attributes.properties.length > 0 ? [node.attributes] : emptyArray;
}
else if (isTypeCallTypeNode(node)) {
return map(<NodeArray<TypeNode>>node.arguments, (arg: TypeNode) => typeNodeToExpression(arg)) || emptyArray;
}
else {
return node.arguments || emptyArray;
}
Expand All @@ -15594,7 +15587,7 @@ namespace ts {
* us to match a property decorator.
* Otherwise, the argument count is the length of the 'args' array.
*/
function getEffectiveArgumentCount(node: CallLike, args: ReadonlyArray<Expression>, signature: Signature) {
function getEffectiveArgumentCount(node: CallLike, args: Arguments, signature: Signature) {
if (node.kind === SyntaxKind.Decorator) {
switch (node.parent.kind) {
case SyntaxKind.ClassDeclaration:
Expand Down Expand Up @@ -15829,7 +15822,7 @@ namespace ts {
/**
* Gets the effective argument expression for an argument in a call expression.
*/
function getEffectiveArgument(node: CallLike, args: ReadonlyArray<Expression>, argIndex: number) {
function getEffectiveArgument(node: CallLike, args: Arguments, argIndex: number) {
// For a decorator or the first argument of a tagged template expression we return undefined.
if (node.kind === SyntaxKind.Decorator ||
(argIndex === 0 && node.kind === SyntaxKind.TaggedTemplateExpression)) {
Expand Down Expand Up @@ -15899,11 +15892,11 @@ namespace ts {
const isSingleNonGenericCandidate = candidates.length === 1 && !candidates[0].typeParameters;
let excludeArgument: boolean[];
let excludeCount = 0;
if (!isDecorator && !isSingleNonGenericCandidate) {
if (!isDecorator && !isSingleNonGenericCandidate && !isTypeCallTypeNode(node)) {
// We do not need to call `getEffectiveArgumentCount` here as it only
// applies when calculating the number of arguments for a decorator.
for (let i = isTaggedTemplate ? 1 : 0; i < args.length; i++) {
if (isContextSensitive(args[i])) {
if (isContextSensitive(<Expression>args[i])) {
if (!excludeArgument) {
excludeArgument = new Array(args.length);
}
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,8 @@ namespace ts {

export type DeclarationName = Identifier | StringLiteral | NumericLiteral | ComputedPropertyName | BindingPattern;

export type Arguments = ReadonlyArray<Expression> | ReadonlyArray<TypeNode>;

export interface Declaration extends Node {
_declarationBrand: any;
}
Expand Down
4 changes: 1 addition & 3 deletions tests/baselines/reference/overloadSelection.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ interface Match {
(o: object): 0;
(o: any): 1;
}
type Wrap = <T = RegExp>(v: T) => Match(T);
type Wrap = <T>(v: T) => Match(T);
type A = Wrap(RegExp);
// falls thru to 1, `object` checked not with generic val `RegExp` but with its constraint (generic `any`)


//// [overloadSelection.js]
// falls thru to 1, `object` checked not with generic val `RegExp` but with its constraint (generic `any`)
9 changes: 3 additions & 6 deletions tests/baselines/reference/overloadSelection.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,16 @@ interface Match {
(o: any): 1;
>o : Symbol(o, Decl(overloadSelection.ts, 2, 3))
}
type Wrap = <T = RegExp>(v: T) => Match(T);
type Wrap = <T>(v: T) => Match(T);
>Wrap : Symbol(Wrap, Decl(overloadSelection.ts, 3, 1))
>T : Symbol(T, Decl(overloadSelection.ts, 4, 13))
>RegExp : Symbol(RegExp, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
>v : Symbol(v, Decl(overloadSelection.ts, 4, 25))
>v : Symbol(v, Decl(overloadSelection.ts, 4, 16))
>T : Symbol(T, Decl(overloadSelection.ts, 4, 13))
>Match : Symbol(Match, Decl(overloadSelection.ts, 0, 0))
>T : Symbol(T, Decl(overloadSelection.ts, 4, 13))

type A = Wrap(RegExp);
>A : Symbol(A, Decl(overloadSelection.ts, 4, 43))
>A : Symbol(A, Decl(overloadSelection.ts, 4, 34))
>Wrap : Symbol(Wrap, Decl(overloadSelection.ts, 3, 1))
>RegExp : Symbol(RegExp, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))

// falls thru to 1, `object` checked not with generic val `RegExp` but with its constraint (generic `any`)

7 changes: 2 additions & 5 deletions tests/baselines/reference/overloadSelection.types
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,16 @@ interface Match {
(o: any): 1;
>o : any
}
type Wrap = <T = RegExp>(v: T) => Match(T);
type Wrap = <T>(v: T) => Match(T);
>Wrap : Wrap
>T : T
>RegExp : RegExp
>v : T
>T : T
>Match : Match
>T : T

type A = Wrap(RegExp);
>A : Match(T)
>A : 0
>Wrap : Wrap
>RegExp : RegExp

// falls thru to 1, `object` checked not with generic val `RegExp` but with its constraint (generic `any`)

18 changes: 6 additions & 12 deletions tests/baselines/reference/typeCall.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,25 +29,20 @@ type i = Wrap<123>;
type F5 = () => () => { a: () => 1; };
type j = F5()()['a']();

declare function id<T>(v: T): T;
let l = id<string>('foo');

interface IsPrimitive {
(o: object): '0';
(o: any): '1';
}
type stringIsPrimitive = IsPrimitive(string); // '1', ok
type regexpIsPrimitive = IsPrimitive(RegExp); // '0', ok
type stringIsPrimitive = IsPrimitive(string);
type regexpIsPrimitive = IsPrimitive(RegExp);

// alternative, pass as parameters
type genericIsPrimitive3 = <T>(v: T) => IsPrimitive(T);
type stringIsPrimitive3 = genericIsPrimitive3(string); // '1', ok
type stringIsPrimitive3 = genericIsPrimitive3(string);
type regexpIsPrimitive3 = genericIsPrimitive3(RegExp)
// fails, see #17471, '1' instead of '0', should delay overload selection until type argument is known

type map = <Fn extends (v: T) => any, O extends { [k: string]: T }, T>(fn: Fn, obj: O) => { [P in keyof O]: Fn(O[P]) };
type z = map(<T>(v: T) => [T], { a: 1, b: 2, c: 3 });
// FAILS!, wanted `{ a: [1], b: [2], c: [3] }`, got `{ a: any; b: any; c: any; }`.

// binary function composition
type Fn1 = <T1 extends number>(v1: T1[]) => { [k: string]: T1 };
Expand All @@ -58,9 +53,9 @@ type Fn3 = <T3 extends number[]>(v3: T3) => Fn2(Fn1(T3));
// type Fn4 = Fn3(1); // errors, ok
let ones = null! as 1[];
type Fn4b = Fn3(typeof ones);
// FAILS, wanted `ReadonlyArray<1>`, got `ReadonlyArray<number>`.
// FAILS, wanted `ReadonlyArray<1>`, got `ReadonlyArray<{}>`.
type Fn4c = Fn3(1[]);
// FAILS, wanted `ReadonlyArray<1>`, got `ReadonlyArray<number>`.
// FAILS, wanted `ReadonlyArray<1>`, got `ReadonlyArray<{}>`.
// let x = fn2(fn1(1)); // errors with not assignable, ok
// type X = Fn2(Fn1(1)); // errors with not assignable, ok
let y = fn2(fn1(ones));
Expand Down Expand Up @@ -156,12 +151,11 @@ function comparability<T>(x: T, y: () => T) {

//// [typeCall.js]
var a = 'foo';
var l = id('foo');
var fn1 = null;
var fn2 = null;
// type Fn4 = Fn3(1); // errors, ok
var ones = null;
// FAILS, wanted `ReadonlyArray<1>`, got `ReadonlyArray<number>`.
// FAILS, wanted `ReadonlyArray<1>`, got `ReadonlyArray<{}>`.
// let x = fn2(fn1(1)); // errors with not assignable, ok
// type X = Fn2(Fn1(1)); // errors with not assignable, ok
var y = fn2(fn1(ones));
Expand Down
Loading