Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 35 additions & 7 deletions internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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.
Expand Down Expand Up @@ -23575,13 +23572,44 @@ func (c *Checker) getArrayOrTupleTargetType(node *ast.Node) *Type {
elementType := c.getArrayElementTypeNode(node)
if elementType != nil {
if readonly {
return c.globalReadonlyArrayType
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% sure about this fix so I refrained myself from doing more changes to reduce the noise.

If the fix gets accepted, I think it would be best to update:

  • all other raw c.globalReadonlyArrayType/c.globalArrayType to use the introduced getters
  • likely do the same with other global types that are referenced directly and not through getters

I noticed that in Strada more types were retrieved through such caching getters but that pattern seems to be gone from Corsa so perhaps it's something you want to actively avoid.

That said, Strada doesn't use this pattern for those global array types and it's prone to the very same crash

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
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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", "")
}
Original file line number Diff line number Diff line change
@@ -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<T> {
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();

Original file line number Diff line number Diff line change
@@ -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<T> {
>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))

Original file line number Diff line number Diff line change
@@ -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<T> {
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

Original file line number Diff line number Diff line change
@@ -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<T> {
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();