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
35 changes: 23 additions & 12 deletions tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -21893,24 +21893,35 @@ func (c *Checker) getPropertyOfUnionOrIntersectionType(t *Type, name string, ski
// these partial properties when identifying discriminant properties, but otherwise they are filtered out
// and do not appear to be present in the union type.
func (c *Checker) getUnionOrIntersectionProperty(t *Type, name string, skipObjectFunctionPropertyAugment bool) *ast.Symbol {
var cache ast.SymbolTable
data := t.AsUnionOrIntersectionType()
if skipObjectFunctionPropertyAugment {
cache = ast.GetSymbolTable(&t.AsUnionOrIntersectionType().propertyCacheWithoutFunctionPropertyAugment)
if prop := data.propertyCacheWithoutFunctionPropertyAugment[name]; prop != nil {
return prop
}
} else {
cache = ast.GetSymbolTable(&t.AsUnionOrIntersectionType().propertyCache)
}
if prop := cache[name]; prop != nil {
return prop
if data.propertyCache != nil {
if prop := data.propertyCache[name]; prop != nil {
return prop
}
}
// Complete non-augmented properties are also valid for augmented
// lookup. Share the entry instead of retaining it in both tables.
if prop := data.propertyCacheWithoutFunctionPropertyAugment[name]; prop != nil && prop.CheckFlags()&ast.CheckFlagsPartial == 0 {
return prop
}
}
prop := c.createUnionOrIntersectionProperty(t, name, skipObjectFunctionPropertyAugment)
if prop != nil {
cache[name] = prop
// Propagate an entry from the non-augmented cache to the augmented cache unless the property is partial.
if skipObjectFunctionPropertyAugment && prop.CheckFlags()&ast.CheckFlagsPartial == 0 {
augmentedCache := ast.GetSymbolTable(&t.AsUnionOrIntersectionType().propertyCache)
if augmentedCache[name] == nil {
augmentedCache[name] = prop
if skipObjectFunctionPropertyAugment {
ast.GetSymbolTable(&data.propertyCacheWithoutFunctionPropertyAugment)[name] = prop
if prop.CheckFlags()&ast.CheckFlagsPartial == 0 && data.propertyCache[name] == prop {
Comment thread
jakebailey marked this conversation as resolved.
delete(data.propertyCache, name)
if len(data.propertyCache) == 0 {
data.propertyCache = nil
}
}
} else {
ast.GetSymbolTable(&data.propertyCache)[name] = prop
}
}
return prop
Expand Down
46 changes: 46 additions & 0 deletions tsc/internal/checker/checker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package checker_test
import (
"fmt"
"path/filepath"
"strconv"
"strings"
"testing"

Expand Down Expand Up @@ -117,6 +118,51 @@ export type E = D;`,
assert.Equal(t, defaultClause, defaultReference)
}

func TestUnionPropertyFunctionAugmentation(t *testing.T) {
t.Parallel()
for _, lookupFirst := range []bool{false, true} {
t.Run(strconv.FormatBool(lookupFirst), func(t *testing.T) {
t.Parallel()
fs := bundled.WrapFS(vfstest.FromMap(map[string]string{
"/main.ts": `type Callable = { (): string; shared: string };
type Other = { call: number; shared: number };
declare const instance: Callable | Other;
instance;`,
"/function.d.ts": `interface Function {
call(thisArg: any, ...args: any[]): any;
}
interface CallableFunction extends Function {}`,
"/tsconfig.json": `{"compilerOptions":{"strict":true,"noLib":true},"files":["main.ts","function.d.ts"]}`,
}, tspath.CaseInsensitive))
host := compiler.NewCompilerHost(fs, bundled.LibPath(), nil, nil, nil)
parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile("/tsconfig.json", &core.CompilerOptions{}, nil, fs, nil)
assert.Equal(t, len(errors), 0)
program := compiler.NewProgram(compiler.ProgramOptions{Config: parsed, Host: host})
program.BindSourceFiles()
c, done := program.GetTypeChecker(t.Context())
defer done()
file := program.GetSourceFile("/main.ts")
typ := c.GetTypeAtLocation(file.Statements.Nodes[3].Expression())
var call *ast.Symbol
if lookupFirst {
call = c.GetPropertyOfType(typ, "call")
assert.Assert(t, call != nil)
}
properties := c.GetPropertiesOfType(typ)
assert.Equal(t, len(properties), 1)
assert.Equal(t, properties[0].Name(), "shared")
if call == nil {
call = c.GetPropertyOfType(typ, "call")
assert.Assert(t, call != nil)
}
assert.Equal(t, c.GetPropertyOfType(typ, "call"), call)
assert.Equal(t, c.GetPropertyOfType(typ, "shared"), properties[0])
assert.Equal(t, len(c.GetPropertiesOfType(typ)), 1)
assert.Equal(t, len(c.GetDiagnostics(t.Context(), file)), 0)
})
}
}

func BenchmarkNewChecker(b *testing.B) {
fs := bundled.WrapFS(osvfs.FS())
rootPath := tspath.RootedDirectoryPathFromAbsolute(filepath.Join(repo.TestDataPath(), "fixtures/compiler"))
Expand Down
2 changes: 1 addition & 1 deletion tsc/internal/checker/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1362,7 +1362,7 @@ type EvolvingArrayType struct {
type UnionOrIntersectionType struct {
StructuredType
types []*Type
propertyCache ast.SymbolTable
propertyCache ast.SymbolTable // Augmented entries not shared with the non-augmented cache.
propertyCacheWithoutFunctionPropertyAugment ast.SymbolTable
resolvedProperties []*ast.Symbol
}
Expand Down
78 changes: 78 additions & 0 deletions tsc/internal/checker/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,84 @@ func TestCompactValueSymbolLinksLayout(t *testing.T) {
assert.Equal(t, links.getContainingType(), types[2])
}

func TestUnionIntersectionPropertyCacheSharing(t *testing.T) {
t.Parallel()
for _, intersection := range []bool{false, true} {
for _, augmentedFirst := range []bool{false, true} {
t.Run(strconv.FormatBool(intersection)+"/"+strconv.FormatBool(augmentedFirst), func(t *testing.T) {
t.Parallel()
c, _ := newCompositeTestChecker()
members := make(ast.SymbolTable)
for i := range 300 {
name := strconv.Itoa(i)
members[name] = c.newSymbol(ast.SymbolFlagsProperty, name)
}
members[""] = c.newSymbol(ast.SymbolFlagsProperty, "")
object := c.newAnonymousType(nil, members, nil, nil, nil)
var typ *Type
if intersection {
typ = c.newIntersectionType(ObjectFlagsNone, []*Type{object, object})
} else {
typ = c.newUnionType(ObjectFlagsNone, []*Type{object, object})
}
data := typ.AsUnionOrIntersectionType()
for name, symbol := range members {
assert.Equal(t, c.getUnionOrIntersectionProperty(typ, name, !augmentedFirst), symbol)
assert.Equal(t, c.getUnionOrIntersectionProperty(typ, name, augmentedFirst), symbol)
}
assert.Equal(t, len(data.propertyCacheWithoutFunctionPropertyAugment), len(members))
assert.Assert(t, data.propertyCache == nil)
runtime.GC()
for name, symbol := range members {
assert.Equal(t, c.getUnionOrIntersectionProperty(typ, name, true), symbol)
assert.Equal(t, c.getUnionOrIntersectionProperty(typ, name, false), symbol)
}
runtime.KeepAlive(c)
})
}
}
}

func TestUnionIntersectionPropertyCacheModes(t *testing.T) {
t.Parallel()
for _, intersection := range []bool{false, true} {
t.Run(strconv.FormatBool(intersection), func(t *testing.T) {
t.Parallel()
c, _ := newCompositeTestChecker()
var typ *Type
if intersection {
typ = c.newIntersectionType(ObjectFlagsNone, nil)
} else {
typ = c.newUnionType(ObjectFlagsNone, nil)
}
data := typ.AsUnionOrIntersectionType()
assert.Assert(t, c.getUnionOrIntersectionProperty(typ, "missing", true) == nil)
assert.Assert(t, c.getUnionOrIntersectionProperty(typ, "missing", false) == nil)
assert.Assert(t, data.propertyCache == nil)
assert.Assert(t, data.propertyCacheWithoutFunctionPropertyAugment == nil)
data.propertyCacheWithoutFunctionPropertyAugment = make(ast.SymbolTable)
data.propertyCache = make(ast.SymbolTable)
for _, flags := range []ast.CheckFlags{ast.CheckFlagsReadPartial, ast.CheckFlagsWritePartial} {
name := strconv.Itoa(int(flags))
partial := c.newSymbolEx(ast.SymbolFlagsProperty, name, flags)
complete := c.newSymbol(ast.SymbolFlagsProperty, name)
data.propertyCacheWithoutFunctionPropertyAugment[name] = partial
assert.Equal(t, c.getUnionOrIntersectionProperty(typ, name, true), partial)
assert.Assert(t, c.getUnionOrIntersectionProperty(typ, name, false) == nil)
assert.Equal(t, c.getPropertyOfUnionOrIntersectionType(typ, name, true) == nil, flags&ast.CheckFlagsReadPartial != 0)
data.propertyCache[name] = complete
assert.Equal(t, c.getUnionOrIntersectionProperty(typ, name, false), complete)
}
nonAugmented := c.newSymbol(ast.SymbolFlagsProperty, "different")
augmented := c.newSymbol(ast.SymbolFlagsProperty, "different")
data.propertyCacheWithoutFunctionPropertyAugment["different"] = nonAugmented
data.propertyCache["different"] = augmented
assert.Equal(t, c.getUnionOrIntersectionProperty(typ, "different", true), nonAugmented)
assert.Equal(t, c.getUnionOrIntersectionProperty(typ, "different", false), augmented)
})
}
}

func TestCompactStructuredTypeLayout(t *testing.T) {
t.Parallel()
assert.Equal(t, unsafe.Sizeof(StructuredType{}), unsafe.Sizeof(ConstrainedType{})+5*unsafe.Sizeof(uintptr(0)))
Expand Down
Loading