Skip to content

Commit f0a448f

Browse files
committed
perf: improve performance by doument cache and precomputed scopes
1 parent daf3931 commit f0a448f

File tree

12 files changed

+358
-68
lines changed

12 files changed

+358
-68
lines changed

packages/shared/src/tree/hierarchy-tree.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export class HierarchyNode<V> {
3737
readonly parent?: HierarchyNode<V>
3838
readonly children: Map<string, HierarchyNode<V>>
3939
readonly data: Set<V>
40+
syntehticData: V | undefined
4041

4142
constructor(name: string, parent?: HierarchyNode<V>) {
4243
this.name = name

packages/zenscript/src/module.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { ZenScriptScopeProvider } from './reference/scope-provider'
1616
import { ZenScriptTypeComputer } from './typing/type-computer'
1717
import { registerValidationChecks, ZenScriptValidator } from './validation/validator'
1818
import { ZenScriptBracketManager } from './workspace/bracket-manager'
19+
import { ZenScriptClassIndex } from './workspace/class-index'
1920
import { ZenScriptConfigurationManager } from './workspace/configuration-manager'
2021
import { ZenScriptPackageManager } from './workspace/package-manager'
2122
import { ZenScriptWorkspaceManager } from './workspace/workspace-manager'
@@ -37,6 +38,7 @@ export interface ZenScriptAddedServices {
3738
workspace: {
3839
PackageManager: ZenScriptPackageManager
3940
BracketManager: ZenScriptBracketManager
41+
ClassIndex: ZenScriptClassIndex
4042
}
4143
}
4244

@@ -74,6 +76,7 @@ export const ZenScriptModule: Module<ZenScriptServices, PartialLangiumServices &
7476
workspace: {
7577
PackageManager: services => new ZenScriptPackageManager(services),
7678
BracketManager: services => new ZenScriptBracketManager(services),
79+
ClassIndex: services => new ZenScriptClassIndex(services),
7780
},
7881
parser: {
7982
TokenBuilder: () => new CustomTokenBuilder(),

packages/zenscript/src/reference/dynamic-provider.ts

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import type { AstNode, AstNodeDescription, AstNodeDescriptionProvider } from 'la
22
import type { ZenScriptAstType } from '../generated/ast'
33
import type { ZenScriptServices } from '../module'
44
import type { TypeComputer } from '../typing/type-computer'
5+
import type { ZenScriptClassIndex } from '../workspace/class-index'
56
import type { MemberProvider } from './member-provider'
67
import { AstUtils, stream } from 'langium'
7-
import { isCallExpression, isClassDeclaration, isFunctionDeclaration, isOperatorFunctionDeclaration } from '../generated/ast'
8+
import { isCallExpression, isClassDeclaration, isFunctionDeclaration } from '../generated/ast'
89
import { isClassType, isFunctionType } from '../typing/type-description'
910

1011
export interface DynamicProvider {
@@ -18,11 +19,13 @@ export class ZenScriptDynamicProvider implements DynamicProvider {
1819
private readonly descriptions: AstNodeDescriptionProvider
1920
private readonly typeComputer: TypeComputer
2021
private readonly memberProvider: MemberProvider
22+
private readonly classIndex: ZenScriptClassIndex
2123

2224
constructor(services: ZenScriptServices) {
2325
this.descriptions = services.workspace.AstNodeDescriptionProvider
2426
this.typeComputer = services.typing.TypeComputer
2527
this.memberProvider = services.references.MemberProvider
28+
this.classIndex = services.workspace.ClassIndex
2629
}
2730

2831
getDynamics(source: AstNode): AstNodeDescription[] {
@@ -37,7 +40,7 @@ export class ZenScriptDynamicProvider implements DynamicProvider {
3740
// dynamic this
3841
const classDecl = AstUtils.getContainerOfType(source, isClassDeclaration)
3942
if (classDecl) {
40-
dynamics.push(this.descriptions.createDescription(classDecl, 'this'))
43+
dynamics.push(this.classIndex.get(classDecl).thisSymbol)
4144
}
4245

4346
// dynamic arguments
@@ -48,11 +51,10 @@ export class ZenScriptDynamicProvider implements DynamicProvider {
4851
const paramType = receiverType.paramTypes[index]
4952
if (isClassType(paramType)) {
5053
stream(this.memberProvider.getMembers(paramType.declaration))
51-
.map(it => it.node)
52-
.filter(it => isFunctionDeclaration(it))
53-
.filter(it => it.prefix === 'static')
54-
.filter(it => it.parameters.length === 0)
55-
.forEach(it => dynamics.push(this.descriptions.createDescription(it, it.name)))
54+
.filter(it => isFunctionDeclaration(it.node)
55+
&& it.node.prefix === 'static'
56+
&& it.node.parameters.length === 0)
57+
.forEach(it => dynamics.push(it))
5658
}
5759
}
5860
}
@@ -63,14 +65,14 @@ export class ZenScriptDynamicProvider implements DynamicProvider {
6365
MemberAccess: (source) => {
6466
const dynamics: AstNodeDescription[] = []
6567

66-
// dynamic member
67-
const operatorDecl = stream(this.memberProvider.getMembers(source.receiver))
68-
.map(it => it.node)
69-
.filter(it => isOperatorFunctionDeclaration(it))
70-
.filter(it => it.parameters.length === 1)
71-
.find(it => it.op === '.')
72-
if (operatorDecl) {
73-
dynamics.push(this.descriptions.createDescription(operatorDecl.parameters[0], source.target.$refText))
68+
const receiverType = this.typeComputer.inferType(source.receiver)
69+
70+
if (isClassType(receiverType)) {
71+
// dynamic member
72+
const operatorDecl = this.classIndex.findOperators(receiverType.declaration, '.').at(0)
73+
if (operatorDecl) {
74+
dynamics.push(this.descriptions.createDescription(operatorDecl.parameters[0], source.target.$refText))
75+
}
7476
}
7577

7678
return dynamics

packages/zenscript/src/reference/member-provider.ts

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,54 @@
1-
import type { AstNode, AstNodeDescription, AstNodeDescriptionProvider } from 'langium'
1+
import type { AstNode, AstNodeDescription, AstNodeDescriptionProvider, Stream } from 'langium'
22
import type { ZenScriptAstType } from '../generated/ast'
33
import type { ZenScriptServices } from '../module'
44
import type { TypeComputer } from '../typing/type-computer'
5+
import type { ZenScriptClassIndex } from '../workspace/class-index'
6+
import type { PackageManager } from '../workspace/package-manager'
57
import type { ZenScriptSyntheticAstType } from './synthetic'
6-
import { stream } from 'langium'
8+
import { AstUtils, stream } from 'langium'
79
import { isClassDeclaration, isVariableDeclaration } from '../generated/ast'
810
import { ClassType, isAnyType, isClassType, isFunctionType, type Type, type ZenScriptType } from '../typing/type-description'
9-
import { getClassChain, isStatic } from '../utils/ast'
10-
import { createSyntheticAstNodeDescription, isSyntheticAstNode } from './synthetic'
11+
import { getPrecomputedDescription } from '../utils/document'
12+
import { isSyntheticAstNode } from './synthetic'
1113

1214
export interface MemberProvider {
13-
getMembers: (source: AstNode | Type | undefined) => AstNodeDescription[]
15+
getMembers: (source: AstNode | Type | undefined) => Stream<AstNodeDescription>
1416
}
1517

1618
type SourceMap = ZenScriptAstType & ZenScriptType & ZenScriptSyntheticAstType
17-
type RuleMap = { [K in keyof SourceMap]?: (source: SourceMap[K]) => AstNodeDescription[] }
19+
type RuleMap = { [K in keyof SourceMap]?: (source: SourceMap[K]) => Stream<AstNodeDescription> }
1820

1921
export class ZenScriptMemberProvider implements MemberProvider {
2022
private readonly descriptions: AstNodeDescriptionProvider
2123
private readonly typeComputer: TypeComputer
24+
private readonly classIndex: ZenScriptClassIndex
25+
private readonly packageManager: PackageManager
2226

2327
constructor(services: ZenScriptServices) {
2428
this.descriptions = services.workspace.AstNodeDescriptionProvider
2529
this.typeComputer = services.typing.TypeComputer
30+
this.classIndex = services.workspace.ClassIndex
31+
this.packageManager = services.workspace.PackageManager
2632
}
2733

28-
getMembers(source: AstNode | Type | undefined): AstNodeDescription[] {
34+
getMembers(source: AstNode | Type | undefined): Stream<AstNodeDescription> {
2935
// @ts-expect-error allowed index type
30-
return this.rules[source?.$type]?.call(this, source) ?? []
36+
return this.rules[source?.$type]?.call(this, source) ?? stream()
3137
}
3238

3339
private readonly rules: RuleMap = {
3440
SyntheticHierarchyNode: (source) => {
3541
const declarations = stream(source.children.values())
3642
.filter(it => it.isDataNode())
3743
.flatMap(it => it.data)
38-
.map(it => this.descriptions.createDescription(it, undefined))
44+
.map((it) => {
45+
const document = AstUtils.getDocument(it)
46+
return getPrecomputedDescription(document, it)
47+
})
3948
const packages = stream(source.children.values())
4049
.filter(it => it.isInternalNode())
41-
.map(it => createSyntheticAstNodeDescription('SyntheticHierarchyNode', it.name, it))
42-
return stream(declarations, packages).toArray()
50+
.map(it => this.packageManager.syntheticDescriptionOf(it))
51+
return stream(declarations, packages)
4352
},
4453

4554
Script: (source) => {
@@ -49,18 +58,21 @@ export class ZenScriptMemberProvider implements MemberProvider {
4958
source.statements.filter(it => isVariableDeclaration(it))
5059
.filter(it => it.prefix === 'static')
5160
.forEach(it => members.push(it))
52-
return members.map(it => this.descriptions.createDescription(it, undefined))
61+
return stream(members
62+
.map((it) => {
63+
const document = AstUtils.getDocument(it)
64+
return getPrecomputedDescription(document, it)
65+
}))
5366
},
5467

5568
ImportDeclaration: (source) => {
5669
return this.getMembers(source.path.at(-1)?.ref)
5770
},
5871

5972
ClassDeclaration: (source) => {
60-
return getClassChain(source)
61-
.flatMap(it => it.members)
62-
.filter(it => isStatic(it))
63-
.map(it => this.descriptions.createDescription(it, undefined))
73+
const index = this.classIndex.get(source)
74+
75+
return index.streamDescriptions(true)
6476
},
6577

6678
VariableDeclaration: (source) => {
@@ -81,7 +93,7 @@ export class ZenScriptMemberProvider implements MemberProvider {
8193
MemberAccess: (source) => {
8294
const target = source.target.ref
8395
if (!target) {
84-
return []
96+
return stream()
8597
}
8698

8799
if (isSyntheticAstNode(target)) {
@@ -120,7 +132,7 @@ export class ZenScriptMemberProvider implements MemberProvider {
120132
if (isAnyType(receiverType)) {
121133
return this.getMembers(receiverType)
122134
}
123-
return []
135+
return stream()
124136
},
125137

126138
BracketExpression: (source) => {
@@ -159,10 +171,8 @@ export class ZenScriptMemberProvider implements MemberProvider {
159171
},
160172

161173
ClassType: (source) => {
162-
return getClassChain(source.declaration)
163-
.flatMap(it => it.members)
164-
.filter(it => !isStatic(it))
165-
.map(it => this.descriptions.createDescription(it, undefined))
174+
const index = this.classIndex.get(source.declaration)
175+
return index.streamDescriptions(false)
166176
},
167177
}
168178
}

packages/zenscript/src/reference/scope-computation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { AstNode, AstNodeDescription, LangiumDocument } from 'langium'
1+
import type { AstNode, AstNodeDescription, LangiumDocument, PrecomputedScopes } from 'langium'
22
import type { Script } from '../generated/ast'
33
import type { ZenScriptServices } from '../module'
44
import { DefaultScopeComputation } from 'langium'

packages/zenscript/src/reference/scope-provider.ts

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import type { AstNode, AstNodeDescription, ReferenceInfo, Scope } from 'langium'
22
import type { ZenScriptAstType } from '../generated/ast'
33
import type { ZenScriptServices } from '../module'
4+
import type { ZenScriptClassIndex } from '../workspace/class-index'
45
import type { PackageManager } from '../workspace/package-manager'
56
import type { DynamicProvider } from './dynamic-provider'
67
import type { MemberProvider } from './member-provider'
78
import { substringBeforeLast } from '@intellizen/shared'
8-
import { AstUtils, DefaultScopeProvider, EMPTY_SCOPE, stream } from 'langium'
9+
import { AstUtils, DefaultScopeProvider, EMPTY_SCOPE, MapScope, stream, StreamScope } from 'langium'
910
import { ClassDeclaration, ImportDeclaration, isClassDeclaration, TypeParameter } from '../generated/ast'
1011
import { getPathAsString } from '../utils/ast'
12+
import { getPrecomputedDescription } from '../utils/document'
1113
import { generateStream } from '../utils/stream'
12-
import { createSyntheticAstNodeDescription } from './synthetic'
1314

1415
type SourceMap = ZenScriptAstType
1516
type RuleMap = { [K in keyof SourceMap]?: (source: ReferenceInfo & { container: SourceMap[K] }) => Scope }
@@ -18,12 +19,14 @@ export class ZenScriptScopeProvider extends DefaultScopeProvider {
1819
private readonly packageManager: PackageManager
1920
private readonly memberProvider: MemberProvider
2021
private readonly dynamicProvider: DynamicProvider
22+
private readonly classIndex: ZenScriptClassIndex
2123

2224
constructor(services: ZenScriptServices) {
2325
super(services)
2426
this.packageManager = services.workspace.PackageManager
2527
this.memberProvider = services.references.MemberProvider
2628
this.dynamicProvider = services.references.DynamicProvider
29+
this.classIndex = services.workspace.ClassIndex
2730
}
2831

2932
override getScope(context: ReferenceInfo): Scope {
@@ -41,7 +44,7 @@ export class ZenScriptScopeProvider extends DefaultScopeProvider {
4144
.map(container => precomputed?.get(container))
4245
.nonNullable()
4346
.map(descriptions => stream(descriptions).map(processor).nonNullable())
44-
.reduce((outer, descriptions) => this.createScope(descriptions, outer), outside as Scope)
47+
.reduce((outer, descriptions) => new StreamScope(descriptions, outer), outside as Scope)
4548
}
4649

4750
private dynamicScope(astNode: AstNode, outside?: Scope) {
@@ -50,22 +53,45 @@ export class ZenScriptScopeProvider extends DefaultScopeProvider {
5053

5154
private globalScope(outside?: Scope) {
5255
return this.createScope(this.indexManager.allElements(), outside)
56+
// return new MapScope(this.indexManager.allElements(), outside)
5357
}
5458

5559
private packageScope(outside?: Scope) {
5660
const packages = stream(this.packageManager.root.children.values())
5761
.filter(it => it.isInternalNode())
58-
.map(it => createSyntheticAstNodeDescription('SyntheticHierarchyNode', it.name, it))
59-
return this.createScope(packages, outside)
62+
.map(it => this.packageManager.syntheticDescriptionOf(it))
63+
return new StreamScope(packages, outside)
6064
}
6165

6266
private classScope(outside?: Scope) {
6367
const classes = stream(this.packageManager.root.children.values())
6468
.filter(it => it.isDataNode())
6569
.flatMap(it => it.data)
6670
.filter(isClassDeclaration)
67-
.map(it => this.descriptions.createDescription(it, it.name))
68-
return this.createScope(classes, outside)
71+
.map((it) => {
72+
const document = AstUtils.getDocument(it)
73+
return getPrecomputedDescription(document, it)
74+
})
75+
return new StreamScope(classes, outside)
76+
}
77+
78+
private getImportDescription(importDecl: ImportDeclaration): AstNodeDescription | undefined {
79+
const refNode = importDecl.path.at(-1)
80+
if (!refNode) {
81+
return
82+
}
83+
84+
// access the ref to ensure the lookup of the import
85+
const ref = refNode?.ref
86+
if (!ref) {
87+
return
88+
}
89+
90+
if (!importDecl.alias) {
91+
return refNode?.$nodeDescription
92+
}
93+
94+
return this.descriptions.createDescription(ref, this.nameProvider.getName(importDecl), AstUtils.getDocument(ref))
6995
}
7096

7197
private readonly rules: RuleMap = {
@@ -80,10 +106,13 @@ export class ZenScriptScopeProvider extends DefaultScopeProvider {
80106
const elements: AstNodeDescription[] = []
81107
for (const sibling of siblings) {
82108
if (sibling.isDataNode()) {
83-
sibling.data.forEach(it => elements.push(this.descriptions.createDescription(it, sibling.name)))
109+
sibling.data.forEach((it) => {
110+
const document = AstUtils.getDocument(it)
111+
elements.push(getPrecomputedDescription(document, it))
112+
})
84113
}
85114
else {
86-
elements.push(createSyntheticAstNodeDescription('SyntheticHierarchyNode', sibling.name, sibling))
115+
elements.push(this.packageManager.syntheticDescriptionOf(sibling))
87116
}
88117
}
89118
return this.createScope(elements)
@@ -101,8 +130,7 @@ export class ZenScriptScopeProvider extends DefaultScopeProvider {
101130
return
102131
case ImportDeclaration: {
103132
const importDecl = desc.node as ImportDeclaration
104-
const ref = importDecl.path.at(-1)?.ref ?? importDecl
105-
return this.descriptions.createDescription(ref, this.nameProvider.getName(importDecl))
133+
return this.getImportDescription(importDecl) ?? desc
106134
}
107135
default:
108136
return desc
@@ -114,7 +142,7 @@ export class ZenScriptScopeProvider extends DefaultScopeProvider {
114142
MemberAccess: (source) => {
115143
const outer = this.dynamicScope(source.container)
116144
const members = this.memberProvider.getMembers(source.container.receiver)
117-
return this.createScope(members, outer)
145+
return new StreamScope(members, outer)
118146
},
119147

120148
NamedTypeReference: (source) => {
@@ -127,8 +155,7 @@ export class ZenScriptScopeProvider extends DefaultScopeProvider {
127155
return desc
128156
case ImportDeclaration: {
129157
const importDecl = desc.node as ImportDeclaration
130-
const ref = importDecl.path.at(-1)?.ref ?? importDecl
131-
return this.descriptions.createDescription(ref, this.nameProvider.getName(importDecl))
158+
return this.getImportDescription(importDecl) ?? desc
132159
}
133160
}
134161
}
@@ -137,7 +164,7 @@ export class ZenScriptScopeProvider extends DefaultScopeProvider {
137164
else {
138165
const prev = source.container.path[source.index - 1].ref
139166
const members = this.memberProvider.getMembers(prev)
140-
return this.createScope(members)
167+
return new StreamScope(members)
141168
}
142169
},
143170
}

packages/zenscript/src/reference/synthetic.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ export function createSyntheticAstNodeDescription<K extends keyof ZenScriptSynth
1010
$type: K,
1111
name: string,
1212
origin?: ZenScriptSyntheticAstType[K],
13+
node?: AstNode,
1314
): AstNodeDescription {
1415
return {
15-
node: createSyntheticAstNode($type, origin),
16+
node: node ?? createSyntheticAstNode($type, origin),
1617
type: $type,
1718
name,
1819
documentUri: URI.from({ scheme: 'synthetic', path: `/${$type}/${name}` }),

0 commit comments

Comments
 (0)