Skip to content

Commit 307427a

Browse files
authored
feat:(uniast) add more RelationType (#36)
* feat: add more relations on node graph * AddRelation * feat: add relation Groups * fix * update Uniast to v0.1.1 * add version const
1 parent ab2b573 commit 307427a

File tree

6 files changed

+164
-49
lines changed

6 files changed

+164
-49
lines changed

docs/uniast-zh.md

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Universal Abstract-Syntax-Tree Specification (v0.1.0)
1+
# Universal Abstract-Syntax-Tree Specification (v0.1.1)
22

33
Universal Abstract-Syntax-Tree 是 ABCoder 建立的一种LLM亲和、语言无关的代码上下文数据结构,表示某个仓库代码的统一抽象语法树。收集了语言实体(函数、类型、常(变)量)的 定义 及其 相互依赖关系,用于后续的 AI 理解、coding-workflow 开发。
44

@@ -526,6 +526,16 @@ Universal Abstract-Syntax-Tree 是 ABCoder 建立的一种LLM亲和、语言无
526526

527527
- Content:定义代码,如 `var A int = 1 `
528528

529+
- Dependencies:复杂变量声明体中依赖的其他节点,如
530+
```go
531+
var x = getx(y db.Data) int {
532+
return y + model.Var2
533+
}
534+
```
535+
中的 `db.Data``model.Var2`
536+
537+
- Groups: 同组定义, 如 Go 中的 `const( A=1, B=2, C=3)`,Groups 为 `[C=3, B=2]`(假设A为变量自身)
538+
529539

530540
### Graph
531541

@@ -570,7 +580,12 @@ Universal Abstract-Syntax-Tree 是 ABCoder 建立的一种LLM亲和、语言无
570580
"Name": "InitDefaultManager",
571581
"Line": 3
572582
}
573-
]
583+
],
584+
"Dependencies": [],
585+
"References": [],
586+
"Implements": [],
587+
"Inherits": [],
588+
"Groups": []
574589
}
575590
```
576591

@@ -623,9 +638,7 @@ const (
623638

624639
#### Relation
625640

626-
用于存储两个节点之间的关系。
627-
628-
641+
用于存储两个节点之间的关系。示例如下:
629642
```
630643
{
631644
"Kind": "Dependency",
@@ -638,7 +651,14 @@ const (
638651
}
639652
```
640653

641-
- Kind: 关系类型,目前包括 Dependency 和 Reference,分别表示依赖和引用。
654+
- Kind: 关系类型,目前包括:
655+
- Dependency:依赖关系,如函数调用、变量引用等
656+
657+
- Implement:实现关系,如接口方法实现等
658+
659+
- Inherit:继承关系,如结构体字段等
660+
661+
- Group:同组定义,如 Go 中的 `const( A=1; B=2; C=3)`
642662

643663

644664
- ModPath: 模块路径,见【Identity】介绍

lang/golang/parser/file.go

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,16 +58,34 @@ func (p *GoParser) parseFile(ctx *fileContext, f *ast.File) error {
5858
for _, spec := range decl.Specs {
5959
vspec, ok := spec.(*ast.ValueSpec)
6060
if ok {
61-
_, firstVal = p.parseVar(ctx, vspec, false, nil, firstVal, doc)
61+
_, _, firstVal = p.parseVar(ctx, vspec, false, nil, firstVal, doc)
6262
}
6363
}
6464
case token.CONST:
65-
var firstType *Identity
66-
var firstVal *float64
65+
var curType *Identity
66+
var curVal *float64
67+
var vars []*Var
68+
var v *Var
6769
for _, spec := range decl.Specs {
6870
vspec, ok := spec.(*ast.ValueSpec)
6971
if ok {
70-
firstType, firstVal = p.parseVar(ctx, vspec, true, firstType, firstVal, doc)
72+
curType, v, curVal = p.parseVar(ctx, vspec, true, curType, curVal, doc)
73+
if v != nil {
74+
vars = append(vars, v)
75+
}
76+
}
77+
}
78+
if len(vars) > 1 {
79+
// exclude self and add other vars to Var.Groups
80+
for i, v := range vars {
81+
gps := make([]Identity, 0, len(vars)-1)
82+
for j, v2 := range vars {
83+
if i == j {
84+
continue
85+
}
86+
gps = append(gps, v2.Identity)
87+
}
88+
v.Groups = gps
7189
}
7290
}
7391
}
@@ -87,9 +105,10 @@ func (p *GoParser) newVar(mod string, pkg string, name string, isConst bool) *Va
87105
return p.repo.SetVar(ret.Identity, ret)
88106
}
89107

90-
func (p *GoParser) parseVar(ctx *fileContext, vspec *ast.ValueSpec, isConst bool, lastType *Identity, lastValue *float64, doc *ast.CommentGroup) (*Identity, *float64) {
108+
func (p *GoParser) parseVar(ctx *fileContext, vspec *ast.ValueSpec, isConst bool, lastType *Identity, lastValue *float64, doc *ast.CommentGroup) (*Identity, *Var, *float64) {
91109
var typ *Identity
92110
var val *ast.Expr
111+
var v *Var
93112
for i, name := range vspec.Names {
94113
if name.Name == "_" {
95114
// igore anonymous var
@@ -98,7 +117,7 @@ func (p *GoParser) parseVar(ctx *fileContext, vspec *ast.ValueSpec, isConst bool
98117
if vspec.Values != nil {
99118
val = &vspec.Values[i]
100119
}
101-
v := p.newVar(ctx.module.Name, ctx.pkgPath, name.Name, isConst)
120+
v = p.newVar(ctx.module.Name, ctx.pkgPath, name.Name, isConst)
102121
v.FileLine = ctx.FileLine(vspec)
103122
if vspec.Type != nil {
104123
ti := ctx.GetTypeInfo(vspec.Type)
@@ -198,7 +217,7 @@ func (p *GoParser) parseVar(ctx *fileContext, vspec *ast.ValueSpec, isConst bool
198217

199218
typ = v.Type
200219
}
201-
return typ, lastValue
220+
return typ, v, lastValue
202221
}
203222

204223
// newFunc allocate a function in the repo
@@ -552,7 +571,10 @@ func (p *GoParser) parseInterface(ctx *fileContext, name *ast.Ident, decl *ast.I
552571
if obj := ctx.pkgTypeInfo.Defs[name]; obj != nil {
553572
if named, ok := obj.Type().(*types.Named); ok {
554573
iface := named.Underlying().(*types.Interface)
555-
p.interfaces[iface] = st.Identity
574+
// exclude empty interface
575+
if !iface.Empty() {
576+
p.interfaces[iface] = st.Identity
577+
}
556578
}
557579
}
558580

lang/parse.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ func Parse(ctx context.Context, uri string, args ParseOptions) ([]byte, error) {
8686
repo.Name = args.RepoID
8787
}
8888

89+
repo.ASTVersion = uniast.Version
90+
8991
out, err := json.Marshal(repo)
9092
if err != nil {
9193
log.Error("Failed to marshal repository: %v\n", err)

lang/uniast/ast.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,10 @@ type NodeGraph map[string]*Node
7171

7272
// Repository
7373
type Repository struct {
74-
Name string `json:"id"` // module name
75-
Modules map[string]*Module // module name => module
76-
Graph NodeGraph // node id => node
74+
Name string `json:"id"` // module name
75+
Modules map[string]*Module // module name => module
76+
Graph NodeGraph // node id => node
77+
ASTVersion string
7778
}
7879

7980
func (r Repository) ID() string {
@@ -92,9 +93,10 @@ func (r Repository) InternalModules() []*Module {
9293

9394
func NewRepository(name string) Repository {
9495
ret := Repository{
95-
Name: name,
96-
Modules: map[string]*Module{},
97-
Graph: map[string]*Node{},
96+
Name: name,
97+
Modules: map[string]*Module{},
98+
Graph: map[string]*Node{},
99+
ASTVersion: Version,
98100
}
99101
return ret
100102
}
@@ -519,10 +521,10 @@ type Type struct {
519521
FileLine
520522
Content string // struct declaration content
521523

522-
// field type (not include basic types), type name => type id
524+
// field type, type name => type id
523525
SubStruct []Dependency `json:",omitempty"`
524526

525-
// inline field type (not include basic types)
527+
// inherit field type
526528
InlineStruct []Dependency `json:",omitempty"`
527529

528530
// methods defined on the Struct, not including inlined type's method
@@ -547,6 +549,8 @@ type Var struct {
547549
Type *Identity `json:",omitempty"`
548550
Content string
549551
Dependencies []Dependency `json:",omitempty"`
552+
// Groups means the var is a group of vars, like Enum in Go
553+
Groups []Identity `json:",omitempty"`
550554

551555
CompressData *string `json:"compress_data,omitempty"`
552556
}

lang/uniast/node.go

Lines changed: 75 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -82,19 +82,42 @@ func (r *Repository) SetNode(id Identity, typ NodeType) *Node {
8282

8383
func calOffset(ref, dep FileLine) int {
8484
refLine := dep.Line - ref.Line
85-
if refLine < 0 {
86-
return -1
85+
if refLine <= 0 {
86+
return 0
8787
}
8888
return refLine
8989
}
9090

91-
func (r *Repository) AddRelation(node *Node, dep Identity, depFl FileLine) {
91+
func (r *Repository) AddRelation(node *Node, dep Identity, depFl FileLine, kinds ...RelationKind) {
9292
line := calOffset(node.FileLine(), depFl)
93-
node.Dependencies = InsertRelation(node.Dependencies, Relation{
94-
Identity: dep,
95-
Kind: DEPENDENCY,
96-
Line: line,
97-
})
93+
for _, kind := range kinds {
94+
if kind == DEPENDENCY {
95+
node.Dependencies = InsertRelation(node.Dependencies, Relation{
96+
Identity: dep,
97+
Kind: DEPENDENCY,
98+
Line: line,
99+
})
100+
} else if kind == IMPLEMENT {
101+
node.Implements = InsertRelation(node.Implements, Relation{
102+
Identity: dep,
103+
Kind: IMPLEMENT,
104+
Line: line,
105+
})
106+
} else if kind == INHERIT {
107+
node.Inherits = InsertRelation(node.Inherits, Relation{
108+
Identity: dep,
109+
Kind: INHERIT,
110+
Line: line,
111+
})
112+
} else if kind == GROUP {
113+
node.Groups = InsertRelation(node.Groups, Relation{
114+
Identity: dep,
115+
Kind: GROUP,
116+
Line: line,
117+
})
118+
}
119+
}
120+
98121
key := dep.Full()
99122
nd, ok := r.Graph[key]
100123
if !ok {
@@ -104,11 +127,16 @@ func (r *Repository) AddRelation(node *Node, dep Identity, depFl FileLine) {
104127
}
105128
r.Graph[key] = nd
106129
}
107-
nd.References = InsertRelation(nd.References, Relation{
108-
Identity: node.Identity,
109-
Kind: REFERENCE,
110-
Line: line,
111-
})
130+
for _, kind := range kinds {
131+
if kind == DEPENDENCY {
132+
nd.References = InsertRelation(nd.References, Relation{
133+
Identity: node.Identity,
134+
Kind: DEPENDENCY,
135+
Line: line,
136+
})
137+
}
138+
}
139+
112140
if f := r.GetFunction(dep); f != nil {
113141
nd.Type = FUNC
114142
} else if t := r.GetType(dep); t != nil {
@@ -137,36 +165,46 @@ func (r *Repository) BuildGraph() error {
137165
for _, f := range pkg.Functions {
138166
n := r.SetNode(f.Identity, FUNC)
139167
for _, dep := range f.FunctionCalls {
140-
r.AddRelation(n, dep.Identity, dep.FileLine)
168+
r.AddRelation(n, dep.Identity, dep.FileLine, DEPENDENCY)
141169
}
142170
for _, dep := range f.MethodCalls {
143-
r.AddRelation(n, dep.Identity, dep.FileLine)
171+
r.AddRelation(n, dep.Identity, dep.FileLine, DEPENDENCY)
144172
}
145173
for _, dep := range f.Types {
146-
r.AddRelation(n, dep.Identity, dep.FileLine)
174+
r.AddRelation(n, dep.Identity, dep.FileLine, DEPENDENCY)
147175
}
176+
// NOTICE: We regard the receiver of a method as a dependency of the method
148177
if f.Receiver != nil {
149-
r.AddRelation(n, f.Receiver.Type, n.FileLine())
178+
r.AddRelation(n, f.Receiver.Type, n.FileLine(), DEPENDENCY)
150179
}
151180
for _, dep := range f.GlobalVars {
152-
r.AddRelation(n, dep.Identity, dep.FileLine)
181+
r.AddRelation(n, dep.Identity, dep.FileLine, DEPENDENCY)
153182
}
154183
}
155184

156185
for _, t := range pkg.Types {
157186
n := r.SetNode(t.Identity, TYPE)
158187
for _, dep := range t.SubStruct {
159-
r.AddRelation(n, dep.Identity, dep.FileLine)
188+
r.AddRelation(n, dep.Identity, dep.FileLine, DEPENDENCY)
160189
}
161190
for _, dep := range t.InlineStruct {
162-
r.AddRelation(n, dep.Identity, dep.FileLine)
191+
r.AddRelation(n, dep.Identity, dep.FileLine, INHERIT)
192+
}
193+
for _, dep := range t.Implements {
194+
r.AddRelation(n, dep, n.FileLine(), IMPLEMENT)
163195
}
164196
}
165197

166198
for _, v := range pkg.Vars {
167199
n := r.SetNode(v.Identity, VAR)
168200
if v.Type != nil {
169-
r.AddRelation(n, *v.Type, v.FileLine)
201+
r.AddRelation(n, *v.Type, v.FileLine, DEPENDENCY)
202+
}
203+
for _, dep := range v.Dependencies {
204+
r.AddRelation(n, dep.Identity, dep.FileLine, DEPENDENCY)
205+
}
206+
for _, dep := range v.Groups {
207+
r.AddRelation(n, dep, n.FileLine(), GROUP)
170208
}
171209
}
172210
}
@@ -180,8 +218,12 @@ type RelationKind string
180218
const (
181219
// DEPENDENCY: the target node is a dependency of the current node
182220
DEPENDENCY RelationKind = "Dependency"
183-
// REFERENCE: the target node is a reference of the current node
184-
REFERENCE RelationKind = "Reference"
221+
// IMPLEMENT: the target node is implemented by the current node
222+
IMPLEMENT RelationKind = "Implement"
223+
// INHERIT: the target node is inherited by the current node
224+
INHERIT RelationKind = "Inherit"
225+
// GROUPT: the target is in same definition group of nodes, like `const(a=1,b=2)`
226+
GROUP RelationKind = "Group"
185227
)
186228

187229
// Relation between two nodes
@@ -191,7 +233,7 @@ type Relation struct {
191233
// target node
192234
Identity
193235
// start line-offset of the target token related to the current node's codes
194-
Line int
236+
Line int `json:",omitempty"`
195237
// information about this relation
196238
Desc *string `json:",omitempty"`
197239
// related codes representing this relation, comming from current node's codes
@@ -273,9 +315,15 @@ type Node struct {
273315
// Node Type, must be one of FUNC, TYPE, VAR
274316
Type NodeType
275317
// other nodes that depends on this node
276-
Dependencies []Relation
277-
// other nodes that reference this node
278-
References []Relation
318+
Dependencies []Relation `json:",omitempty"`
319+
// other nodes that references this node
320+
References []Relation `json:",omitempty"`
321+
// other nodes this node implements
322+
Implements []Relation `json:",omitempty"`
323+
// other nodes this node inherits
324+
Inherits []Relation `json:",omitempty"`
325+
// other nodes in the same definition group
326+
Groups []Relation `json:",omitempty"`
279327
// the repo that this node belongs to
280328
Repo *Repository `json:"-"`
281329
}

lang/uniast/version.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Copyright 2025 ByteDance Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package uniast
18+
19+
const Version = "v0.1.1"

0 commit comments

Comments
 (0)