///|
fn is_mapping_property_name(name : String) -> Bool {
match name {
"length"
| "isEmpty"
// PKL-148: project the no-arg surface used by `.isNotEmpty` / `.entries`
// / `.toMap` / `.toMapping` from pkl:base.
| "isNotEmpty"
| "entries"
| "toMap"
| "toMapping"
| "keys"
| "values" => true
_ => false
}
}
///|
fn is_mapping_method_name(name : String) -> Bool {
match name {
"containsKey"
| "containsValue"
| "getOrNull"
| "getOrDefault"
| "fold"
// PKL-134: identity conversion methods used by pkf / pkspec.
| "toMap"
| "toList"
// PKL-135: predicate methods. Upstream's Mapping.every/any/none take
// `(Key, Value) -> Boolean`. count is upstream's `count((K, V) -> ...)`.
| "every"
| "any"
| "none"
| "count" => true
_ => false
}
}
///|
fn contains_entry_key(entries : Array[ValueEntry], key : Value) -> Bool {
for entry in entries {
if values_equal(entry.key, key) {
return true
}
}
false
}
///|
fn contains_value(xs : Array[Value], v : Value) -> Bool {
for x in xs {
if values_equal(x, v) {
return true
}
}
false
}
///|
fn set_collection_non_empty_message(elements : Array[Value]) -> String {
"Expected a non-empty collection. Collection: \{render_pcf_value_inline(SetValue(elements))}"
}
///|
fn set_collection_index_range_message(
index : Int64,
lower : Int,
upper : Int,
elements : Array[Value],
) -> String {
"Element index `\{index}` is out of range `\{lower}`..`\{upper}`. Collection: \{render_pcf_value_inline(SetValue(elements))}"
}
///|
fn first_top_level_pure_deferred_entry_message(
entries : Array[ValueEntry],
) -> String? {
for entry in entries {
match entry.value {
ObjectValue(members) if visible_members(members).length() == 0 =>
match deferred_error_message(entry.value) {
Some(message) => return Some(message)
None => ()
}
_ => ()
}
} nobreak {
None
}
}
///|
fn value_to_string_for_join(v : Value) -> String {
match v {
IntValue(n) => "\{n}"
BoolValue(true) => "true"
BoolValue(false) => "false"
StringValue(s) => s
NullValue => "null"
// PKL-148bh: container values render through the compact inline
// form (one line, `;`-separated, leading `new `) so string
// interpolation produces `new Listing { 1; 2; 3 }` rather than the
// multi-line PCF block. `Dynamic` ObjectValues already carry the
// `__class` tag for the Dynamic case; non-tagged Objects render
// bare `new { ... }` for now (universal user-class tagging is
// deferred — `basic/string` accepts the bare form for both
// `Dynamic` and a user class in current gold).
ObjectValue(members) =>
match
(lookup_member(members, "value"), lookup_member(members, "groups")) {
(Some(StringValue(s)), Some(_)) => s
_ => {
let buf = StringBuilder::new()
let class_name = match find_object_class_tag(members) {
Some(name) => name
None => ""
}
buf.write_string("new ")
if class_name != "" {
buf.write_string(class_name)
buf.write_char(' ')
}
let visible = visible_members(members)
if visible.length() == 0 {
buf.write_string("{}")
} else {
buf.write_string("{ ")
for i = 0; i < visible.length(); i = i + 1 {
if i > 0 {
buf.write_string("; ")
}
// PKL-148c: a property whose value is a Listing /
// Mapping uses the implicit-body form (`names { ... }`)
// rather than the explicit-assignment form (`names =
// new Listing { ... }`). Matches Apple Pkl's
// `reflect.Property.toString()` projection.
match visible[i].value {
ListingValue(elements)
| DefaultedListingValue(_, elements, _)
| ListValue(elements)
| SetValue(elements) => {
buf.write_string(visible[i].name)
buf.write_string(" { ")
for j = 0; j < elements.length(); j = j + 1 {
if j > 0 {
buf.write_string("; ")
}
render_pcf_scalar(elements[j], buf)
}
buf.write_string(" }")
}
MappingValue(entries)
| DefaultedMappingValue(_, entries, _)
| MapValue(entries) => {
buf.write_string(visible[i].name)
buf.write_string(" { ")
for j = 0; j < entries.length(); j = j + 1 {
if j > 0 {
buf.write_string("; ")
}
buf.write_char('[')
render_pcf_scalar(entries[j].key, buf)
buf.write_string("] = ")
render_pcf_scalar(entries[j].value, buf)
}
buf.write_string(" }")
}
_ => {
buf.write_string(visible[i].name)
buf.write_string(" = ")
render_pcf_scalar(visible[i].value, buf)
}
}
}
buf.write_string(" }")
}
buf.to_string()
}
}
ListingValue(elements) | DefaultedListingValue(_, elements, _) => {
let buf = StringBuilder::new()
buf.write_string("new Listing")
if elements.length() == 0 {
buf.write_string(" {}")
} else {
buf.write_string(" { ")
for i = 0; i < elements.length(); i = i + 1 {
if i > 0 {
buf.write_string("; ")
}
render_pcf_scalar(elements[i], buf)
}
buf.write_string(" }")
}
buf.to_string()
}
MappingValue(entries) | DefaultedMappingValue(_, entries, _) => {
let buf = StringBuilder::new()
buf.write_string("new Mapping")
if entries.length() == 0 {
buf.write_string(" {}")
} else {
buf.write_string(" { ")
for i = 0; i < entries.length(); i = i + 1 {
if i > 0 {
buf.write_string("; ")
}
buf.write_char('[')
render_pcf_scalar(entries[i].key, buf)
buf.write_string("] = ")
render_pcf_scalar(entries[i].value, buf)
}
buf.write_string(" }")
}
buf.to_string()
}
// Apple Pkl renders function values as `new FunctionN {}` in
// toString / interpolation contexts (N = arity).
FunctionValue(parameters, _, _, _, _) =>
"new Function\{parameters.length()} {}"
_ => {
let buf = StringBuilder::new()
render_pcf_inline(v, 0, false, buf)
buf.to_string()
}
}
}
///|
fn eval_mapping_property(
entries : Array[ValueEntry],
name : String,
diagnostics : Array[Diagnostic],
) -> Value? {
match name {
"length" => Some(IntValue(entries.length().to_int64()))
"isEmpty" => Some(BoolValue(entries.length() == 0))
"isNotEmpty" => Some(BoolValue(entries.length() != 0))
"keys" => {
let keys : Array[Value] = []
for entry in entries {
keys.push(entry.key)
}
Some(SetValue(keys))
}
"values" => {
let values : Array[Value] = []
for entry in entries {
values.push(entry.value)
}
Some(ListingValue(values))
}
"entries" => {
// PKL-148: `Mapping.entries` projects to a Listing of
// Pair mirroring `Map.entries`.
let pairs : Array[Value] = []
for entry in entries {
pairs.push(PairValue(entry.key, entry.value))
}
Some(ListingValue(pairs))
}
"toMap" => {
// PKL-148: lift the Mapping into a Map value preserving order.
let kvs : Array[ValueEntry] = []
for entry in entries {
kvs.push({ key: entry.key, value: entry.value })
}
Some(MapValue(kvs))
}
"toMapping" => Some(MappingValue(entries))
_ => {
diagnostics.push(
diag("Cannot find property `\{name}` in object of type `Mapping`."),
)
None
}
}
}
///|
/// PKL-119b: dispatch methods on an IntSeqValue. `step(newValue)`
/// returns a new IntSeq with the carrier step replaced; `toList`
/// materializes a `ListValue`, `toListing` materializes a
/// `ListingValue`; `map(f)` materializes then applies the lambda to
/// each element; `fold(initial, op)` reduces left-to-right.
fn eval_intseq_method(
start : Int64,
end_v : Int64,
step : Int64,
method_name : String,
arguments : Array[Expr],
bindings : Array[Binding],
env : Array[ValueBinding],
class_env : Array[ClassBinding],
cache : Array[ValueBinding],
stack : Array[String],
declarations : Array[Declaration],
diagnostics : Array[Diagnostic],
resolve_import : (String) -> EvalResult?,
) -> Value? {
let arg_values : Array[Value] = []
let mut ok = true
for argument in arguments {
match
eval_expr_with_bindings(
argument, bindings, env, class_env, cache, stack, declarations, diagnostics,
resolve_import,
) {
Some(v) => arg_values.push(v)
None => ok = false
}
}
if !ok {
return None
}
match method_name {
"step" => {
if arg_values.length() != 1 {
diagnostics.push(
diag("IntSeq.step expects 1 argument, got \{arg_values.length()}"),
)
return None
}
match arg_values[0] {
IntValue(new_step) =>
if new_step == 0L {
diagnostics.push(
diag("Type constraint `isNonZero` violated. Value: 0"),
)
None
} else {
Some(IntSeqValue(start, end_v, new_step))
}
_ => {
diagnostics.push(diag("IntSeq.step expects Int"))
None
}
}
}
"toList" | "toListing" => {
if arg_values.length() != 0 {
diagnostics.push(
diag(
"IntSeq.\{method_name} expects 0 arguments, got \{arg_values.length()}",
),
)
return None
}
let elements = intseq_materialize(start, end_v, step)
if method_name == "toList" {
Some(ListValue(elements))
} else {
Some(ListingValue(elements))
}
}
"map" => {
if arg_values.length() != 1 {
diagnostics.push(
diag("IntSeq.map expects 1 argument, got \{arg_values.length()}"),
)
return None
}
let elements = intseq_materialize(start, end_v, step)
let mapped : Array[Value] = []
for element in elements {
match
apply_function_value(
"IntSeq.map callback",
arg_values[0],
[element],
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
) {
Some(v) => mapped.push(v)
None => return None
}
}
// Apple Pkl's IntSeq.map returns `List` — the gold fixture
// `basic/let`'s `res11 = IntSeq(1, 5).map(...)` renders through
// the `List(3, 4, 5, 6, 7)` constructor form rather than a
// Listing block.
Some(ListValue(mapped))
}
"fold" => {
if arg_values.length() != 2 {
diagnostics.push(
diag("IntSeq.fold expects 2 arguments, got \{arg_values.length()}"),
)
return None
}
let mut acc = arg_values[0]
let elements = intseq_materialize(start, end_v, step)
for element in elements {
match
apply_function_value(
"IntSeq.fold operator",
arg_values[1],
[acc, element],
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
) {
Some(v) => acc = v
None => return None
}
}
Some(acc)
}
_ => {
diagnostics.push(
diag(
"Cannot find property `\{method_name}` in object of type `IntSeq`.",
),
)
None
}
}
}
///|
/// PKL-119c: dispatch methods on a `SetValue`. `.contains(x)` reuses
/// the same value-equality helper as Listing; `.toList()` /
/// `.toListing()` materialise into a `ListingValue` (the two are
/// aliased until PKL-119d splits List / Map out); `.toSet()` is
/// identity since the receiver is already a Set; `.map` / `.filter`
/// / `.fold` / `.join` mirror the Listing surface but return into a
/// `ListingValue` (mapped Sets keep insertion order but the upstream
/// signature for `Set.map` is `Listing`, not `Set`).
fn eval_set_method(
elements : Array[Value],
method_name : String,
arguments : Array[Expr],
bindings : Array[Binding],
env : Array[ValueBinding],
class_env : Array[ClassBinding],
cache : Array[ValueBinding],
stack : Array[String],
declarations : Array[Declaration],
diagnostics : Array[Diagnostic],
resolve_import : (String) -> EvalResult?,
) -> Value? {
let arg_values : Array[Value] = []
let mut ok = true
for argument in arguments {
match
eval_expr_with_bindings(
argument, bindings, env, class_env, cache, stack, declarations, diagnostics,
resolve_import,
) {
Some(v) => arg_values.push(v)
None => ok = false
}
}
if !ok {
return None
}
if method_name == "reduce" && elements.length() == 0 {
diagnostics.push(diag(set_collection_non_empty_message(elements)))
return None
}
if (method_name == "minBy" || method_name == "maxBy") &&
elements.length() == 0 {
diagnostics.push(diag(set_collection_non_empty_message(elements)))
return None
}
if method_name == "split" {
if arg_values.length() != 1 {
diagnostics.push(
diag("Set.split expects 1 argument, got \{arg_values.length()}"),
)
return None
}
match arg_values[0] {
IntValue(index64) => {
let index = index64.to_int()
if index64 < 0L || index > elements.length() {
diagnostics.push(
diag(
set_collection_index_range_message(
index64,
0,
elements.length(),
elements,
),
),
)
return None
}
}
_ => ()
}
}
match method_name {
"contains" => {
if arg_values.length() != 1 {
diagnostics.push(
diag("Set.contains expects 1 argument, got \{arg_values.length()}"),
)
return None
}
Some(BoolValue(contains_value(elements, arg_values[0])))
}
// PKL-148: `Set.add(x)` returns a new Set with `x` appended (no-op
// when the element already belongs). Apple Pkl's signature is
// `add(Value) -> Set` — non-mutating.
"add" => {
if arg_values.length() != 1 {
diagnostics.push(
diag("Set.add expects 1 argument, got \{arg_values.length()}"),
)
return None
}
let next : Array[Value] = []
for v in elements {
next.push(v)
}
if !contains_value(next, arg_values[0]) {
next.push(arg_values[0])
}
Some(SetValue(next))
}
"firstOrNull" =>
if elements.length() == 0 {
Some(NullValue)
} else {
value_or_deferred_diagnostic(elements[0], diagnostics)
}
"lastOrNull" =>
if elements.length() == 0 {
Some(NullValue)
} else {
value_or_deferred_diagnostic(
elements[elements.length() - 1],
diagnostics,
)
}
"every" | "any" | "none" => {
if arg_values.length() != 1 {
diagnostics.push(
diag(
"Set.\{method_name} expects 1 argument, got \{arg_values.length()}",
),
)
return None
}
let mut hit = false
for element in elements {
match
apply_function_value(
"Set.\{method_name} predicate",
arg_values[0],
[element],
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
) {
Some(BoolValue(true)) => {
hit = true
if method_name == "any" || method_name == "none" {
break
}
}
Some(BoolValue(false)) =>
if method_name == "every" {
return Some(BoolValue(false))
}
Some(value) => {
diagnostics.push(diag(pkl_bool_return_type_message(value)))
return None
}
None => return None
}
}
match method_name {
"every" => Some(BoolValue(true))
"any" => Some(BoolValue(hit))
"none" => Some(BoolValue(!hit))
_ => None
}
}
"count" => {
if arg_values.length() != 1 {
diagnostics.push(
diag("Set.count expects 1 argument, got \{arg_values.length()}"),
)
return None
}
let mut n = 0
for element in elements {
match
apply_function_value(
"Set.count predicate",
arg_values[0],
[element],
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
) {
Some(BoolValue(true)) => n = n + 1
Some(BoolValue(false)) => ()
Some(value) => {
diagnostics.push(diag(pkl_bool_return_type_message(value)))
return None
}
None => return None
}
}
Some(IntValue(n.to_int64()))
}
"toList" | "toListing" => {
if arg_values.length() != 0 {
diagnostics.push(
diag(
"Set.\{method_name} expects 0 arguments, got \{arg_values.length()}",
),
)
return None
}
// PKL-148h: `Set.toList` → `ListValue`, `Set.toListing` →
// `ListingValue`. Element order is preserved (Set walks in
// insertion order, matching the Apple Pkl PCF projection).
if method_name == "toList" {
Some(ListValue(elements))
} else {
Some(ListingValue(elements))
}
}
"toSet" => {
if arg_values.length() != 0 {
diagnostics.push(
diag("Set.toSet expects 0 arguments, got \{arg_values.length()}"),
)
return None
}
Some(SetValue(elements))
}
"map" => {
if arg_values.length() != 1 {
diagnostics.push(
diag("Set.map expects 1 argument, got \{arg_values.length()}"),
)
return None
}
let result : Array[Value] = []
for element in elements {
match
apply_function_value(
"Set.map callback",
arg_values[0],
[element],
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
) {
Some(v) => result.push(v)
None => return None
}
}
Some(SetValue(unique_values(result)))
}
"filter" => {
if arg_values.length() != 1 {
diagnostics.push(
diag("Set.filter expects 1 argument, got \{arg_values.length()}"),
)
return None
}
let result : Array[Value] = []
for element in elements {
match
apply_function_value(
"Set.filter predicate",
arg_values[0],
[element],
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
) {
Some(BoolValue(true)) => result.push(element)
Some(BoolValue(false)) => ()
Some(value) => {
diagnostics.push(diag(pkl_bool_return_type_message(value)))
return None
}
None => return None
}
}
Some(SetValue(result))
}
"fold" => {
if arg_values.length() != 2 {
diagnostics.push(
diag("Set.fold expects 2 arguments, got \{arg_values.length()}"),
)
return None
}
let mut acc = arg_values[0]
for element in elements {
match
apply_function_value(
"Set.fold operator",
arg_values[1],
[acc, element],
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
) {
Some(v) => acc = v
None => return None
}
}
Some(acc)
}
"join" => {
if arg_values.length() != 1 {
diagnostics.push(
diag("Set.join expects 1 argument, got \{arg_values.length()}"),
)
return None
}
match arg_values[0] {
StringValue(separator) => {
let buf = StringBuilder::new()
for i = 0; i < elements.length(); i = i + 1 {
if i > 0 {
buf.write_string(separator)
}
match elements[i] {
StringValue(s) => buf.write_string(s)
other => render_pcf_inline(other, 0, false, buf)
}
}
Some(StringValue(buf.to_string()))
}
_ => {
diagnostics.push(diag("Set.join expects String separator"))
None
}
}
}
"sortWith" => {
if arg_values.length() != 1 {
diagnostics.push(
diag("Set.sortWith expects 1 argument, got \{arg_values.length()}"),
)
return None
}
match
sort_with_comparator(
elements,
arg_values[0],
"Set.sortWith",
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
) {
Some(out) => Some(ListValue(out))
None => None
}
}
"maxWith" | "maxWithOrNull" => {
let delegated_name = if method_name == "maxWithOrNull" {
"maxWithOrNull"
} else {
"minWith"
}
eval_list_or_listing_method(
false, elements, delegated_name, arguments, bindings, env, class_env, cache,
stack, declarations, diagnostics, resolve_import,
)
}
"intersect" | "difference" => {
if arg_values.length() != 1 {
diagnostics.push(
diag(
"Set.\{method_name} expects 1 argument, got \{arg_values.length()}",
),
)
return None
}
let other = match arg_values[0] {
SetValue(xs) | ListValue(xs) | ListingValue(xs) => xs
_ => {
diagnostics.push(
diag("Set.\{method_name} expects a Collection argument"),
)
return None
}
}
let out : Array[Value] = []
if method_name == "intersect" {
for element in elements {
if contains_value(other, element) {
out.push(element)
}
}
} else {
for element in elements {
if !contains_value(other, element) {
out.push(element)
}
}
}
Some(SetValue(out))
}
"toMap" => {
if arg_values.length() != 2 {
diagnostics.push(
diag("Set.toMap expects 2 arguments, got \{arg_values.length()}"),
)
return None
}
let entries : Array[ValueEntry] = []
for element in elements {
let key = match
apply_function_value(
"Set.toMap keyFn",
arg_values[0],
[element],
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
) {
Some(v) => v
None => return None
}
let value = match
apply_function_value(
"Set.toMap valueFn",
arg_values[1],
[element],
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
) {
Some(v) => v
None => return None
}
entries.push({ key, value })
}
Some(MapValue(entries))
}
_ =>
if is_listing_method_name(method_name) {
match
eval_list_or_listing_method(
false, elements, method_name, arguments, bindings, env, class_env, cache,
stack, declarations, diagnostics, resolve_import,
) {
Some(ListingValue(xs)) =>
match method_name {
"sort" | "sortBy" | "sortWith" | "repeat" | "reverse" =>
Some(ListValue(xs))
"toListing" => Some(ListingValue(xs))
_ => Some(SetValue(unique_values(xs)))
}
Some(ListValue(xs)) =>
match method_name {
"foldBack" => Some(ListValue(xs))
"toList"
| "sort"
| "sortBy"
| "sortWith"
| "repeat"
| "reverse" => Some(ListValue(xs))
_ => Some(SetValue(unique_values(xs)))
}
Some(PairValue(ListValue(a), ListValue(b))) =>
if method_name == "split" ||
method_name == "splitOrNull" ||
method_name == "partition" {
Some(
PairValue(
SetValue(unique_values(a)),
SetValue(unique_values(b)),
),
)
} else {
Some(PairValue(ListValue(a), ListValue(b)))
}
Some(MapValue(entries)) if method_name == "groupBy" => {
let converted : Array[ValueEntry] = []
for entry in entries {
match entry.value {
ListValue(xs) =>
converted.push({
key: entry.key,
value: SetValue(unique_values(xs)),
})
_ => converted.push(entry)
}
}
Some(MapValue(converted))
}
other => other
}
} else {
diagnostics.push(
diag("Cannot find property `\{method_name}` in object of type `Set`."),
)
None
}
}
}
///|
/// PKL-119d: dispatch methods on a `MapValue`. The lookup methods
/// (`getOrNull` / `containsKey` / `getOrThrow`) use the same value-
/// equality helper as `contains_value` so any key type that derives
/// Eq works (Pkl supports arbitrary key types). Projection helpers
/// materialize into the matching variant — `.toMap` is identity,
/// `.toMapping` re-projects into the object-style `MappingValue`,
/// `.toList` walks the entries as `Listing>`. `.map(f)`
/// expects a `(key, value) -> Pair` lambda; for
/// the MVP we accept the upstream convention. `.filter` and `.fold`
/// mirror the same pattern as Listing / Set.
fn eval_map_method(
entries : Array[ValueEntry],
method_name : String,
arguments : Array[Expr],
bindings : Array[Binding],
env : Array[ValueBinding],
class_env : Array[ClassBinding],
cache : Array[ValueBinding],
stack : Array[String],
declarations : Array[Declaration],
diagnostics : Array[Diagnostic],
resolve_import : (String) -> EvalResult?,
) -> Value? {
let arg_values : Array[Value] = []
let mut ok = true
for argument in arguments {
match
eval_expr_with_bindings(
argument, bindings, env, class_env, cache, stack, declarations, diagnostics,
resolve_import,
) {
Some(v) => arg_values.push(v)
None => ok = false
}
}
if !ok {
return None
}
match method_name {
"containsKey" => {
if arg_values.length() != 1 {
diagnostics.push(
diag("Map.containsKey expects 1 argument, got \{arg_values.length()}"),
)
return None
}
let key = arg_values[0]
for entry in entries {
if values_equal(entry.key, key) {
return Some(BoolValue(true))
}
}
Some(BoolValue(false))
}
"containsValue" => {
if arg_values.length() != 1 {
diagnostics.push(
diag(
"Map.containsValue expects 1 argument, got \{arg_values.length()}",
),
)
return None
}
let v = arg_values[0]
for entry in entries {
if values_equal(entry.value, v) {
return Some(BoolValue(true))
}
}
Some(BoolValue(false))
}
"getOrNull" => {
if arg_values.length() != 1 {
diagnostics.push(
diag("Map.getOrNull expects 1 argument, got \{arg_values.length()}"),
)
return None
}
let key = arg_values[0]
for entry in entries {
if values_equal(entry.key, key) {
return Some(entry.value)
}
}
Some(NullValue)
}
"getOrThrow" => {
if arg_values.length() != 1 {
diagnostics.push(
diag("Map.getOrThrow expects 1 argument, got \{arg_values.length()}"),
)
return None
}
let key = arg_values[0]
for entry in entries {
if values_equal(entry.key, key) {
return Some(entry.value)
}
}
diagnostics.push(diag("Map.getOrThrow: key not found"))
None
}
"toMap" => {
if arg_values.length() != 0 {
diagnostics.push(
diag("Map.toMap expects 0 arguments, got \{arg_values.length()}"),
)
return None
}
Some(MapValue(entries))
}
"toMapping" => {
if arg_values.length() != 0 {
diagnostics.push(
diag("Map.toMapping expects 0 arguments, got \{arg_values.length()}"),
)
return None
}
Some(MappingValue(entries))
}
"toDynamic" => {
if arg_values.length() != 0 {
diagnostics.push(
diag("Map.toDynamic expects 0 arguments, got \{arg_values.length()}"),
)
return None
}
let members : Array[ValueMember] = []
for entry in entries {
match entry.key {
StringValue(name) =>
members.push({
name,
value: entry.value,
source: None,
annotations: [],
})
_ => ()
}
}
Some(ObjectValue(tag_object_with_class(members, "Dynamic")))
}
"toList" => {
if arg_values.length() != 0 {
diagnostics.push(
diag("Map.toList expects 0 arguments, got \{arg_values.length()}"),
)
return None
}
let pairs : Array[Value] = []
for entry in entries {
pairs.push(PairValue(entry.key, entry.value))
}
Some(ListingValue(pairs))
}
"map" => {
if arg_values.length() != 1 {
diagnostics.push(
diag("Map.map expects 1 argument, got \{arg_values.length()}"),
)
return None
}
let result : Array[ValueEntry] = []
for entry in entries {
match
apply_function_value(
"Map.map callback",
arg_values[0],
[entry.key, entry.value],
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
) {
Some(PairValue(new_key, new_value)) =>
result.push({ key: new_key, value: new_value })
Some(value) => {
diagnostics.push(
diag(
"Expected value of type `Pair`, but got type `\{eval_value_type_name(value)}`. Value: \{render_pcf_value_inline(value)}",
),
)
return None
}
None => return None
}
}
Some(MapValue(result))
}
"mapKeys" => {
if arg_values.length() != 1 {
diagnostics.push(
diag("Map.mapKeys expects 1 argument, got \{arg_values.length()}"),
)
return None
}
let result : Array[ValueEntry] = []
for entry in entries {
match
apply_function_value(
"Map.mapKeys callback",
arg_values[0],
[entry.key, entry.value],
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
) {
Some(new_key) => result.push({ key: new_key, value: entry.value })
None => return None
}
}
Some(MapValue(result))
}
"mapValues" => {
if arg_values.length() != 1 {
diagnostics.push(
diag("Map.mapValues expects 1 argument, got \{arg_values.length()}"),
)
return None
}
let result : Array[ValueEntry] = []
for entry in entries {
match
apply_function_value(
"Map.mapValues callback",
arg_values[0],
[entry.key, entry.value],
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
) {
Some(new_value) => result.push({ key: entry.key, value: new_value })
None => return None
}
}
Some(MapValue(result))
}
"flatMap" => {
if arg_values.length() != 1 {
diagnostics.push(
diag("Map.flatMap expects 1 argument, got \{arg_values.length()}"),
)
return None
}
let result : Array[ValueEntry] = []
for entry in entries {
match
apply_function_value(
"Map.flatMap callback",
arg_values[0],
[entry.key, entry.value],
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
) {
Some(MapValue(next_entries)) =>
for next in next_entries {
result.push(next)
}
Some(value) => {
diagnostics.push(
diag(
"Expected value of type `Map`, but got type `\{eval_value_type_name(value)}`. Value: \{render_pcf_value_inline(value)}",
),
)
return None
}
None => return None
}
}
Some(MapValue(result))
}
"filter" => {
if arg_values.length() != 1 {
diagnostics.push(
diag("Map.filter expects 1 argument, got \{arg_values.length()}"),
)
return None
}
let result : Array[ValueEntry] = []
for entry in entries {
match
apply_function_value(
"Map.filter predicate",
arg_values[0],
[entry.key, entry.value],
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
) {
Some(BoolValue(true)) => result.push(entry)
Some(BoolValue(false)) => ()
Some(_) => {
diagnostics.push(diag("Map.filter predicate must return Boolean"))
return None
}
None => return None
}
}
Some(MapValue(result))
}
"remove" => {
if arg_values.length() != 1 {
diagnostics.push(
diag("Map.remove expects 1 argument, got \{arg_values.length()}"),
)
return None
}
let result : Array[ValueEntry] = []
for entry in entries {
if !values_equal(entry.key, arg_values[0]) {
result.push(entry)
}
}
Some(MapValue(result))
}
"put" => {
if arg_values.length() != 2 {
diagnostics.push(
diag("Map.put expects 2 arguments, got \{arg_values.length()}"),
)
return None
}
let result : Array[ValueEntry] = []
let mut replaced = false
for entry in entries {
if values_equal(entry.key, arg_values[0]) {
result.push({ key: arg_values[0], value: arg_values[1] })
replaced = true
} else {
result.push(entry)
}
}
if !replaced {
result.push({ key: arg_values[0], value: arg_values[1] })
}
Some(MapValue(result))
}
"toTyped" => {
if arguments.length() != 1 {
diagnostics.push(
diag("Map.toTyped expects 1 argument, got \{arguments.length()}"),
)
return None
}
match
eval_expr_with_bindings(
arguments[0],
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
) {
Some(class_value) =>
match
(
class_mirror_simple_name(class_value),
class_mirror_display_name(class_value),
) {
(Some(class_name), Some(display_name)) =>
dynamic_members_to_typed_value(
map_entries_to_dynamic_members(entries),
class_name,
display_name,
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
)
_ => {
diagnostics.push(
diag("Map.toTyped expects a Class mirror argument"),
)
None
}
}
None => None
}
}
"fold" => {
if arg_values.length() != 2 {
diagnostics.push(
diag("Map.fold expects 2 arguments, got \{arg_values.length()}"),
)
return None
}
let mut acc = arg_values[0]
for entry in entries {
match
apply_function_value(
"Map.fold operator",
arg_values[1],
[acc, entry.key, entry.value],
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
) {
Some(v) => acc = v
None => return None
}
}
Some(acc)
}
// PKL-148bb: Map mirrors Mapping's predicate trio. Each takes a
// 2-arg lambda `(key, value) -> Boolean`. Used by class-property
// constraints whose Map slot fires the `every(...)` cascade
// (`classes/mapConstraints1`).
"every" | "any" | "none" => {
if arg_values.length() != 1 {
diagnostics.push(
diag(
"Map.\{method_name} expects 1 argument, got \{arg_values.length()}",
),
)
return None
}
let mut hit = false
let mut all = true
for entry in entries {
match
apply_function_value(
"Map.\{method_name} predicate",
arg_values[0],
[entry.key, entry.value],
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
) {
Some(BoolValue(true)) => hit = true
Some(BoolValue(false)) => all = false
Some(_) => {
diagnostics.push(
diag("Map.\{method_name} predicate must return Boolean"),
)
return None
}
None => return None
}
if method_name == "any" && hit {
return Some(BoolValue(true))
}
if method_name == "every" && !all {
return Some(BoolValue(false))
}
}
match method_name {
"every" => Some(BoolValue(all))
"any" => Some(BoolValue(hit))
"none" => Some(BoolValue(!hit))
_ => None
}
}
"getOrDefault" => {
if arg_values.length() != 1 {
diagnostics.push(
diag(
"Mapping.getOrDefault expects 1 argument, got \{arg_values.length()}",
),
)
return None
}
match lookup_entry(entries, arg_values[0]) {
Some(v) => Some(v)
None => Some(NullValue)
}
}
_ => {
diagnostics.push(
diag("Cannot find property `\{method_name}` in object of type `Map`."),
)
None
}
}
}
///|
fn eval_mapping_method(
entries : Array[ValueEntry],
method_name : String,
arguments : Array[Expr],
bindings : Array[Binding],
env : Array[ValueBinding],
class_env : Array[ClassBinding],
cache : Array[ValueBinding],
stack : Array[String],
declarations : Array[Declaration],
diagnostics : Array[Diagnostic],
resolve_import : (String) -> EvalResult?,
) -> Value? {
let arg_values : Array[Value] = []
let mut ok = true
for argument in arguments {
match
eval_expr_with_bindings(
argument, bindings, env, class_env, cache, stack, declarations, diagnostics,
resolve_import,
) {
Some(v) => arg_values.push(v)
None => ok = false
}
}
if !ok {
return None
}
match method_name {
"containsKey" => {
if arg_values.length() != 1 {
diagnostics.push(
diag(
"Mapping.containsKey expects 1 argument, got \{arg_values.length()}",
),
)
return None
}
Some(BoolValue(contains_entry_key(entries, arg_values[0])))
}
"containsValue" => {
if arg_values.length() != 1 {
diagnostics.push(
diag(
"Mapping.containsValue expects 1 argument, got \{arg_values.length()}",
),
)
return None
}
let values : Array[Value] = []
for entry in entries {
values.push(entry.value)
}
Some(BoolValue(contains_value(values, arg_values[0])))
}
"getOrNull" => {
if arg_values.length() != 1 {
diagnostics.push(
diag(
"Mapping.getOrNull expects 1 argument, got \{arg_values.length()}",
),
)
return None
}
match lookup_entry(entries, arg_values[0]) {
Some(v) => Some(v)
None => Some(NullValue)
}
}
"fold" => {
if arg_values.length() != 2 {
diagnostics.push(
diag("Mapping.fold expects 2 arguments, got \{arg_values.length()}"),
)
return None
}
let mut acc = arg_values[0]
let callback = arg_values[1]
for entry in entries {
match
apply_function_value(
"Mapping.fold combine",
callback,
[acc, entry.key, entry.value],
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
) {
Some(v) => acc = v
None => return None
}
}
Some(acc)
}
// PKL-134: identity conversion. toMap on a Mapping is self;
// toList projects the entries' values (matching Apple Pkl's
// `Map.values.toList()` shape closely enough for shape-only chains).
"toMap" => {
if arg_values.length() != 0 {
diagnostics.push(
diag("Mapping.toMap expects 0 arguments, got \{arg_values.length()}"),
)
return None
}
match first_top_level_pure_deferred_entry_message(entries) {
Some(message) => {
diagnostics.push(diag(message))
return None
}
None => ()
}
let kvs : Array[ValueEntry] = []
for entry in entries {
kvs.push({ key: entry.key, value: entry.value })
}
Some(MapValue(kvs))
}
"toList" => {
if arg_values.length() != 0 {
diagnostics.push(
diag("Mapping.toList expects 0 arguments, got \{arg_values.length()}"),
)
return None
}
let values : Array[Value] = []
for entry in entries {
values.push(entry.value)
}
Some(ListingValue(values))
}
// PKL-135: predicate methods. Upstream's signature is
// `(Key, Value) -> Boolean` for every / any; pkl-mbt mirrors that.
"every" => {
if arg_values.length() != 1 {
diagnostics.push(
diag("Mapping.every expects 1 argument, got \{arg_values.length()}"),
)
return None
}
for entry in entries {
match
apply_function_value(
"Mapping.every predicate",
arg_values[0],
[entry.key, entry.value],
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
) {
Some(BoolValue(true)) => ()
Some(BoolValue(false)) => return Some(BoolValue(false))
Some(_) => {
diagnostics.push(
diag("Mapping.every predicate must return Boolean"),
)
return None
}
None => return None
}
}
Some(BoolValue(true))
}
"any" => {
if arg_values.length() != 1 {
diagnostics.push(
diag("Mapping.any expects 1 argument, got \{arg_values.length()}"),
)
return None
}
for entry in entries {
match
apply_function_value(
"Mapping.any predicate",
arg_values[0],
[entry.key, entry.value],
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
) {
Some(BoolValue(true)) => return Some(BoolValue(true))
Some(BoolValue(false)) => ()
Some(_) => {
diagnostics.push(diag("Mapping.any predicate must return Boolean"))
return None
}
None => return None
}
}
Some(BoolValue(false))
}
"none" => {
if arg_values.length() != 1 {
diagnostics.push(
diag("Mapping.none expects 1 argument, got \{arg_values.length()}"),
)
return None
}
for entry in entries {
match
apply_function_value(
"Mapping.none predicate",
arg_values[0],
[entry.key, entry.value],
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
) {
Some(BoolValue(true)) => return Some(BoolValue(false))
Some(BoolValue(false)) => ()
Some(_) => {
diagnostics.push(diag("Mapping.none predicate must return Boolean"))
return None
}
None => return None
}
}
Some(BoolValue(true))
}
"count" => {
if arg_values.length() != 1 {
diagnostics.push(
diag("Mapping.count expects 1 argument, got \{arg_values.length()}"),
)
return None
}
let mut n = 0
for entry in entries {
match
apply_function_value(
"Mapping.count predicate",
arg_values[0],
[entry.key, entry.value],
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
) {
Some(BoolValue(true)) => n = n + 1
Some(BoolValue(false)) => ()
Some(_) => {
diagnostics.push(
diag("Mapping.count predicate must return Boolean"),
)
return None
}
None => return None
}
}
Some(IntValue(n.to_int64()))
}
_ => None
}
}