diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 0959df0642..66e4a6f858 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -1313,7 +1313,7 @@ func (c *Checker) initializeChecker() { c.valueSymbolLinks.Get(c.unknownSymbol).resolvedType = c.errorType c.valueSymbolLinks.Get(c.globalThisSymbol).resolvedType = c.newObjectType(ObjectFlagsAnonymous, c.globalThisSymbol) // Initialize special types - c.globalArrayType = c.getGlobalType("Array", 1 /*arity*/, true /*reportErrors*/) + c.getGlobalArrayType() c.globalObjectType = c.getGlobalType("Object", 0 /*arity*/, true /*reportErrors*/) c.globalFunctionType = c.getGlobalType("Function", 0 /*arity*/, true /*reportErrors*/) c.globalCallableFunctionType = c.getGlobalStrictFunctionType("CallableFunction") @@ -1328,10 +1328,7 @@ func (c *Checker) initializeChecker() { // autoArrayType is used as a marker, so even if global Array type is not defined, it needs to be a unique type c.autoArrayType = c.newAnonymousType(nil, nil, nil, nil, nil) } - c.globalReadonlyArrayType = c.getGlobalType("ReadonlyArray", 1 /*arity*/, false /*reportErrors*/) - if c.globalReadonlyArrayType == c.emptyGenericType { - c.globalReadonlyArrayType = c.globalArrayType - } + c.getGlobalReadonlyArrayType() c.anyReadonlyArrayType = c.createTypeFromGenericGlobalType(c.globalReadonlyArrayType, []*Type{c.anyType}) c.globalThisType = c.getGlobalType("ThisType", 1 /*arity*/, false /*reportErrors*/) // merge _nonglobal_ module augmentations. @@ -23575,13 +23572,44 @@ func (c *Checker) getArrayOrTupleTargetType(node *ast.Node) *Type { elementType := c.getArrayElementTypeNode(node) if elementType != nil { if readonly { - return c.globalReadonlyArrayType + return c.getGlobalReadonlyArrayType() } - return c.globalArrayType + return c.getGlobalArrayType() } return c.getTupleTargetType(core.Map(node.Elements(), c.getTupleElementInfo), readonly) } +func (c *Checker) ensureGlobalSymbolTransient(name string) { + if sym, ok := c.globals[name]; ok && sym.Flags&ast.SymbolFlagsTransient == 0 { + cloned := c.cloneSymbol(sym) + for memberName, memberSym := range cloned.Members { + if memberSym.Flags&ast.SymbolFlagsTypeParameter != 0 && memberSym.Flags&ast.SymbolFlagsTransient == 0 { + cloned.Members[memberName] = c.cloneSymbol(memberSym) + } + } + c.globals[name] = cloned + } +} + +func (c *Checker) getGlobalArrayType() *Type { + if c.globalArrayType == nil { + c.ensureGlobalSymbolTransient("Array") + c.globalArrayType = c.getGlobalType("Array", 1 /*arity*/, true /*reportErrors*/) + } + return c.globalArrayType +} + +func (c *Checker) getGlobalReadonlyArrayType() *Type { + if c.globalReadonlyArrayType == nil { + c.ensureGlobalSymbolTransient("ReadonlyArray") + c.globalReadonlyArrayType = c.getGlobalType("ReadonlyArray", 1 /*arity*/, false /*reportErrors*/) + if c.globalReadonlyArrayType == c.emptyGenericType { + c.globalReadonlyArrayType = c.getGlobalArrayType() + } + } + return c.globalReadonlyArrayType +} + func (c *Checker) isReadonlyTypeOperator(node *ast.Node) bool { return ast.IsTypeOperatorNode(node) && node.AsTypeOperatorNode().Operator == ast.KindReadonlyKeyword } diff --git a/internal/fourslash/tests/quickInfoAmbientModuleMergeWithReexportNoCrash1_test.go b/internal/fourslash/tests/quickInfoAmbientModuleMergeWithReexportNoCrash1_test.go new file mode 100644 index 0000000000..d367783efe --- /dev/null +++ b/internal/fourslash/tests/quickInfoAmbientModuleMergeWithReexportNoCrash1_test.go @@ -0,0 +1,26 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestQuickInfoAmbientModuleMergeWithReexportNoCrash1(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /node_modules/foo/index.d.ts +declare function foo(): void; +declare namespace foo { export const items: string[]; } +export = foo; +// @Filename: /a.d.ts +declare module 'mymod' { import * as foo from 'foo'; export { foo }; } +// @Filename: /b.d.ts +declare module 'mymod' { export const foo: number; } +// @Filename: /index.ts +const x/*m1*/ = 1;` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + f.VerifyQuickInfoAt(t, "m1", "const x: 1", "") +} diff --git a/testdata/baselines/reference/compiler/globalArrayAugmentationWithAmbientModuleReexportMerge1.errors.txt b/testdata/baselines/reference/compiler/globalArrayAugmentationWithAmbientModuleReexportMerge1.errors.txt new file mode 100644 index 0000000000..971e59cf1c --- /dev/null +++ b/testdata/baselines/reference/compiler/globalArrayAugmentationWithAmbientModuleReexportMerge1.errors.txt @@ -0,0 +1,37 @@ +/a.d.ts(1,63): error TS2451: Cannot redeclare block-scoped variable 'foo'. +/b.d.ts(1,39): error TS2451: Cannot redeclare block-scoped variable 'foo'. + + +==== /node_modules/foo/index.d.ts (0 errors) ==== + declare function foo(): void; + declare namespace foo { export const items: string[]; } + export = foo; + +==== /a.d.ts (1 errors) ==== + declare module 'mymod' { import * as foo from 'foo'; export { foo }; } + ~~~ +!!! error TS2451: Cannot redeclare block-scoped variable 'foo'. +!!! related TS6203 /b.d.ts:1:39: 'foo' was also declared here. + +==== /b.d.ts (1 errors) ==== + declare module 'mymod' { export const foo: number; } + ~~~ +!!! error TS2451: Cannot redeclare block-scoped variable 'foo'. +!!! related TS6203 /a.d.ts:1:63: 'foo' was also declared here. + +==== /augment.ts (0 errors) ==== + declare global { + interface Array { + customMethod(): T; + } + } + export {}; + +==== /index.ts (0 errors) ==== + import * as foo from 'foo'; + const items = foo.items; + const result: string = items.customMethod(); + + const fresh: string[] = []; + const result2: string = fresh.customMethod(); + \ No newline at end of file diff --git a/testdata/baselines/reference/compiler/globalArrayAugmentationWithAmbientModuleReexportMerge1.symbols b/testdata/baselines/reference/compiler/globalArrayAugmentationWithAmbientModuleReexportMerge1.symbols new file mode 100644 index 0000000000..437fbd42e2 --- /dev/null +++ b/testdata/baselines/reference/compiler/globalArrayAugmentationWithAmbientModuleReexportMerge1.symbols @@ -0,0 +1,64 @@ +//// [tests/cases/compiler/globalArrayAugmentationWithAmbientModuleReexportMerge1.ts] //// + +=== /node_modules/foo/index.d.ts === +declare function foo(): void; +>foo : Symbol(foo, Decl(index.d.ts, 0, 0), Decl(index.d.ts, 0, 29)) + +declare namespace foo { export const items: string[]; } +>foo : Symbol(foo, Decl(index.d.ts, 0, 0), Decl(index.d.ts, 0, 29)) +>items : Symbol(items, Decl(index.d.ts, 1, 36)) + +export = foo; +>foo : Symbol(foo, Decl(index.d.ts, 0, 0), Decl(index.d.ts, 0, 29)) + +=== /a.d.ts === +declare module 'mymod' { import * as foo from 'foo'; export { foo }; } +>'mymod' : Symbol("mymod", Decl(a.d.ts, 0, 0), Decl(b.d.ts, 0, 0)) +>foo : Symbol(foo, Decl(a.d.ts, 0, 31)) +>foo : Symbol(foo, Decl(a.d.ts, 0, 61)) + +=== /b.d.ts === +declare module 'mymod' { export const foo: number; } +>'mymod' : Symbol("mymod", Decl(a.d.ts, 0, 0), Decl(b.d.ts, 0, 0)) +>foo : Symbol(foo, Decl(b.d.ts, 0, 37)) + +=== /augment.ts === +declare global { +>global : Symbol(global, Decl(augment.ts, 0, 0)) + + interface Array { +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(augment.ts, 0, 16)) +>T : Symbol(T, Decl(lib.es5.d.ts, --, --), Decl(augment.ts, 1, 20)) + + customMethod(): T; +>customMethod : Symbol(Array.customMethod, Decl(augment.ts, 1, 24)) +>T : Symbol(T, Decl(lib.es5.d.ts, --, --), Decl(augment.ts, 1, 20)) + } +} +export {}; + +=== /index.ts === +import * as foo from 'foo'; +>foo : Symbol(foo, Decl(index.ts, 0, 6)) + +const items = foo.items; +>items : Symbol(items, Decl(index.ts, 1, 5)) +>foo.items : Symbol(items, Decl(index.d.ts, 1, 36)) +>foo : Symbol(foo, Decl(index.ts, 0, 6)) +>items : Symbol(items, Decl(index.d.ts, 1, 36)) + +const result: string = items.customMethod(); +>result : Symbol(result, Decl(index.ts, 2, 5)) +>items.customMethod : Symbol(Array.customMethod, Decl(augment.ts, 1, 24)) +>items : Symbol(items, Decl(index.ts, 1, 5)) +>customMethod : Symbol(Array.customMethod, Decl(augment.ts, 1, 24)) + +const fresh: string[] = []; +>fresh : Symbol(fresh, Decl(index.ts, 4, 5)) + +const result2: string = fresh.customMethod(); +>result2 : Symbol(result2, Decl(index.ts, 5, 5)) +>fresh.customMethod : Symbol(Array.customMethod, Decl(augment.ts, 1, 24)) +>fresh : Symbol(fresh, Decl(index.ts, 4, 5)) +>customMethod : Symbol(Array.customMethod, Decl(augment.ts, 1, 24)) + diff --git a/testdata/baselines/reference/compiler/globalArrayAugmentationWithAmbientModuleReexportMerge1.types b/testdata/baselines/reference/compiler/globalArrayAugmentationWithAmbientModuleReexportMerge1.types new file mode 100644 index 0000000000..4b3f16b380 --- /dev/null +++ b/testdata/baselines/reference/compiler/globalArrayAugmentationWithAmbientModuleReexportMerge1.types @@ -0,0 +1,63 @@ +//// [tests/cases/compiler/globalArrayAugmentationWithAmbientModuleReexportMerge1.ts] //// + +=== /node_modules/foo/index.d.ts === +declare function foo(): void; +>foo : typeof foo + +declare namespace foo { export const items: string[]; } +>foo : typeof foo +>items : string[] + +export = foo; +>foo : typeof foo + +=== /a.d.ts === +declare module 'mymod' { import * as foo from 'foo'; export { foo }; } +>'mymod' : typeof import("mymod") +>foo : typeof foo +>foo : typeof foo + +=== /b.d.ts === +declare module 'mymod' { export const foo: number; } +>'mymod' : typeof import("mymod") +>foo : number + +=== /augment.ts === +declare global { +>global : any + + interface Array { + customMethod(): T; +>customMethod : () => T + } +} +export {}; + +=== /index.ts === +import * as foo from 'foo'; +>foo : typeof foo + +const items = foo.items; +>items : string[] +>foo.items : string[] +>foo : typeof foo +>items : string[] + +const result: string = items.customMethod(); +>result : string +>items.customMethod() : string +>items.customMethod : () => string +>items : string[] +>customMethod : () => string + +const fresh: string[] = []; +>fresh : string[] +>[] : never[] + +const result2: string = fresh.customMethod(); +>result2 : string +>fresh.customMethod() : string +>fresh.customMethod : () => string +>fresh : string[] +>customMethod : () => string + diff --git a/testdata/tests/cases/compiler/globalArrayAugmentationWithAmbientModuleReexportMerge1.ts b/testdata/tests/cases/compiler/globalArrayAugmentationWithAmbientModuleReexportMerge1.ts new file mode 100644 index 0000000000..f48fa008ed --- /dev/null +++ b/testdata/tests/cases/compiler/globalArrayAugmentationWithAmbientModuleReexportMerge1.ts @@ -0,0 +1,30 @@ +// @target: es2015 +// @lib: es5 +// @noEmit: true + +// @Filename: /node_modules/foo/index.d.ts +declare function foo(): void; +declare namespace foo { export const items: string[]; } +export = foo; + +// @Filename: /a.d.ts +declare module 'mymod' { import * as foo from 'foo'; export { foo }; } + +// @Filename: /b.d.ts +declare module 'mymod' { export const foo: number; } + +// @Filename: /augment.ts +declare global { + interface Array { + customMethod(): T; + } +} +export {}; + +// @Filename: /index.ts +import * as foo from 'foo'; +const items = foo.items; +const result: string = items.customMethod(); + +const fresh: string[] = []; +const result2: string = fresh.customMethod();