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

Strict bind, call, and apply methods on functions #27028

Open
wants to merge 19 commits into
base: master
from

Conversation

Projects
None yet
8 participants
@ahejlsberg
Member

ahejlsberg commented Sep 11, 2018

This PR introduces a new --strictBindCallApply compiler option (in the --strict family of options) with which the bind, call, and apply methods on function objects are strongly typed and strictly checked. Since the stricter checks may uncover previously unreported errors, this is a breaking change in --strict mode.

The PR includes two new types, CallableFunction and NewableFunction, in lib.d.ts. These type contain generic method declarations for bind, call, and apply for regular functions and constructor functions, respectively. The declarations use generic rest parameters (see #24897) to capture and reflect parameter lists in a strongly typed manner. In --strictBindCallApply mode these declarations are used in place of the (very permissive) declarations provided by type Function.

interface CallableFunction extends Function {
    /**
      * Calls the function with the specified object as the this value and the elements of specified array as the arguments.
      * @param thisArg The object to be used as the this object.
      * @param args An array of argument values to be passed to the function.
      */
    apply<T, R>(this: (this: T) => R, thisArg: T): R;
    apply<T, A extends any[], R>(this: (this: T, ...args: A) => R, thisArg: T, args: A): R;

    /**
      * Calls the function with the specified object as the this value and the specified rest arguments as the arguments.
      * @param thisArg The object to be used as the this object.
      * @param args Argument values to be passed to the function.
      */
    call<T, A extends any[], R>(this: (this: T, ...args: A) => R, thisArg: T, ...args: A): R;

    /**
      * For a given function, creates a bound function that has the same body as the original function.
      * The this object of the bound function is associated with the specified object, and has the specified initial parameters.
      * @param thisArg The object to be used as the this object.
      * @param args Arguments to bind to the parameters of the function.
      */
    bind<T, A extends any[], R>(this: (this: T, ...args: A) => R, thisArg: T): (...args: A) => R;
    bind<T, A0, A extends any[], R>(this: (this: T, arg0: A0, ...args: A) => R, thisArg: T, arg0: A0): (...args: A) => R;
    bind<T, A0, A1, A extends any[], R>(this: (this: T, arg0: A0, arg1: A1, ...args: A) => R, thisArg: T, arg0: A0, arg1: A1): (...args: A) => R;
    bind<T, A0, A1, A2, A extends any[], R>(this: (this: T, arg0: A0, arg1: A1, arg2: A2, ...args: A) => R, thisArg: T, arg0: A0, arg1: A1, arg2: A2): (...args: A) => R;
    bind<T, A0, A1, A2, A3, A extends any[], R>(this: (this: T, arg0: A0, arg1: A1, arg2: A2, arg3: A3, ...args: A) => R, thisArg: T, arg0: A0, arg1: A1, arg2: A2, arg3: A3): (...args: A) => R;
    bind<T, AX, R>(this: (this: T, ...args: AX[]) => R, thisArg: T, ...args: AX[]): (...args: AX[]) => R;
}

interface NewableFunction extends Function {
    /**
      * Calls the function with the specified object as the this value and the elements of specified array as the arguments.
      * @param thisArg The object to be used as the this object.
      * @param args An array of argument values to be passed to the function.
      */
    apply<T>(this: new () => T, thisArg: T): void;
    apply<T, A extends any[]>(this: new (...args: A) => T, thisArg: T, args: A): void;

    /**
      * Calls the function with the specified object as the this value and the specified rest arguments as the arguments.
      * @param thisArg The object to be used as the this object.
      * @param args Argument values to be passed to the function.
      */
    call<T, A extends any[]>(this: new (...args: A) => T, thisArg: T, ...args: A): void;

    /**
      * For a given function, creates a bound function that has the same body as the original function.
      * The this object of the bound function is associated with the specified object, and has the specified initial parameters.
      * @param thisArg The object to be used as the this object.
      * @param args Arguments to bind to the parameters of the function.
      */
    bind<A extends any[], R>(this: new (...args: A) => R, thisArg: any): new (...args: A) => R;
    bind<A0, A extends any[], R>(this: new (arg0: A0, ...args: A) => R, thisArg: any, arg0: A0): new (...args: A) => R;
    bind<A0, A1, A extends any[], R>(this: new (arg0: A0, arg1: A1, ...args: A) => R, thisArg: any, arg0: A0, arg1: A1): new (...args: A) => R;
    bind<A0, A1, A2, A extends any[], R>(this: new (arg0: A0, arg1: A1, arg2: A2, ...args: A) => R, thisArg: any, arg0: A0, arg1: A1, arg2: A2): new (...args: A) => R;
    bind<A0, A1, A2, A3, A extends any[], R>(this: new (arg0: A0, arg1: A1, arg2: A2, arg3: A3, ...args: A) => R, thisArg: any, arg0: A0, arg1: A1, arg2: A2, arg3: A3): new (...args: A) => R;
    bind<AX, R>(this: new (...args: AX[]) => R, thisArg: any, ...args: AX[]): new (...args: AX[]) => R;
}

Note that the overloads of bind include up to four bound arguments beyond the this argument. (In the real world code we inspected in researching this PR, practically all uses of bind supplied only the this argument, and a few cases supplied one regular argument. No cases with more arguments were observed.)

Some examples:

// Compile with --strictBindCallApply

declare function foo(a: number, b: string): string;

let f00 = foo.bind(undefined);  // (a: number, b: string) => string
let f01 = foo.bind(undefined, 10);  // (b: string) => string
let f02 = foo.bind(undefined, 10, "hello");  // () => string
let f03 = foo.bind(undefined, 10, 20);  // Error

let c00 = foo.call(undefined, 10, "hello");  // string
let c01 = foo.call(undefined, 10);  // Error
let c02 = foo.call(undefined, 10, 20);  // Error
let c03 = foo.call(undefined, 10, "hello", 30);  // Error

let a00 = foo.apply(undefined, [10, "hello"]);  // string
let a01 = foo.apply(undefined, [10]);  // Error
let a02 = foo.apply(undefined, [10, 20]);  // Error
let a03 = foo.apply(undefined, [10, "hello", 30]);  // Error

class C {
    constructor(a: number, b: string) {}
    foo(this: this, a: number, b: string): string { return "" }
}

declare let c: C;
declare let obj: {};

let f10 = c.foo.bind(c);  // (a: number, b: string) => string
let f11 = c.foo.bind(c, 10);  // (b: string) => string
let f12 = c.foo.bind(c, 10, "hello");  // () => string
let f13 = c.foo.bind(c, 10, 20);  // Error
let f14 = c.foo.bind(undefined);  // Error

let c10 = c.foo.call(c, 10, "hello");  // string
let c11 = c.foo.call(c, 10);  // Error
let c12 = c.foo.call(c, 10, 20);  // Error
let c13 = c.foo.call(c, 10, "hello", 30);  // Error
let c14 = c.foo.call(undefined, 10, "hello");  // Error

let a10 = c.foo.apply(c, [10, "hello"]);  // string
let a11 = c.foo.apply(c, [10]);  // Error
let a12 = c.foo.apply(c, [10, 20]);  // Error
let a13 = c.foo.apply(c, [10, "hello", 30]);  // Error
let a14 = c.foo.apply(undefined, [10, "hello"]);  // Error

let f20 = C.bind(undefined);  // new (a: number, b: string) => C
let f21 = C.bind(undefined, 10);  // new (b: string) => C
let f22 = C.bind(undefined, 10, "hello");  // new () => C
let f23 = C.bind(undefined, 10, 20);  // Error

C.call(c, 10, "hello");  // void
C.call(c, 10);  // Error
C.call(c, 10, 20);  // Error
C.call(c, 10, "hello", 30);  // Error

C.apply(c, [10, "hello"]);  // void
C.apply(c, [10]);  // Error
C.apply(c, [10, 20]);  // Error
C.apply(c, [10, "hello", 30]);  // Error

Fixes #212. (Hey, just a short four years later!)

@ahejlsberg ahejlsberg requested a review from DanielRosenwasser Sep 11, 2018

ahejlsberg added some commits Sep 11, 2018

Merge branch 'master' into typedBindCallApply
# Conflicts:
#	src/compiler/diagnosticMessages.json
if (resolved === anyFunctionType || resolved.callSignatures.length || resolved.constructSignatures.length) {
const symbol = getPropertyOfObjectType(globalFunctionType, name);
const functionType = resolved === anyFunctionType ? globalFunctionType :
resolved.callSignatures.length ? globalCallableFunctionType :

This comment has been minimized.

@weswigham

weswigham Sep 11, 2018

Member

Is there a reason we prefer callable to newable if a given type is both?

@weswigham

weswigham Sep 11, 2018

Member

Is there a reason we prefer callable to newable if a given type is both?

This comment has been minimized.

@ahejlsberg

ahejlsberg Sep 11, 2018

Member

I'm not sure I have a convincing argument for either preference. All I know is that it is very rare to have both and the two sets of signatures presumably align such that calling without new just causes the function to allocate an instance (as is the case with Array<T>, for example). I also know that having a combined list of overloads causes error reporting to be poor for one side because we always use the last arity-matching signature as the error exemplar.

@ahejlsberg

ahejlsberg Sep 11, 2018

Member

I'm not sure I have a convincing argument for either preference. All I know is that it is very rare to have both and the two sets of signatures presumably align such that calling without new just causes the function to allocate an instance (as is the case with Array<T>, for example). I also know that having a combined list of overloads causes error reporting to be poor for one side because we always use the last arity-matching signature as the error exemplar.

This comment has been minimized.

@weswigham

weswigham Sep 12, 2018

Member

Can we not use both sets of overloads for resolution, and if both fail, simply prefer one set or the other just when reporting errors?

@weswigham

weswigham Sep 12, 2018

Member

Can we not use both sets of overloads for resolution, and if both fail, simply prefer one set or the other just when reporting errors?

This comment has been minimized.

@ahejlsberg

ahejlsberg Sep 12, 2018

Member

No, not easily. We assume that all overloads are in a single list of signatures, so we'd somehow have to merge them or declare a third type (CallableNewableFunction) in lib.d.ts that has the combined sets of overloads.

@ahejlsberg

ahejlsberg Sep 12, 2018

Member

No, not easily. We assume that all overloads are in a single list of signatures, so we'd somehow have to merge them or declare a third type (CallableNewableFunction) in lib.d.ts that has the combined sets of overloads.

@Yuudaari

This comment has been minimized.

Show comment
Hide comment
@Yuudaari

Yuudaari Sep 11, 2018

Correct me if I'm wrong, but won't the bind method overloads fail on functions with variable arguments? I use this kind of thing in my codebases... (not often, but they do exist)

function foo(...numbers: number[]) {}
const bar = [1, 2, 3];
foo.bind(undefined, ...bar);

Yuudaari commented Sep 11, 2018

Correct me if I'm wrong, but won't the bind method overloads fail on functions with variable arguments? I use this kind of thing in my codebases... (not often, but they do exist)

function foo(...numbers: number[]) {}
const bar = [1, 2, 3];
foo.bind(undefined, ...bar);
@ahejlsberg

This comment has been minimized.

Show comment
Hide comment
@ahejlsberg

ahejlsberg Sep 11, 2018

Member

Correct me if I'm wrong, but won't the bind method overloads fail on functions with variable arguments?

Yes, your example fails when bar is an array (but succeeds if bar is a tuple with four or less number elements). I think you're right, we need an extra overload to handle binding of functions with rest parameter arrays. That is:

interface CallableFunction extends Function {
    // ...
    bind<T, AX, R>(this: (this: T, ...args: AX[]) => R, thisArg: T, ...args: AX[]): (...args: AX[]) => R;
}

interface NewableFunction extends Function {
    // ...
    bind<AX, R>(this: new (...args: AX[]) => R, thisArg: any, ...args: AX[]): new (...args: AX[]) => R;
}

Should be pretty easy to add.

Member

ahejlsberg commented Sep 11, 2018

Correct me if I'm wrong, but won't the bind method overloads fail on functions with variable arguments?

Yes, your example fails when bar is an array (but succeeds if bar is a tuple with four or less number elements). I think you're right, we need an extra overload to handle binding of functions with rest parameter arrays. That is:

interface CallableFunction extends Function {
    // ...
    bind<T, AX, R>(this: (this: T, ...args: AX[]) => R, thisArg: T, ...args: AX[]): (...args: AX[]) => R;
}

interface NewableFunction extends Function {
    // ...
    bind<AX, R>(this: new (...args: AX[]) => R, thisArg: any, ...args: AX[]): new (...args: AX[]) => R;
}

Should be pretty easy to add.

@Kovensky

This comment has been minimized.

Show comment
Hide comment
@Kovensky

Kovensky Sep 12, 2018

Contributor

The second argument to apply is not actually an array but an ArrayLike, though I'm not sure how important that is on real world code; almost always when it's not an array it's just an arguments object.

Contributor

Kovensky commented Sep 12, 2018

The second argument to apply is not actually an array but an ArrayLike, though I'm not sure how important that is on real world code; almost always when it's not an array it's just an arguments object.

@ljharb

This comment has been minimized.

Show comment
Hide comment
@ljharb

ljharb Sep 12, 2018

It's very important that it be an ArrayLike, since people .apply with NodeLists, arrays, and arguments objects.

ljharb commented Sep 12, 2018

It's very important that it be an ArrayLike, since people .apply with NodeLists, arrays, and arguments objects.

@felixfbecker

This comment has been minimized.

Show comment
Hide comment
@felixfbecker

felixfbecker Sep 12, 2018

Some part of me wishes this was not behind a flag. What’s an example of a usage of apply/bind that is correct but would break with this?

If the two interfaces are only “injected” if the flag is on, how can libraries reference them without making an assumption on the consumer’s flag seeting?

felixfbecker commented Sep 12, 2018

Some part of me wishes this was not behind a flag. What’s an example of a usage of apply/bind that is correct but would break with this?

If the two interfaces are only “injected” if the flag is on, how can libraries reference them without making an assumption on the consumer’s flag seeting?

@sandersn

One initial comment, plus can you elaborate on the breaks that result from having this on everywhere as @felixfbecker requested? Or was that discussed during the last design meeting when I was out?

@@ -7366,8 +7369,12 @@ namespace ts {
if (symbol && symbolIsValue(symbol)) {
return symbol;
}
if (resolved === anyFunctionType || resolved.callSignatures.length || resolved.constructSignatures.length) {
const symbol = getPropertyOfObjectType(globalFunctionType, name);
const functionType = resolved === anyFunctionType ? globalFunctionType :

This comment has been minimized.

@sandersn

sandersn Sep 12, 2018

Member

This should be put into a function and anyFunctionType’s declaration needs a comment saying “Use this function instead”.

@sandersn

sandersn Sep 12, 2018

Member

This should be put into a function and anyFunctionType’s declaration needs a comment saying “Use this function instead”.

@sandersn

Why is the variance change needed?

@@ -13752,15 +13759,17 @@ namespace ts {
if (!inferredType) {
const signature = context.signature;
if (signature) {
if (inference.contraCandidates && (!inference.candidates || inference.candidates.length === 1 && inference.candidates[0].flags & TypeFlags.Never)) {

This comment has been minimized.

@sandersn

sandersn Sep 12, 2018

Member

Why is this change needed?

@sandersn

sandersn Sep 12, 2018

Member

Why is this change needed?

This comment has been minimized.

@ahejlsberg

ahejlsberg Sep 12, 2018

Member

The variance change is there to ensure we infer the most specific type possible when we have both co- and contra-variant candidates. Furthermore, knowing that an error will result when the co-variant inference is not a subtype of the contra-variant inference, we now prefer the contra-variant inference because it is likely to have come from an explicit type annotation on a function. This improves our error reporting.

@ahejlsberg

ahejlsberg Sep 12, 2018

Member

The variance change is there to ensure we infer the most specific type possible when we have both co- and contra-variant candidates. Furthermore, knowing that an error will result when the co-variant inference is not a subtype of the contra-variant inference, we now prefer the contra-variant inference because it is likely to have come from an explicit type annotation on a function. This improves our error reporting.

@@ -28543,6 +28555,8 @@ namespace ts {
globalArrayType = getGlobalType("Array" as __String, /*arity*/ 1, /*reportErrors*/ true);
globalObjectType = getGlobalType("Object" as __String, /*arity*/ 0, /*reportErrors*/ true);
globalFunctionType = getGlobalType("Function" as __String, /*arity*/ 0, /*reportErrors*/ true);
globalCallableFunctionType = strictBindCallApply && getGlobalType("CallableFunction" as __String, /*arity*/ 0, /*reportErrors*/ true) || globalFunctionType;

This comment has been minimized.

@sandersn

sandersn Sep 12, 2018

Member

Comment here is also a good idea.

@sandersn

sandersn Sep 12, 2018

Member

Comment here is also a good idea.

==== tests/cases/conformance/types/rest/genericRestArityStrict.ts (1 errors) ====
==== tests/cases/conformance/types/rest/genericRestArityStrict.ts (2 errors) ====
// Repro from #25559
declare function call<TS extends unknown[]>(
handler: (...args: TS) => void,
...args: TS): void;
call((x: number, y: number) => x + y);

This comment has been minimized.

@sandersn

sandersn Sep 12, 2018

Member

Is this improved error because of the variance inference change?

@sandersn

sandersn Sep 12, 2018

Member

Is this improved error because of the variance inference change?

This comment has been minimized.

@ahejlsberg
@ahejlsberg

ahejlsberg Sep 12, 2018

Member

Yes

error TS2318: Cannot find global type 'NewableFunction'.
!!! error TS2318: Cannot find global type 'CallableFunction'.

This comment has been minimized.

@sandersn

sandersn Sep 12, 2018

Member

Probably shouldn’t get an error here.

@sandersn

sandersn Sep 12, 2018

Member

Probably shouldn’t get an error here.

This comment has been minimized.

@ahejlsberg

ahejlsberg Sep 12, 2018

Member

I didn't want to update the private lib.d.ts used by the tests as I suspect that'll cause some really noisy baseline changes.

@ahejlsberg

ahejlsberg Sep 12, 2018

Member

I didn't want to update the private lib.d.ts used by the tests as I suspect that'll cause some really noisy baseline changes.

This comment has been minimized.

@sandersn

sandersn Sep 12, 2018

Member

OK, just wanted to make sure this error wouldn't show up in some public-facing scenario.

@sandersn

sandersn Sep 12, 2018

Member

OK, just wanted to make sure this error wouldn't show up in some public-facing scenario.

@ahejlsberg

This comment has been minimized.

Show comment
Hide comment
@ahejlsberg

ahejlsberg Sep 12, 2018

Member

The second argument to apply is not actually an array but an ArrayLike, though I'm not sure how important that is on real world code; almost always when it's not an array it's just an arguments object.

There is currently no type-safe way to use an ArrayLike<T> as the argument list in apply. I think this is fine since, as you point out, the typical use case is the arguments object which is inherently untyped anyway. To use an array-like with apply in --strictBindCallApply mode you need to cast the function object to type Function or cast the argument array to any:

(myFunction as Function).apply(undefined, arguments);
myFunction.apply(undefined, arguments as any);

Note that you can still call or apply methods of Array<T> with an ArrayLike<T> as the thisArg:

function printArguments() {
    Array.prototype.forEach.call(arguments, (item: any) => console.log(item));
}

This is permitted because we only strictly check the thisArg if the function on which you're invoking call or apply has an explicit this parameter. We might someday also tighten this up with a --strictThis flag, although there are several non-trivial issues with doing that. We're tracking this in #7968.

Member

ahejlsberg commented Sep 12, 2018

The second argument to apply is not actually an array but an ArrayLike, though I'm not sure how important that is on real world code; almost always when it's not an array it's just an arguments object.

There is currently no type-safe way to use an ArrayLike<T> as the argument list in apply. I think this is fine since, as you point out, the typical use case is the arguments object which is inherently untyped anyway. To use an array-like with apply in --strictBindCallApply mode you need to cast the function object to type Function or cast the argument array to any:

(myFunction as Function).apply(undefined, arguments);
myFunction.apply(undefined, arguments as any);

Note that you can still call or apply methods of Array<T> with an ArrayLike<T> as the thisArg:

function printArguments() {
    Array.prototype.forEach.call(arguments, (item: any) => console.log(item));
}

This is permitted because we only strictly check the thisArg if the function on which you're invoking call or apply has an explicit this parameter. We might someday also tighten this up with a --strictThis flag, although there are several non-trivial issues with doing that. We're tracking this in #7968.

@ahejlsberg

This comment has been minimized.

Show comment
Hide comment
@ahejlsberg

ahejlsberg Sep 12, 2018

Member

@felixfbecker There is a lot of code out there today that uses apply with the arguments object to intercept functions. A substantial portion of our real world code suites break with this type of error when the stricter checking is enabled. It's technically correct to report the issue (the affected code is unsafe and an explicit cast is merited), but without a compiler switch it becomes a gating issue for adopting the latest compiler. The main point of --strict mode is to signal that you're ok with such breakage. But conversely, when you're not --strict we take it as a signal to minimize breakage.

Member

ahejlsberg commented Sep 12, 2018

@felixfbecker There is a lot of code out there today that uses apply with the arguments object to intercept functions. A substantial portion of our real world code suites break with this type of error when the stricter checking is enabled. It's technically correct to report the issue (the affected code is unsafe and an explicit cast is merited), but without a compiler switch it becomes a gating issue for adopting the latest compiler. The main point of --strict mode is to signal that you're ok with such breakage. But conversely, when you're not --strict we take it as a signal to minimize breakage.

@ljharb

This comment has been minimized.

Show comment
Hide comment
@ljharb

ljharb Sep 12, 2018

@ahejlsberg { length: number }?

ljharb commented Sep 12, 2018

@ahejlsberg { length: number }?

@ahejlsberg

This comment has been minimized.

Show comment
Hide comment
@ahejlsberg

ahejlsberg Sep 12, 2018

Member

@ljharb Not sure what you mean by that?

Member

ahejlsberg commented Sep 12, 2018

@ljharb Not sure what you mean by that?

@ljharb

This comment has been minimized.

Show comment
Hide comment
@ljharb

ljharb Sep 12, 2018

@ahejlsberg sorry, i mean, you said "There is currently no type-safe way to use an ArrayLike" and i'm not sure why that's the case, since "arraylike" is effectively "any object-coercible with a number "length" property.

ljharb commented Sep 12, 2018

@ahejlsberg sorry, i mean, you said "There is currently no type-safe way to use an ArrayLike" and i'm not sure why that's the case, since "arraylike" is effectively "any object-coercible with a number "length" property.

@ahejlsberg

This comment has been minimized.

Show comment
Hide comment
@ahejlsberg

ahejlsberg Sep 12, 2018

Member

Latest commits add bind overloads to handle binding of functions with rest parameter arrays (as discussed here).

Member

ahejlsberg commented Sep 12, 2018

Latest commits add bind overloads to handle binding of functions with rest parameter arrays (as discussed here).

Merge branch 'master' into typedBindCallApply
# Conflicts:
#	src/compiler/diagnosticMessages.json
@ahejlsberg

This comment has been minimized.

Show comment
Hide comment
@ahejlsberg

ahejlsberg Sep 12, 2018

Member

@ljharb I'm still not sure what the issue is. Can you give me an example of something that uses an ArrayLike<T> that we wouldn't allow but you think we should?

Member

ahejlsberg commented Sep 12, 2018

@ljharb I'm still not sure what the issue is. Can you give me an example of something that uses an ArrayLike<T> that we wouldn't allow but you think we should?

@ljharb

This comment has been minimized.

Show comment
Hide comment
@ljharb

ljharb Sep 12, 2018

@ahejlsberg i mean, the types should reflect what javascript allows - I don't have a concrete use case off the top of my head, but (function () { return [...arguments] }).apply(null, Object('bar')) should return ['b', 'a', 'r'].

ljharb commented Sep 12, 2018

@ahejlsberg i mean, the types should reflect what javascript allows - I don't have a concrete use case off the top of my head, but (function () { return [...arguments] }).apply(null, Object('bar')) should return ['b', 'a', 'r'].

@weswigham

This comment has been minimized.

Show comment
Hide comment
@weswigham

weswigham Sep 13, 2018

Member

Just an aside: Because of how the change works, if the lookup error were squeltched, this could just be a lib flag. That'd be much less discoverable than a strict flag, though.

Member

weswigham commented Sep 13, 2018

Just an aside: Because of how the change works, if the lookup error were squeltched, this could just be a lib flag. That'd be much less discoverable than a strict flag, though.

@ahejlsberg

This comment has been minimized.

Show comment
Hide comment
@ahejlsberg

ahejlsberg Sep 13, 2018

Member

@ljharb That particular example is pretty far beyond the capabilities of the type checker. (Extra points for the added subtlety of spreading a string into individual characters!)

Part of what makes this PR useful is that we can capture parameter lists as tuple types and then, in the case of apply, require that the arguments array match the tuple type. But an ArrayLike<T> is never going to match a tuple type, so there's not much the checker can do other than report an error (which you can squelch using a type assertion).

Member

ahejlsberg commented Sep 13, 2018

@ljharb That particular example is pretty far beyond the capabilities of the type checker. (Extra points for the added subtlety of spreading a string into individual characters!)

Part of what makes this PR useful is that we can capture parameter lists as tuple types and then, in the case of apply, require that the arguments array match the tuple type. But an ArrayLike<T> is never going to match a tuple type, so there's not much the checker can do other than report an error (which you can squelch using a type assertion).

@RyanCavanaugh

This comment has been minimized.

Show comment
Hide comment
@RyanCavanaugh

RyanCavanaugh Sep 13, 2018

Member

@ahejlsberg FYI please hold off on merging since we haven't fully forked 3.1 yet

Member

RyanCavanaugh commented Sep 13, 2018

@ahejlsberg FYI please hold off on merging since we haven't fully forked 3.1 yet

@ahejlsberg

This comment has been minimized.

Show comment
Hide comment
@ahejlsberg
Member

ahejlsberg commented Sep 13, 2018

@RyanCavanaugh Will do.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment