Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

d2ir: use current var stack #2052

Merged
merged 1 commit into from
Aug 6, 2024
Merged
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
2 changes: 2 additions & 0 deletions ci/release/changelogs/next.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#### Features 🚀

- Variables definitions can now refer to other variables in the current scope [#2052](https://github.com/terrastruct/d2/pull/2052)

#### Improvements 🧹

- Sequence diagram edge groups account for edge label heights [#2038](https://github.com/terrastruct/d2/pull/2038)
Expand Down
26 changes: 26 additions & 0 deletions d2compiler/compile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2763,6 +2763,29 @@ x*: {
tassert.Equal(t, "x2", g.Objects[3].AbsID())
},
},
{
name: "var_in_vars",
text: `vars: {
Apple: {
shape:circle
label:Granny Smith
}
Cherry: {
shape:circle
label:Rainier Cherry
}
SummerFruit: {
xx: ${Apple}
cc: ${Cherry}
xx -> cc
}
}

x: ${Apple}
c: ${Cherry}
sf: ${SummerFruit}
`,
},
{
name: "class-shape-class",
text: `classes: {
Expand Down Expand Up @@ -4071,11 +4094,14 @@ vars: {
hi: {
vars: {
x: ${x}-b
b: ${x}
}
yo: ${x}
hey: ${b}
}
`, "")
assert.Equal(t, "a-b", g.Objects[1].Label.Value)
assert.Equal(t, "a-b", g.Objects[2].Label.Value)
},
},
{
Expand Down
35 changes: 28 additions & 7 deletions d2ir/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,8 @@ func (c *compiler) compileSubstitutions(m *Map, varsStack []*Map) {
}
}
} else if f.Map() != nil {
// don't resolve substitutions in vars with the current scope of vars
if f.Name == "vars" {
c.compileSubstitutions(f.Map(), varsStack[1:])
c.compileSubstitutions(f.Map(), varsStack)
c.validateConfigs(f.Map().GetField("d2-config"))
} else {
c.compileSubstitutions(f.Map(), varsStack)
Expand Down Expand Up @@ -227,8 +226,8 @@ func (c *compiler) resolveSubstitutions(varsStack []*Map, node Node) (removedFie
case *d2ast.UnquotedString:
for i, box := range s.Value {
if box.Substitution != nil {
for _, vars := range varsStack {
resolvedField = c.resolveSubstitution(vars, box.Substitution)
for i, vars := range varsStack {
resolvedField = c.resolveSubstitution(vars, node, box.Substitution, i == 0)
if resolvedField != nil {
if resolvedField.Primary() != nil {
if _, ok := resolvedField.Primary().Value.(*d2ast.Null); ok {
Expand Down Expand Up @@ -319,8 +318,8 @@ func (c *compiler) resolveSubstitutions(varsStack []*Map, node Node) (removedFie
case *d2ast.DoubleQuotedString:
for i, box := range s.Value {
if box.Substitution != nil {
for _, vars := range varsStack {
resolvedField = c.resolveSubstitution(vars, box.Substitution)
for i, vars := range varsStack {
resolvedField = c.resolveSubstitution(vars, node, box.Substitution, i == 0)
if resolvedField != nil {
break
}
Expand All @@ -344,16 +343,38 @@ func (c *compiler) resolveSubstitutions(varsStack []*Map, node Node) (removedFie
return removedField
}

func (c *compiler) resolveSubstitution(vars *Map, substitution *d2ast.Substitution) *Field {
func (c *compiler) resolveSubstitution(vars *Map, node Node, substitution *d2ast.Substitution, isCurrentScopeVars bool) *Field {
if vars == nil {
return nil
}

fieldNode, fok := node.(*Field)
parent := ParentField(node)

for i, p := range substitution.Path {
f := vars.GetField(p.Unbox().ScalarString())
if f == nil {
return nil
}
// Consider this case:
//
// ```
// vars: {
// x: a
// }
// hi: {
// vars: {
// x: ${x}-b
// }
// yo: ${x}
// }
// ```
//
// When resolving hi.vars.x, the vars stack includes itself.
// So this next if clause says, "ignore if we're using the current scope's vars to try to resolve a substitution that requires a var from further in the stack"
if fok && fieldNode.Name == p.Unbox().ScalarString() && isCurrentScopeVars && parent.Name == "vars" {
return nil
}

if i == len(substitution.Path)-1 {
return f
Expand Down
Loading
Loading