microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.31k stars 12.39k forks source link

Change return type of `Object` call signature to `unknown` (currently `any`) #57379

Open szacskesz opened 7 months ago

szacskesz commented 7 months ago

πŸ”Ž Search Terms

ObjectConstructor, Object, assignable, function

πŸ•— Version & Regression Information

⏯ Playground Link

https://www.typescriptlang.org/play?#code/CYUwxgNghgTiAEAzArgOzAFwJYHtVJxwAowAjALniIEp4BeAPngGcMYtUBzaygNxyzAA3ACgRiQjSEB6afECg5PACiADwAO4DCGDw4zZBAyUQMGDhjjJNekwBEt6jLmLVGzNt0h9hyqhzwTMwsJYmtGeFQDCEdZBWV1TQ89AyMA03NLYgBvAF9qWJcE9x1knzSgzKIAZTYObic410SSrxTff0CMkKIAOWQAW1ITGOd4ty0W71TOi0qAeVIAK00R+EAZcjHmzynjdIsgA

πŸ’» Code

declare function foo(cb: () => string): void;

foo();// βœ… Expected result: error
foo(() => "");// βœ… Expected result: no error
foo(() => null);// βœ… Expected result: error
foo({})// βœ… Expected result: error
foo(String);// βœ… Expected result: no error
foo(Number);// βœ… Expected result: error

foo(Object);// ❌ Expected result: error

πŸ™ Actual behavior

Object (ObjectContructor) is assignable to any function, because it has a callable signature with return type of any.

This lets the developers to use Object where only a properly typed function would be allowed, which can lead to unexpected runtime errors.

I know this is a pretty edge case, but in my opinion it's worth looking into.

πŸ™‚ Expected behavior

Object is not assignable to functions with different return type

Additional information about the issue

No response

RyanCavanaugh commented 7 months ago

It's declared as having a (): any; call signature since the definition predates the existence of unknown/object and types deriving explicitly from Object are obviously free to be ~anything. I don't see much upside in changing that -- this is basically the only observable effect of that.

szacskesz commented 7 months ago

Wouldn't it be better to have an unknown return type for the callable signature (): unknown;, and let the descendants specify it more if they want? To me it makes more sense, and it would be consistent with the Do's and Don'ts you wrote about declaration files too:

In cases where you don’t know what type you want to accept, or when you want to accept anything because you will be blindly passing it through without interacting with it, you can use unknown.

rotu commented 7 months ago

I think object would be more appropriate than unknown. The return type of Object is known to not be a primitive type and can be used where an Object is required, e.g. as the key of a WeakMap. See also e.g. #57000

Also, it's confusing that Object() and new Object() have different return types. Not sure if intentional.

matAtWork commented 1 month ago

Can I suggest more strictly typing this as:

type Boxed<T> =
    T extends number ? Number :
    T extends string ? String :
    T extends boolean ? Boolean :
    T extends bigint ? BigInt :
    T;

declare global {
    interface ObjectConstructor {
          assign<T extends {}, U>(target: T, source: U): Boxed<T> & U;
          new <T>(value?: T): Boxed<T>;
          <T>(value: T): Boxed<T>;
      }
}

This mirrors how all mainstream JS engines actually handle primitive types passed to Object(p) and Object.assign(p,q), and avoids a problem with type checking here: https://github.com/samchon/typia/issues/911#issuecomment-2288613380

It's more than possible my basic augmentation is incomplete, but any is certainly a poor choice for modelling the actual behaviour of these expressions.