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
Compile-time errors related to this
are gone
#6
Comments
Good catch. Is there anything we can do to prevent these mistakes? My guess is there's not, since the function is just being passed as a value—TypeScript doesn't know how/when P.S. in your first example… pipeWith(
'foo',
JSON.parse
) … |
You're absolutely right, the correct way to call it would be without point-free style, e.g. |
If functions dependent on function pipeWith<A, B>(a: A, ab: (this: void, a: A) => B): B This makes it type-safe. JSON.parse('1'); // ✅ Works
parse('1'); // ✅ Compile-time error
pipeWith('foo', JSON.parse); // ✅ Compile-time error
pipeWith('foo', parse); // ✅ Compile-time error
pipeWith('foo', arg => JSON.parse(arg)); // ✅ Works |
Nice! Let's do that |
@karol-majewski I just realised, your example of |
In hindsight, isn't this better handled with something like https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/unbound-method.md? IIUC, the benefit the lint rule has over the changes made by this PR is that it will catch mistakes for methods where class Foo {
bar(key: string) {
return key;
}
}
const foo = new Foo();
// No type error here ❌
// ESLint error here ✅
const result = pipeWith('foo', foo.bar); |
That's right — the snippet in the original post was using a different definition. A version that is closer to how global constructors are defined. Not everyone is using ESLint. It would be best if such an error came from the right place — the type checker itself. Perhaps it's worth seeing if the standard library types can be changed in a way that requires a specific interface JSON {
- parse(text: string, reviver?: (this: any, key: string, value: any) => any): any;
+ parse(this: JSON, text: string, reviver?: (this: any, key: string, value: any) => any): any;
} Have you tried using this rule? I tried using its TSLint cousin and it didn't cover all cases. These two didn't raise an error (they should): parse('1'); // No error (expected one)
pipeWith('foo', parse); // No error (expected one) |
That could help, although note that in the specific example of const { parse } = JSON;
parse('{ "foo": 1 }') // => { foo: 1 } This makes me wonder why TypeScript doesn't prescribe a class Foo {
value = 2
add(number: number) {
return this.value + number;
}
}
const foo = new Foo();
// No type error here because `foo.add` doesn't have an *explicit*` this type ❌
const result = pipeWith(1, foo.add); It would be very annoying to manually type - add(number: number) {
+ add(this: Foo, number: number) { IMO this shouldn't be necessary because TypeScript clearly knows the type of Unless consumers are very careful to manually + explicitly annotate the
I used the TSLint rule a long time ago and it seemed to be useful. Yesterday I played with the ESLint variant here: https://github.com/unsplash/unsplash-web/pull/4467. I think the ESLint rule has the same problem though: typescript-eslint/typescript-eslint#1112. |
Exactly — it would be safe to implicitly apply a interface Collection<T> {
// This methods is available for any T.
toArray(): T[];
// This method is only available for array types, where T is a number
sum(this: Collection<number>): number;
}
declare const strings: Collection<string>
declare const numbers: Collection<number>
strings.sum(); // Compile-time error
numbers.sum(); // OK But since
In this specific example, yes, but it's easy to find an example where it doesn't: const { getItem } = localStorage;
getItem('foo'); // Runtime exception |
Let's find out: microsoft/TypeScript#36100 🍿 |
Sounds like they don't do it for performance reasons microsoft/TypeScript#35451 Off the back of the discussion in typescript-eslint/typescript-eslint#1279, I'm convinced this should be left to a lint rule. For this to work:
It doesn't hurt to keep |
I don't think there's any harm in keeping it for the benefit of users who do make the effort and type their methods deliberately? Example: the Snowplow collector requires a declare const trackers: Snowplow.TrackersByNamespace;
declare global {
interface Window {
snowplow: {
(callback: (this: TrackersByNamespace) => void): void;
}
}
}
pipeWith(
trackers,
window.snowplow,
); If we lift that requirement from |
Let's suppose I have a function that uses
this
other thanWindow
. In this example, I have a static method that must be called withthis
ofJSON
.TypeScript will warn you when you try and call
parse
as if it were a standalone function.However, when we do the same thing with
pipe
/pipeWith
, the error is gone.The text was updated successfully, but these errors were encountered: