Skip to content

Commit ae3987d

Browse files
committed
encoding/jsonschema: avoid unnecessary alias to root
Issue #3354 demonstrates that aliases are used when they're not actually needed. When investigating the fix for #2287, I realised where the problem might be, and this is the result. The problem was that all the self-references need to reference the same AST node, but they were not doing so. Fix that by creating the struct node to be embedded when we know that we need a self-reference. We can also use the presence of that node to signal that a self-reference is needed, removing the need for `hasSelfReference`. Fixes #3354 Signed-off-by: Roger Peppe <[email protected]> Change-Id: Ie886b5819c612cbd64abca62d3231aedd530e2bf Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1199626 Reviewed-by: Daniel Martí <[email protected]> Unity-Result: CUE porcuepine <[email protected]> TryBot-Result: CUEcueckoo <[email protected]>
1 parent 4eb3d53 commit ae3987d

File tree

5 files changed

+42
-54
lines changed

5 files changed

+42
-54
lines changed

Diff for: encoding/jsonschema/decode.go

+32-11
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ type decoder struct {
4444
errs errors.Error
4545
numID int // for creating unique numbers: increment on each use
4646
mapURLErrors map[string]bool
47+
// self holds the struct literal that will eventually be embedded
48+
// in the top level file. It is only set when decoder.rootRef is
49+
// called.
50+
self *ast.StructLit
4751
}
4852

4953
// addImport registers
@@ -169,17 +173,35 @@ func (d *decoder) schema(ref []ast.Label, v cue.Value) (a []ast.Decl) {
169173
expr = ast.NewStruct(ref[i], expr)
170174
}
171175

172-
if root.hasSelfReference {
173-
return []ast.Decl{
174-
&ast.EmbedDecl{Expr: ast.NewIdent(topSchema)},
175-
&ast.Field{
176-
Label: ast.NewIdent(topSchema),
177-
Value: &ast.StructLit{Elts: a},
178-
},
179-
}
176+
if root.self == nil {
177+
return a
178+
}
179+
root.self.Elts = a
180+
return []ast.Decl{
181+
&ast.EmbedDecl{Expr: d.rootRef()},
182+
&ast.Field{
183+
Label: d.rootRef(),
184+
Value: root.self,
185+
},
180186
}
187+
}
181188

182-
return a
189+
// rootRef returns a reference to the top of the file. We do this by
190+
// creating a helper schema:
191+
//
192+
// _schema: {...}
193+
// _schema
194+
//
195+
// This is created at the finalization stage, signaled by d.self being
196+
// set, which rootRef does as a side-effect.
197+
func (d *decoder) rootRef() *ast.Ident {
198+
ident := ast.NewIdent("_schema")
199+
if d.self == nil {
200+
d.self = &ast.StructLit{}
201+
}
202+
// Ensure that all self-references refer to the same node.
203+
ident.Node = d.self
204+
return ident
183205
}
184206

185207
func (d *decoder) errf(n cue.Value, format string, args ...interface{}) ast.Expr {
@@ -363,8 +385,7 @@ type state struct {
363385
definitions []ast.Decl
364386

365387
// Used for inserting definitions, properties, etc.
366-
hasSelfReference bool
367-
obj *ast.StructLit
388+
obj *ast.StructLit
368389
// Complete at finalize.
369390
fieldRefs map[label]refs
370391

Diff for: encoding/jsonschema/ref.go

+4-23
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,6 @@ func (s *state) resolveURI(n cue.Value) *url.URL {
8989
return u
9090
}
9191

92-
const topSchema = "_schema"
93-
9492
// makeCUERef converts a URI into a CUE reference for the current location.
9593
// The returned identifier (or first expression in a selection chain), is
9694
// hardwired to point to the resolved value. This will allow astutil.Sanitize
@@ -136,17 +134,8 @@ func (s *state) makeCUERef(n cue.Value, u *url.URL, fragmentParts []string) (_e
136134
case u.Host == "" && u.Path == "",
137135
s.id != nil && s.id.Host == u.Host && s.id.Path == u.Path:
138136
if len(fragmentParts) == 0 {
139-
// refers to the top of the file. We will allow this by
140-
// creating a helper schema as such:
141-
// _schema: {...}
142-
// _schema
143-
// This is created at the finalization stage if
144-
// hasSelfReference is set.
145-
s.hasSelfReference = true
146-
147-
ident = ast.NewIdent(topSchema)
148-
ident.Node = s.obj
149-
return ident
137+
// Refers to the top of the file.
138+
return s.rootRef()
150139
}
151140

152141
ident, fragmentParts = s.getNextIdent(n, fragmentParts)
@@ -202,16 +191,8 @@ func (s *state) makeCUERef(n cue.Value, u *url.URL, fragmentParts []string) (_e
202191
// state above the root state that we need to update.
203192
s = s.up
204193

205-
// refers to the top of the file. We will allow this by
206-
// creating a helper schema as such:
207-
// _schema: {...}
208-
// _schema
209-
// This is created at the finalization stage if
210-
// hasSelfReference is set.
211-
s.hasSelfReference = true
212-
ident = ast.NewIdent(topSchema)
213-
ident.Node = s.obj
214-
return ident
194+
// Refers to the top of the file.
195+
return s.rootRef()
215196
}
216197

217198
x := s.idRef[0]

Diff for: encoding/jsonschema/testdata/issue3351.txtar

+4-14
Original file line numberDiff line numberDiff line change
@@ -70,24 +70,14 @@ _schema: {
7070
@jsonschema(id="https://www.sourcemeta.com/schemas/vendor/[email protected]")
7171
$else?: #["jsone-value"]
7272
$let?: [string]: null | bool | number | string | [...] | {
73-
[string]: _schema_1
73+
[string]: _schema
7474
}
75-
$sort?: _schema_5 | [...number]
75+
$sort?: _schema | [...number]
7676
{[!~"^($else|$let|$sort)$"]: #["jsone-value"]}
7777

78-
#: "jsone-value": _schema_A | [..._schema_8]
78+
#: "jsone-value": _schema | [..._schema]
7979

8080
#: "jsone-array": [...#["jsone-value"]]
8181

82-
#: "jsone-object-array": [..._schema_E]
82+
#: "jsone-object-array": [..._schema]
8383
}
84-
85-
let _schema_1 = _schema
86-
87-
let _schema_5 = _schema
88-
89-
let _schema_A = _schema
90-
91-
let _schema_8 = _schema
92-
93-
let _schema_E = _schema

Diff for: encoding/jsonschema/testdata/refroot.txtar

+1-3
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@ _schema: {
1919
null | bool | number | string | [...] | {
2020
@jsonschema(id="http://cuelang.org/go/encoding/openapi/testdata/order.json")
2121
value?: _
22-
next?: _schema_1
22+
next?: _schema
2323
...
2424
}
2525
}
26-
27-
let _schema_1 = _schema

Diff for: encoding/jsonschema/testdata/refroot2.txtar

+1-3
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@ _schema: {
1616
@jsonschema(schema="http://json-schema.org/draft-07/schema#")
1717
null | bool | number | string | [...] | {
1818
value?: _
19-
next?: _schema_1
19+
next?: _schema
2020
...
2121
}
2222
}
23-
24-
let _schema_1 = _schema

0 commit comments

Comments
 (0)