-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Don't widen return types of function expressions #241
Comments
Bumping this since we ran into a situation with a leaked function forEach<T, U>(array: T[], callback: (element: T, index: number) => U): U {
if (array) {
for (let i = 0, len = array.length; i < len; i++) {
let result = callback(array[i], i);
if (result) {
return result;
}
}
}
return undefined;
}
forEach([1, 2, 3, 4], () => {
// do stuff
if (blah) {
// bam! accidental any
return undefined;
}
}); |
We need an implementation proposal for this one. |
What if we just did this:
|
I've arrived here via #7220. Would appreciate some guidance on whether the following is expected behaviour or not (playground): interface G<K, R> {
(v: K): R;
}
function F<T>(v: T): T {
return v;
}
interface A {
__Type: "A";
}
interface B {
__Type: "B";
}
let a: A;
let b: B;
a = b; // OK: error as expected, B is not assignable to A
b = a; // OK: error as expected, A is not assignable to B
let x: G<A, B> = F; // How is this possible? It's the final assignment I cannot understand... because the compiler does not complain. |
During assignment checking, type parameters are replaced with |
Time to take this back up since it's affecting a lot of people writing reducers |
I ran into problems with some Is this still being worked on? |
Hi! Is return type widening a possible cause for the lack of errors in this case? type T = { foo: number };
type F = () => T
// no type error
const a: F = () => ({
foo: 1,
bar: 2,
}) Or is it because of the covariance rule on function subtyping? |
Happens for interface IUser {
id: number;
}
// No errors
const users: Array<IUser> = [].map<IUser>(() => ({
id: 1,
unknownProperty: 'unknownProperty',
}));
// Property 'id' is missing in type '{ unknownProperty: string; }' but required in type 'IUser'.ts(2741)
const users1: Array<IUser> = [].map<IUser>(() => ({
unknownProperty: 'unknownProperty',
}));
console.log(users);
console.log(users1); |
…6995) **User-Facing Changes** None **Description** TypeScript currently widens inferred types of function expressions (microsoft/TypeScript#241). This results in the following [sad situation](https://www.typescriptlang.org/play?#code/MYewdgzgLgBAhjAvDA2gRgDQwExYMwC6A3AFAlQCeADgKYwAqSMA3iTOzAB4BcMYArgFsARjQBOpAL5k4AOkFwqACiUBLAJS9GiAHwsSAegMwAegH42HMTSj8xYfRyddeqjJecwKvAEQgQVBCqND5YMEYw4mIgYjCAvBuAsjseMNKS6qQkcgpUADz0OioaSHqsEebJ1rb2jp4uMG7JTt4wfgFBIWERYCCRYtGxgHwbgCi7yanpZEA): ```ts const a = [1, 2, 3]; type T = { x: number; } a.map((i): T => { return { x: i, y: "oopsie", // error 👍 } }); a.map<T>((i) => { return { x: i, y: "oopsie", // no error 🤔 } }); ``` This PR adds a lint rule to automatically replace `map<T>((i) => ...` with `map((i): T => ...`. Depends on https://github.com/foxglove/studio/pull/6989 to merge first. Resolves FG-5336
This issue is also present when writing GraphQL resolvers: const resolvers: Resolvers = {
Query: {
someField: () => ({
requiredField: 'some value',
// @ts-expect-error
optionalFieldWithTyppo: 'some value',
})
}
} As we can see in this example TS is not doing its job of catching the type issue introduced by the typo on the optional field name. AFAIK there is no real workaround (except explicitly putting the return type on the function which defeats the purpose of putting the type in the first place). Is this issue still on the radar of the TypeScript team? |
This issue seems to be the cause of this error: type MyResponse = {
my: string;
response: string;
};
const myFunc = (param: string): MyResponse => {
const myResponse = {
my: 'my',
response: 'response',
extra: 'extra', // no error here
};
return myResponse;
};
const myFunc2 = (param: string): MyResponse => {
return {
my: 'my',
response: 'response',
extra: 'extra', // error here
};
}; What are the action items here? |
Having same issue as @yamcodes |
@RyanCavanaugh @DanielRosenwasser I also noticed that when using object spread in the return statement of a function, no error is reported for additional properties that don’t exist in the target type. For example: ✅ Type error is correctly raised here: type MyResponse = {
my: string;
response: string;
};
const myFunc1 = (param: string): MyResponse => {
return {
my: 'my',
response: 'response',
extra: 'extra', // error here, as 'extra' is not part of 'MyResponse'
};
}; ❌ However, no error is raised in the following cases:
const myFunc2 = (param: string): MyResponse => {
const myResponse = {
my: 'my',
response: 'response',
extra: 'extra', // no error here, even though 'extra' is not part of 'MyResponse'
};
return myResponse;
};
const myFunc3 = (param: string): MyResponse => {
const myResponse = {
my: 'my',
response: 'response',
extra: 'extra', // no error here either
};
return { ...myResponse };
}; In both scenarios, TypeScript is allowing extra properties without raising an error even though it knows the final shape is inconsistent with the type annotations. |
Extra properties are allowed in typescript. Excess property checks only apply to the type in effect at the point of declaration (if any). This is generally considered a linting feature to catch typos, not a type correctness feature. Outside that specific scenario, extra properties are not an error. Consider if you wanted to assign a value When the constant declaration is a step removed from where the value is used, EPC does not come into play. |
This change courtesy @JsonFreeman who is trying it out
(context elided: widening of function expression return types)
The problem with this widening is observable in type argument inference and no implicit any. For type argument inference, we are prone to infer an any, where we should not:
So after we get to parity, I propose we do the following. We do not widen function expressions. A function is simply given a function type. However, a function declaration (and a named function expression) introduces a name whose type is the widened form of the type of the function. Very simple to explain and simple to implement.
I’ve been told that this would be a breaking change, because types that used to be any are now more specific types. But here are some reasons why it would be okay:
Questions:
Going with 'not a breaking change' here because this is unlikely to break working code, but we need to verify this.
In essence, we already have two types: The original and the widened type. So by that measure this is not really a change
Jason willing to try it out and report back
The text was updated successfully, but these errors were encountered: