// Portions adapted from google/go-jsonnet v0.21.0 (Apache-2.0).
// https://github.com/google/go-jsonnet/tree/67968688d9952f50662e979fab1bef889d0d3e57
// Source: linter/internal/types/build_graph.go
// Upstream attribution: Google Inc. (see NOTICE).
// Modifications: Ported type-graph construction to bounded MoonBit data structures.
// License and retained attribution: LICENSE and NOTICE at the module root.
///|
fn LintGraph::build(
self : LintGraph,
e : Expr,
scope : Map[String, Int],
from : String,
id? : Int,
depth? : Int = 0,
) -> LintTyped raise JsonnetError {
if depth >= self.scan.limits.depth {
raise problem(Limit, e.loc, "lint type expression depth exceeded")
}
self.scan.tick(e)
let id = id.unwrap_or_else(() => self.add())
let children = []
let visit = fn(e : Expr, scope : Map[String, Int]) raise JsonnetError {
let node = self.build(e, scope, from, depth=depth + 1)
children.push(node)
node.id
}
let refs = self.slots[id].refs
let t = self.slots[id].concrete
match e.kind {
NumberLiteral(_) => refs.push(3)
Literal(v) =>
refs.push(
match v {
True | False => 2
Number(_) => 3
String(_) => 4
Null => 5
_ => 1
},
)
Var("self" | "super" | "$") => refs.push(9)
Var("$std") => refs.push(11)
Var(n) => refs.push(scope.get(n).unwrap_or(1))
Local(bs, body) => {
let inner = scope.copy()
for { name: n, .. } in bs {
inner[n] = self.add()
}
refs.push(visit(body, inner))
for { name: n, value: x, .. } in bs {
children.push(self.build(x, inner, from, id=inner[n], depth=depth + 1))
}
}
Func(ps, body) => {
let inner = scope.copy()
for { name: n, .. } in ps {
inner[n] = 1
}
let result = visit(body, inner)
for { default_value: value, .. } in ps {
if value is Some(x) {
ignore(visit(x, inner))
}
}
t.func = Some({
results: [result],
params: if ps.is_empty() {
None
} else {
Some(ps.map(p => (p.name, p.default_value is Some(_))))
},
min: ps.filter(p => p.default_value is Some(_)).length(),
max: ps.length(),
})
}
If(a, b, c) => {
ignore(visit(a, scope))
refs.push(visit(b, scope))
refs.push(visit(c, scope))
}
Arr(xs) => {
let elements = []
let further = []
for i, x in xs {
let child = visit(x, scope)
if i < 5 {
elements.push([child])
} else {
further.push(child)
}
}
t.array = Some({ elements, further, })
}
Obj(fs, bs, _, _) => {
let inner = scope.copy()
inner["$"] = 9
for { name: n, .. } in bs {
inner[n] = self.add()
}
let fields : Map[String, Array[Int]] = Map([])
let unknown = []
let mut known = true
// Reference traversal visits all field names before locals and bodies.
for f in fs {
ignore(visit(f.key, scope))
}
for { name: n, value: x, .. } in bs {
children.push(self.build(x, inner, from, id=inner[n], depth=depth + 1))
}
for f in fs {
let value = visit(f.value, inner)
let value = if f.additive { 1 } else { value }
if f.key.kind is Literal(String(key)) {
fields.get_or_init(key, () => []).push(value)
} else {
known = false
unknown.push(value)
}
}
t.object = Some({ fields, unknown, known, })
}
Binary("in", a, { kind: Var("super"), .. }) => {
ignore(visit(a, scope))
refs.push(2)
}
Binary("%", a, b) =>
return self.build(
lint_std_call("mod", [a, b], e.loc),
scope,
from,
id~,
depth~,
)
Binary("in", a, b) =>
return self.build(
lint_std_call("objectHasAll", [b, a], e.loc),
scope,
from,
id~,
depth~,
)
Index({ kind: Var("super"), .. }, key) => {
ignore(visit(key, scope))
refs.push(1)
}
Index(a, b) => {
let target = visit(a, scope)
ignore(visit(b, scope))
let index = match b.kind {
Literal(String(s)) => StringIndex(s)
NumberLiteral(raw) => {
let parsed = Some(@string.parse_double(raw)) catch { _ => None }
if parsed is Some(n) && n >= 0 && n < 5 && n == n.to_int().to_double() {
IntIndex(n.to_int())
} else {
AnyIndex
}
}
Literal(Number(n, ..)) if n >= 0 && n < 5 && n == n.to_int().to_double() =>
IntIndex(n.to_int())
_ => AnyIndex
}
self.slots[id].index = Some((target, index))
}
Call(f, args, _) => {
let target = visit(f, scope)
for (_, x) in args {
ignore(visit(x, scope))
}
self.slots[id].index = Some((target, ResultIndex))
}
Binary(op, a, b) => {
let a = visit(a, scope)
let b = visit(b, scope)
if op == "+" {
self.slots[id].plus = Some((a, b))
} else {
refs.push(1)
}
}
Unary(op, x) => {
ignore(visit(x, scope))
refs.push(if op == "!" { 2 } else { 3 })
}
Fail(x) => ignore(visit(x, scope))
Assert(a, b, c) => {
ignore(visit(a, scope))
refs.push(visit(c, scope))
if b is Some(b) {
ignore(visit(b, scope))
}
}
Slice(a, b, c, d) => {
let null : Expr = { kind: Literal(Json::null()), loc: e.loc, end: None, }
return self.build(
lint_std_call(
"slice",
[a, b.unwrap_or(null), c.unwrap_or(null), d.unwrap_or(null)],
e.loc,
),
scope,
from,
id~,
depth~,
)
}
ArrayComp(body, cs) => {
self.work(cs.length())
return self.build(lint_comp(body, cs, e.loc), scope, from, id~, depth~)
}
ObjectComp(key, body, cs, bs) => {
self.work(cs.length())
let value = if bs.is_empty() {
body
} else {
Expr::{ kind: Local(bs, body), loc: body.loc, end: None, }
}
let object : Expr = {
kind: Obj([{ key, value, visibility: 1, additive: false, }], [], [], []),
loc: e.loc,
end: e.end,
}
return self.build(
lint_std_call("$objectFlatMerge", [lint_comp(object, cs, e.loc)], e.loc),
scope,
from,
id~,
depth~,
)
}
ImportFile(path, false) =>
refs.push(
self.imports
.get((from, path))
.map(id => self.roots.get(id).unwrap_or(1))
.unwrap_or(1),
)
ImportFile(_, true) => refs.push(4)
ImportBinary(_) => refs.push(7)
StandardFunction(_) | StandardValue(_) => refs.push(1)
}
{ expression: e, id, children, }
}