// Portions adapted from google/go-jsonnet v0.21.0 (Apache-2.0).
// https://github.com/google/go-jsonnet/tree/67968688d9952f50662e979fab1bef889d0d3e57
// Source: linter/internal/types/check.go
// Upstream attribution: Google Inc. (see NOTICE).
// Modifications: Ported conservative type checks and diagnostics to MoonBit ASTs.
// License and retained attribution: LICENSE and NOTICE at the module root.
///|
fn LintGraph::check_args(
self : LintGraph,
params : Array[(String, Bool)],
args : Array[(String?, Expr)],
loc : Location,
) -> Unit {
let received : Map[String, Bool] = Map([])
let positional = args.filter(a => a.0 is None)
for i, (_, value) in positional {
if i < params.length() {
received[params[i].0] = true
} else {
self.scan.report(
value.loc,
"Too many arguments, there can be at most \{params.length()}, but \{positional.length()} provided",
)
}
}
for (name, value) in args {
if name is Some(name) {
if received.contains(name) {
self.scan.report(value.loc, "Argument \{name} already provided")
return
}
if !params.any(p => p.0 == name) {
self.scan.report(value.loc, "function has no parameter \{name}")
return
}
received[name] = true
}
}
for (name, optional) in params {
if !optional && !received.contains(name) {
self.scan.report(loc, "Missing argument: \{name}")
return
}
}
}
///|
fn LintGraph::check(
self : LintGraph,
node : LintTyped,
untyped? : Bool = false,
) -> Unit raise JsonnetError {
self.scan.tick(node.expression)
for child in node.children {
self.check(child, untyped~)
}
let e = node.expression
let report = fn(message) { self.scan.report(e.loc, message) }
let child = fn(index) {
self.bounds[if untyped { 0 } else { node.children[index].id }]
}
match e.kind {
Call(_, args, _) => {
let t = child(0)
match t.func {
None =>
report(
"Called value must be a function, but it is assumed to be " +
t.describe(),
)
Some(f) =>
match f.params {
Some(params) => self.check_args(params, args, e.loc)
None => {
if f.min > args.length() {
report(
"Too few arguments: got \{args.length()}, but expected at least \{f.min}",
)
}
if f.max < args.length() {
report(
"Too many arguments: got \{args.length()}, but expected at most \{f.max}",
)
}
}
}
}
}
Index({ kind: Var("super"), .. }, _) => ()
Index(_, key) => {
let target = child(0)
let index = child(1)
let array = target.array is Some(_)
let object = target.object is Some(_)
let string = (target.mask & 4) != 0
if !array && !object && !string {
report("Indexed value is neither an array nor an object nor a string")
} else if !object {
let assumed = if array && string {
"an array or a string"
} else if array {
"an array"
} else {
"a string"
}
if (index.mask & 2) == 0 {
report(
"Indexed value is assumed to be " +
assumed +
", but index is not a number",
)
}
} else if !array && !string {
if (index.mask & 4) == 0 {
report(
"Indexed value is assumed to be an object, but index is not a string",
)
}
if target.object is Some(o) &&
o.known &&
key.kind is Literal(String(name)) &&
!o.fields.contains(name) {
report("Indexed object has no field " + lint_quote(name))
}
} else if (index.mask & 6) == 0 {
report(
"Index is neither a number (for indexing arrays and string) nor a string (for indexing objects)",
)
}
}
Unary(op, _) => {
let t = child(0)
if op == "!" {
if (t.mask & 1) == 0 {
report(
"Operand is not a boolean, it is assumed to be " + t.describe(),
)
}
} else if (t.mask & 2) == 0 {
report("Operand is not a number, it is assumed to be " + t.describe())
}
}
_ => ()
}
}