// Portions adapted from google/go-jsonnet v0.21.0 (Apache-2.0).
// https://github.com/google/go-jsonnet/tree/67968688d9952f50662e979fab1bef889d0d3e57
// Source: linter/internal/types/desc.go
// Upstream attribution: Google Inc. (see NOTICE).
// Modifications: Ported type descriptions to MoonBit strings and local diagnostic quoting.
// License and retained attribution: LICENSE and NOTICE at the module root.
///|
priv struct LintArrayType {
elements : Array[Array[Int]]
further : Array[Int]
}
///|
priv struct LintObjectType {
fields : Map[String, Array[Int]]
unknown : Array[Int]
mut known : Bool
}
///|
priv struct LintFunctionType {
results : Array[Int]
mut params : Array[(String, Bool)]?
mut min : Int
mut max : Int
}
///|
priv struct LintType {
// bool=1, number=2, string=4, null=8
mut mask : Int
mut array : LintArrayType?
mut object : LintObjectType?
mut func : LintFunctionType?
}
///|
fn LintType::empty() -> LintType {
{ mask: 0, array: None, object: None, func: None, }
}
///|
fn lint_refs(refs : Array[Int]) -> Array[Int] {
if refs.contains(1) {
return [1]
}
let result = refs.copy()
result.sort()
let out = []
for id in result {
if out.last() != Some(id) {
out.push(id)
}
}
out
}
///|
fn LintType::widen(self : LintType, other : LintType) -> Unit {
self.mask = self.mask | other.mask
if other.array is Some(b) {
match self.array {
None =>
self.array = Some({
elements: b.elements.map(e => e.copy()),
further: b.further.copy(),
})
Some(a) => {
for i, e in b.elements {
if a.elements.length() <= i {
a.elements.push(a.further.copy())
}
a.elements[i].append(e)
}
for i = b.elements.length(); i < a.elements.length(); i = i + 1 {
a.elements[i].append(b.further)
}
a.further.append(b.further)
}
}
}
if other.object is Some(b) {
match self.object {
None =>
self.object = Some({
fields: b.fields.map((_, v) => v.copy()),
unknown: b.unknown.copy(),
known: b.known,
})
Some(a) => {
a.unknown.append(b.unknown)
for k, v in b.fields {
a.fields.get_or_init(k, () => []).append(v)
}
if !b.known {
for k, v in a.fields {
if !b.fields.contains(k) {
v.append(b.unknown)
}
}
a.known = false
}
}
}
}
if other.func is Some(b) {
match self.func {
None =>
self.func = Some({
results: b.results.copy(),
params: b.params.map(ps => ps.copy()),
min: b.min,
max: b.max,
})
Some(a) => {
if b.min < a.min {
a.min = b.min
}
if b.max > a.max {
a.max = b.max
}
if a.params is None || b.params is None || a.params != b.params {
a.params = None
}
a.results.append(b.results)
}
}
}
}
///|
fn LintType::normalize(self : LintType) -> Unit {
if self.array is Some(a) {
for i, e in a.elements {
a.elements[i] = lint_refs(e)
}
let refs = lint_refs(a.further)
a.further.clear()
a.further.append(refs)
}
if self.object is Some(o) {
for k, v in o.fields {
o.fields[k] = lint_refs(v)
}
let refs = lint_refs(o.unknown)
o.unknown.clear()
o.unknown.append(refs)
}
if self.func is Some(f) {
let refs = lint_refs(f.results)
f.results.clear()
f.results.append(refs)
}
}
///|
fn LintType::describe(self : LintType) -> String {
let parts = []
for
(bit, name) in [
(1, "a bool"),
(2, "a number"),
(4, "a string"),
(8, "a null"),
] {
if (self.mask & bit) != 0 {
parts.push(name)
}
}
if self.func is Some(_) {
parts.push("a function")
}
if self.object is Some(_) {
parts.push("an object")
}
if self.array is Some(_) {
parts.push("an array")
}
if parts.is_empty() {
"void"
} else {
parts.join(" or ")
}
}
///|
fn lint_plus(a : LintType, b : LintType) -> LintType {
let t = LintType::empty()
t.mask = (a.mask & b.mask & 3) | ((a.mask | b.mask) & 4)
if a.array is Some(a) && b.array is Some(b) {
let further = a.further.copy()
for e in b.elements {
further.append(e)
}
further.append(b.further)
t.array = Some({ elements: a.elements.map(e => e.copy()), further, })
}
if a.object is Some(a) && b.object is Some(b) {
let fields : Map[String, Array[Int]] = Map([])
for k, v in a.fields {
fields[k] = v.copy()
if !b.known && !b.fields.contains(k) {
fields[k].append(b.unknown)
}
}
for k, v in b.fields {
fields[k] = v.copy()
if !a.known && !a.fields.contains(k) {
fields[k].append(a.unknown)
}
}
let unknown = a.unknown.copy()
unknown.append(b.unknown)
t.object = Some({ fields, unknown, known: a.known && b.known, })
}
t
}