TypeScript
TypeScript copied to clipboard
Error not issued when global type is an alias of an object type literal
🔎 Search Terms
array, constructor, bracket
🕗 Version & Regression Information
- This is a crash
⏯ Playground Link
https://www.typescriptlang.org/dev/bug-workbench/?noLib=true&target=99&ts=5.5.0-dev.20240227#code/PTAEAEDsHsBkEsBGAuUAXATgVwKYCgQI0BDDAcxzVRwGdIcAPNPPAExwGMAbUnUMrtETEuoAN55QoDtEg00oAIIYMxAJ6oJUqQB4AKqAC8oLJADWMAO6QAfAAoAdE9JkayYpDUBtALoBKVGVVNX0bSW16S1B9IxNzK0gAGnsnBxc3D29-QJV1UPCAXxYpNDUABz4gvL0bWK1tL3hUSCwAW0QcDB9UPXCpLhxIMjQAC2a2joxCvCKCMBFRaAAzdBG+DsEosngANz4AIgAFDGgKjFLQAHIBodHL0HgaUFbHmnghh8h0cr5LsQLLql9sg8ABuUB2LwARkSoAATLCAMz+UA0YhoR5LeC0JS5EItdqdGygsEQ6GwhGgZF+VHozHYp5iUA3YZjUAEyagAok9jcXjSWTyUCkKHNHB7DC+Uki2kYmhYnFMlmjcaEjBcnmcHgYPgyOQKUhw1WTKXgw2y+mK5mDVnGzpclhzVaPVEjaBYLisVYnKIeUCdE7qxBYBTwBSsaC0SCXZiEcBoGgAWkYFQ4aGTKmgU3BkJ8wqeHM6vhpaLlCqe8gw7zIUrwKazCiZBSAA
💻 Code
// @noLib: true
// @target: esnext
declare global {
const Array: {
<T = unknown>(...args:any[]): Array<T>
new <T = unknown,>(...args:any[]): Array<T>
}
type Array<T> = {
[i: number]: T
length: number
}
}
// all of the below give "Property 'length' is missing in type '{}'...":
; ([1, 2, 3]) satisfies Array<number>;
; ([1, 2, 3]) satisfies { length: number };
declare const ar1: never[]
; ar1 satisfies { length: number };
declare const ar2: number[]
; ar2 satisfies { length: number }
// this should throw an error but it doesn't
// @ts-expect-error
; ([] as number[]) satisfies string[]
export { }
🙁 Actual behavior
Types created with the T[] and objects created with the [x as T] syntax are not typed as Array<T> but instead as {}.
🙂 Expected behavior
I expect either:
T[]syntax andArray<T>to be completely synonymous- A compiler error that the
Arraytype is not an instantiable type
Additional information about the issue
Related to #57009.
This is not supported. Array<T> must be an interface.
You're the only person trying to do this, please stop 😅
This is not supported.
Array<T>must be an interface.You're the only person trying to do this, please stop 😅
lol - gotcha. Wasn't sure if "this isn't going to work" was referring to the circular definition or using a type alias. Now I know!
I'm suprised there's not a compiler error - it seems like checker.ts should be checking the presence and the arity of the global Array type. There's even a check in there (that doesn't seem to be working) that global types are declared as a class or interface.
globalArrayType = getGlobalType("Array" as __String, /*arity*/ 1, /*reportErrors*/ true);
// ...
function getGlobalType(name: __String, arity: number, reportErrors: boolean): ObjectType | undefined {
const symbol = getGlobalTypeSymbol(name, reportErrors);
return symbol || reportErrors ? getTypeOfGlobalSymbol(symbol, arity) : undefined;
}
// ...
function getTypeOfGlobalSymbol(symbol: Symbol | undefined, arity: number): ObjectType {
function getTypeDeclaration(symbol: Symbol): Declaration | undefined {
const declarations = symbol.declarations;
if (declarations) {
for (const declaration of declarations) {
switch (declaration.kind) {
case SyntaxKind.ClassDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.EnumDeclaration:
return declaration;
}
}
}
}
if (!symbol) {
return arity ? emptyGenericType : emptyObjectType;
}
const type = getDeclaredTypeOfSymbol(symbol);
if (!(type.flags & TypeFlags.Object)) {
error(getTypeDeclaration(symbol), Diagnostics.Global_type_0_must_be_a_class_or_interface_type, symbolName(symbol));
return arity ? emptyGenericType : emptyObjectType;
}
if (length((type as InterfaceType).typeParameters) !== arity) {
error(getTypeDeclaration(symbol), Diagnostics.Global_type_0_must_have_1_type_parameter_s, symbolName(symbol), arity);
return arity ? emptyGenericType : emptyObjectType;
}
return type as ObjectType;
}
Good catch; I think we changed how eagerly type aliases are resolved in some cases and broke that check.
Gonna hijack this one
It looks like we created an unintentional dependency on this codepath not flagging type aliases when we added PromiseConstructorLike, which is defined as
declare type PromiseConstructorLike = new <T>(executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void) => PromiseLike<T>;
Maybe just telling people to not do this when they try to is sufficient; this function is now quite a bit indirected from different call sites that do/do not have the class/interface distinction.
:wave: Hi, I'm the Repro bot. I can help narrow down and track compiler bugs across releases! This comment reflects the current state of the repro in the issue body running against the nightly TypeScript.
Issue body code block by @rotu
:x: Failed: -
Type '{}' does not satisfy the expected type 'Array'. Property 'length' is missing in type '{}' but required in type 'Array '. Type '{}' does not satisfy the expected type '{ length: number; }'. Property 'length' is missing in type '{}' but required in type '{ length: number; }'.Type '{}' does not satisfy the expected type '{ length: number; }'. Property 'length' is missing in type '{}' but required in type '{ length: number; }'.Type '{}' does not satisfy the expected type '{ length: number; }'. Property 'length' is missing in type '{}' but required in type '{ length: number; }'.Unused '@ts-expect-error' directive.
Historical Information
| Version | Reproduction Outputs |
|---|---|
| 4.9.3, 5.0.2, 5.1.3, 5.2.2, 5.3.2 |
:x: Failed: -
|