///|
fn stack_contains_binding(stack : Array[String], name : String) -> Bool {
for item in stack {
if item == name {
return true
}
}
false
}
///|
/// PKL-pkspec-A: drop object-body sibling slots so an outer-scope
/// binding's RHS re-evaluation can't capture a same-named field that
/// an enclosing object literal pre-registered. Returns the original
/// array unchanged when there is nothing to strip (the common case),
/// avoiding an allocation on the hot path.
fn strip_sibling_slot_bindings(bindings : Array[Binding]) -> Array[Binding] {
let mut has_sibling = false
for b in bindings {
if b.sibling_slot {
has_sibling = true
break
}
}
if !has_sibling {
return bindings
}
let filtered : Array[Binding] = []
for b in bindings {
if !b.sibling_slot {
filtered.push(b)
}
}
filtered
}
///|
fn push_binding_stack(stack : Array[String], name : String) -> Array[String] {
let next : Array[String] = []
for item in stack {
next.push(item)
}
next.push(name)
next
}
///|
fn typed_object_class_name_for_expr(
expr : Expr,
bindings : Array[Binding],
stack : Array[String],
) -> String? {
match expr {
TypedObjectLiteral(type_name, _) => Some(type_name)
AmendExpr(base, _) | NonNullExpr(base) =>
typed_object_class_name_for_expr(base, bindings, stack)
Identifier(name) =>
if stack_contains_binding(stack, name) {
None
} else {
match find_binding(bindings, name) {
Some(binding) =>
// PKL-148bh: `local a: A = new {}` keeps the binding's
// value as an ObjectLiteral (the binding-eval rewrite
// only fires at value time, not at AST construction).
// Honour the binding annotation directly when its value
// is a bare ObjectLiteral so a downstream `a.method()`
// dispatches against class A's method table.
match (binding.value, binding.type_name) {
(ObjectLiteral(_), Some(class_name)) =>
if class_name.contains("<") ||
class_name.contains("|") ||
class_name.contains("(") ||
class_name.contains("?") ||
class_name.contains(".") {
typed_object_class_name_for_expr(
binding.value,
bindings,
push_binding_stack(stack, name),
)
} else {
Some(class_name)
}
_ =>
typed_object_class_name_for_expr(
binding.value,
bindings,
push_binding_stack(stack, name),
)
}
None => None
}
}
_ => None
}
}
///|
fn copy_value_bindings(bindings : Array[ValueBinding]) -> Array[ValueBinding] {
// Pre-size to skip the doubling-realloc dance on the hot
// class-default path. `copy_value_bindings(cache)` runs once per
// class-layer materialisation; on `apple-pkl/stdlib/base.pkl` that
// landed in the top sample bucket as `moonbit_unsafe_ref_array_blit`
// / `moonbit_make_ref_array` traffic.
let copied : Array[ValueBinding] = []
copied.reserve_capacity(bindings.length())
for binding in bindings {
copied.push(binding)
}
copied
}
///|
// Scope-origin marker used only inside evaluator environments. ValueBinding
// intentionally stays a small public contract type; pairing an ordinary
// binding with this impossible-to-spell Pkl name records that a `let` or
// callable parameter is lexical rather than part of an implicit receiver.
fn lexical_value_binding_marker_name(name : String) -> String {
"@__lexical$\{name}"
}
///|
fn push_lexical_value_binding(
target : Array[ValueBinding],
name : String,
value : Value,
) -> Unit {
target.push({ name, value })
target.push({
name: lexical_value_binding_marker_name(name),
value: BoolValue(true),
})
}
///|
fn capture_value_bindings(
env : Array[ValueBinding],
cache : Array[ValueBinding],
) -> Array[ValueBinding] {
let captured = copy_value_bindings(env)
// Upper bound: every cache entry might be retained (excluding the
// handful of `@__module_*` / `@__class_default_*` markers filtered
// below).
captured.reserve_capacity(env.length() + cache.length())
for binding in cache {
// PKL-148bh / PKL-148bl: module metadata markers are plumbing for
// reflect/read dispatch and get reintroduced from the caller cache
// at application time. Keeping them out of captured_env prevents
// ordinary function values from growing hidden bindings.
if binding.name == "@__module_name" ||
binding.name == "@__module_path" ||
binding.name == "@__module_source" ||
binding.name == "@__module_is_amend" ||
binding.name == "@__open_module" ||
binding.name == "@__retain_property_thunks" ||
binding.name == "@__class_default_scope" ||
binding.name == "@__class_default_call_scope" {
continue
}
captured.push(binding)
}
captured
}
///|
fn lookup_value_after_marker(
cache : Array[ValueBinding],
marker : String,
name : String,
) -> Value? {
let mut active = false
let mut found : Value? = None
for binding in cache {
if binding.name == marker {
active = true
continue
}
if active && binding.name == name {
found = Some(binding.value)
}
}
found
}
///|
fn lookup_class_default_call_local(
cache : Array[ValueBinding],
name : String,
) -> Value? {
// Parameters are appended after the class-default call marker. They
// must shadow module bindings with the same name before const
// provenance rejects the module binding.
match lookup_value_after_marker(cache, "@__class_default_call_scope", name) {
Some(value) => Some(value)
None =>
match
lookup_value_after_marker(
cache,
"@__class_default_call_scope",
hidden_member_name(name),
) {
Some(value) => Some(value)
None =>
lookup_value_after_marker(
cache,
"@__class_default_call_scope",
local_member_name(name),
)
}
}
}
///|
fn int_pow(base : Int64, exponent : Int64) -> Int64? {
if exponent == 0L {
return Some(1L)
}
let mut result = 1L
let mut factor = base
let mut remaining = exponent
while remaining > 0L {
if remaining % 2L == 1L {
match checked_int64_mul(result, factor) {
Some(next) => result = next
None => return None
}
}
remaining = remaining / 2L
if remaining > 0L {
match checked_int64_mul(factor, factor) {
Some(next) => factor = next
None => return None
}
}
}
Some(result)
}
///|
fn checked_int64_mul(a : Int64, b : Int64) -> Int64? {
if a == 0L || b == 0L {
return Some(0L)
}
let max = 9223372036854775807L
let min = 0L - 9223372036854775807L - 1L
let ok = if a > 0L {
if b > 0L {
a <= max / b
} else {
b >= min / a
}
} else if b > 0L {
a >= min / b
} else {
a >= max / b
}
if ok {
Some(a * b)
} else {
None
}
}
///|
fn checked_int64_add(a : Int64, b : Int64) -> Int64? {
let max = 9223372036854775807L
let min = 0L - 9223372036854775807L - 1L
let ok = if b > 0L {
a <= max - b
} else if b < 0L {
a >= min - b
} else {
true
}
if ok {
Some(a + b)
} else {
None
}
}
///|
fn checked_int64_sub(a : Int64, b : Int64) -> Int64? {
let max = 9223372036854775807L
let min = 0L - 9223372036854775807L - 1L
let ok = if b > 0L {
a >= min + b
} else if b < 0L {
a <= max + b
} else {
true
}
if ok {
Some(a - b)
} else {
None
}
}
///|
fn binding_collection_host_constraint_rejection_message(
type_name : String?,
value : Value,
declarations : Array[Declaration],
) -> String? {
match type_name {
Some(annotation) => {
let aliases = eval_type_alias_bindings(declarations)
let resolved = eval_resolved_type_alias(annotation, aliases)
let source = strip_lazy_collection_annotation_marker(resolved)
match value {
ListingValue(elements)
| DefaultedListingValue(_, elements, _)
| ListValue(elements) =>
pkl_constrained_listing_rejection_message_from_source(
source, elements,
)
_ => None
}
}
None => None
}
}
///|
fn mapping_literal_declares_default(entries : Array[MappingEntry]) -> Bool {
for entry in entries {
if collection_default_expr_from_mapping_entry(entry) is Some(_) {
return true
}
}
false
}
///|
fn inherited_module_object_amend_expr(
binding_name : String,
expr : Expr,
type_name : String?,
cache : Array[ValueBinding],
class_env : Array[ClassBinding],
) -> Expr {
// PKL-148bv: `output { ... }` and any module-level `name { body }`
// whose parent slot was declared `hidden` (e.g. `hidden parser:
// yaml.Parser`) must amend the parent's value rather than replace
// it, so the parent's hidden defaults / class markers (`__kind` on
// `yaml.Parser`) survive into the child. Visible parent properties
// intentionally stay on the existing "fresh literal" path —
// promoting them to `AmendExpr` would re-route closure / module
// references in `foo { m = module.a }` style amends through the
// amend cache and drop them (basic/moduleRef2).
// A nullable typed slot uses null only as its absence marker. A body such
// as `package { ... }` over `package: Package? = null` must instantiate
// the nullable inner type, not amend the literal Null value. Returning the
// original expression lets the typed-literal promotion below do that.
match (expr, type_name) {
(ListingLiteral(elements), _) =>
match module_super_members_from_cache(cache) {
Some(parent_members) =>
match lookup_value_member(parent_members, binding_name) {
Some(parent_member) =>
if parent_member.value is NullValue {
expr
} else {
AmendExpr(
MemberAccess(Identifier("super"), binding_name),
listing_literal_amend_members(elements),
)
}
None => expr
}
None => expr
}
(MappingLiteral(entries), _) if mapping_literal_declares_default(entries) =>
match module_super_members_from_cache(cache) {
Some(parent_members) =>
match lookup_value_member(parent_members, binding_name) {
Some(parent_member) =>
if parent_member.value is NullValue {
expr
} else {
AmendExpr(
MemberAccess(Identifier("super"), binding_name),
mapping_literal_amend_members(entries),
)
}
None => expr
}
None => expr
}
(ObjectLiteral(members), Some(_)) =>
match module_super_members_from_cache(cache) {
Some(parent_members) =>
match lookup_value_member(parent_members, binding_name) {
Some(parent_member) =>
if parent_member.value is NullValue {
expr
} else if parent_member.value is ObjectValue(parent_object) &&
instantiable_class_name_for_type_annotation(
type_name.unwrap(),
class_env,
)
is Some(expected_class) &&
find_object_class_tag(parent_object) is Some(_) &&
!object_class_tag_matches(parent_object, expected_class) {
// An explicit child annotation can replace a broad parent
// slot (`pkl:Command.options: Typed` -> user `Options`).
// In that case the new body constructs the child class;
// inheriting/amending the parent's Dynamic value would
// preserve the wrong runtime class tag.
expr
} else {
AmendExpr(
MemberAccess(Identifier("super"), binding_name),
members,
)
}
None => expr
}
None => expr
}
(ObjectLiteral(members), None) =>
match module_super_members_from_cache(cache) {
Some(parent_members) => {
let matches_hidden = lookup_value_member(
parent_members,
hidden_member_name(binding_name),
)
is Some(_)
let force_amend = binding_name == "output"
if matches_hidden || force_amend {
match lookup_value_member(parent_members, binding_name) {
Some(_) =>
AmendExpr(
MemberAccess(Identifier("super"), binding_name),
members,
)
None => expr
}
} else {
expr
}
}
None => expr
}
_ => expr
}
}
///|
fn resolve_binding_value(
name : String,
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? {
// PKL-148d: lexical scope wins over module-level bindings. Apple
// Pkl's implicit-receiver chain walks the innermost object body
// first (`bar { x = 2; y = x + 3 }` resolves the RHS `x` to the
// inner `x = 2`, not the module-level `x = 0`). The env slot now
// carries the inner-scope members so a lookup must check it before
// the module's binding cache.
match lookup_value(env, name) {
Some(value) => return Some(value)
None => ()
}
let shadow_binding = find_binding(bindings, name)
if lookup_value(cache, "@__class_default_call_scope") is Some(_) {
match lookup_class_default_call_local(cache, name) {
Some(value) => return Some(value)
None => ()
}
}
if lookup_value(cache, "@__class_default_scope") is Some(_) ||
lookup_value(cache, "@__class_default_call_scope") is Some(_) {
match shadow_binding {
Some(binding) =>
if !binding.is_const && !(binding.value is LambdaExpr(_, _, _)) {
let message = match
lookup_value(cache, "@__class_default_call_name") {
Some(StringValue(method_name)) =>
"Cannot call method `\{method_name}` from here because it is not `const`."
_ =>
"Cannot reference property `\{name}` from here because it is not `const`."
}
diagnostics.push(diag(message))
return None
}
None => ()
}
}
let binding_shadows_cache = match shadow_binding {
Some(binding) =>
binding.abstract_slot && lookup_value(cache, "@__open_module") is None
None => false
}
if !binding_shadows_cache {
match lookup_value(cache, name) {
Some(value) => return Some(value)
None => ()
}
match lookup_value(cache, hidden_member_name(name)) {
Some(value) => return Some(value)
None => ()
}
match lookup_value(cache, local_member_name(name)) {
Some(value) => return Some(value)
None => ()
}
}
match shadow_binding {
Some(binding) =>
if binding.abstract_slot && binding.value is NullLiteral {
match
synthesize_default_for_type(
binding.type_name,
bindings,
env,
class_env,
cache,
declarations,
diagnostics,
resolve_import,
) {
Some(value) => {
let structural_reject = match binding.type_name {
Some(annotation) =>
eval_resolved_annotation_structural_rejection_message(
annotation, value, declarations,
)
None => None
}
match structural_reject {
Some(message) => {
diagnostics.push(diag(message))
None
}
None =>
if pkl_constrained_type_annotation_value_is_valid(
binding.type_name,
value,
diagnostics,
) {
cache.push({ name: binding.name, value })
Some(value)
} else {
None
}
}
}
None => None
}
} else if lookup_value(cache, "@__class_default_scope") is Some(_) &&
!binding.is_const &&
!(binding.value is LambdaExpr(_, _, _)) {
diagnostics.push(
diag(
"Cannot reference property `\{name}` from here because it is not `const`.",
),
)
None
} else if stack_contains_binding(stack, name) {
// PKL-148t: when a property's RHS self-references the
// same name but a sibling module-level function shares
// it (`function qux(...) = ...` beside `qux = qux(...)`),
// Apple Pkl resolves the RHS occurrence through the
// function namespace first. Try the function-shaped
// binding before surfacing the cycle diagnostic — only
// when the currently-resolving binding is itself NOT a
// function (otherwise the fallback would short-circuit
// back to the same binding).
let cyclic_is_function = match binding.value {
LambdaExpr(_, _, _) => true
_ => false
}
if !cyclic_is_function {
match find_function_binding(bindings, name) {
Some(fn_binding) =>
return eval_expr_with_bindings(
fn_binding.value,
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
)
None => ()
}
}
diagnostics.push(diag("cyclic property reference \{name}"))
None
} else {
// PKL-148bg: when the binding annotation names a user class
// (`local base: TheClass2 = new { requiredLength = 5 }`),
// rewrite a bare `new { ... }` (ObjectLiteral) to
// `new TheClass2 { ... }` (TypedObjectLiteral) so the
// class's `hidden` / default machinery runs. Without this
// the unprefixed `requiredLength` would leak into the
// rendered output (`listings/listing6` fixture). When the
// annotation names an in-scope module-shaped value
// (`res1: someModule = new { foo = "..." }`), promote the
// RHS to `AmendExpr(Identifier(someModule), members)` so
// the module's own defaults flow through.
let collection_rewritten_value = inherited_module_object_amend_expr(
binding.name,
collection_literal_expr_for_type_annotation(
binding.value,
binding.type_name,
),
binding.type_name,
cache,
class_env,
)
let rewritten_value : Expr = match
(collection_rewritten_value, binding.type_name) {
(ObjectLiteral(members), Some(class_name)) =>
match
instantiable_class_name_for_type_annotation(class_name, class_env) {
Some(instantiable_name) =>
TypedObjectLiteral(instantiable_name, members)
None =>
if class_name.contains(".") ||
class_name.contains("<") ||
class_name.contains("(") ||
class_name.contains("?") {
collection_rewritten_value
} else if class_name.contains("|") {
// PKL-148bh: union annotation (`*Foo | Baz`)
// — pick the starred branch and route through
// its class. Falls back to ObjectLiteral when
// no starred branch resolves to a user class.
let mut starred_class : String? = None
for choice in split_top_level_union_choices(class_name) {
let t = trim_spaces(choice)
if t.has_prefix("*") {
let head = trim_spaces(
String::unsafe_substring(t, start=1, end=t.length()),
)
if lookup_class_binding(class_env, head) is Some(_) {
starred_class = Some(head)
break
}
}
}
match starred_class {
Some(name) => TypedObjectLiteral(name, members)
None => collection_rewritten_value
}
} else if lookup_value(env, class_name) is Some(ObjectValue(_)) ||
lookup_value(cache, class_name) is Some(ObjectValue(_)) ||
find_binding(bindings, class_name) is Some(_) {
AmendExpr(Identifier(class_name), members)
} else {
collection_rewritten_value
}
}
_ => collection_rewritten_value
}
// PKL-pkspec-A: a real module-/lexical-scope property's RHS must
// not resolve a same-named identifier to an object-body *sibling*
// slot. Object bodies pre-register their members as `sibling_slot`
// bindings (appended after the outer bindings) so intra-body
// forward references work, but those slots must stay invisible
// when we re-evaluate an outer binding whose RHS happens to
// mention a name that collides with a sibling (e.g. module
// `local testNames = tests.toList()` must read the module `tests`,
// not the `tests` field of an enclosing `new Rendered { tests =
// ... }`). Without this, the sibling `tests` (whose value cycles
// back through the same local) is picked by `find_binding`'s
// last-wins walk and a false `cyclic property reference` is
// raised. Sibling slots themselves keep the full binding set so
// genuine intra-body forward refs still resolve.
let rhs_bindings = if binding.sibling_slot {
bindings
} else {
strip_sibling_slot_bindings(bindings)
}
let rhs_cache = copy_value_bindings(cache)
// PKL-161/163: object properties retain thunks beyond construction.
// Output, converter, and amend consumers explicitly force the values
// they select; keeping the marker lexical prevents it from becoming a
// captured user binding while still covering nested object literals.
rhs_cache.push({
name: "@__retain_property_thunks",
value: BoolValue(true),
})
let evaluated_rhs = eval_expr_with_bindings(
rewritten_value,
rhs_bindings,
env,
class_env,
rhs_cache,
push_binding_stack(stack, name),
declarations,
diagnostics,
resolve_import,
)
// The marker needs an isolated lexical scope, but values resolved
// transitively while evaluating this RHS still belong to the shared
// module/object cache. Publish those entries back so two sibling
// module bindings observe the same local object and its memo cells.
for i = cache.length(); i < rhs_cache.length(); i = i + 1 {
let resolved = rhs_cache[i]
if resolved.name != "@__retain_property_thunks" &&
lookup_value(cache, resolved.name) is None {
cache.push(resolved)
}
}
match evaluated_rhs {
Some(raw_value) => {
let value = coerce_value_to_annotated_type(
raw_value,
binding.type_name,
)
let value = apply_collection_default_for_type(
value,
binding.type_name,
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
)
let typed_value = match binding.type_name {
Some(annotation) =>
match
cast_value_to_type_annotation(
annotation, value, bindings, env, class_env, cache, stack, declarations,
resolve_import,
) {
TypeCastOk(casted) => casted
TypeCastErr(message) => {
diagnostics.push(diag(message))
return None
}
}
None => value
}
match
binding_collection_host_constraint_rejection_message(
binding.type_name,
typed_value,
declarations,
) {
Some(message) => {
diagnostics.push(diag(message))
None
}
None => {
cache.push({ name: binding.name, value: typed_value })
Some(typed_value)
}
}
}
None => None
}
}
None =>
match lookup_value(cache, "super") {
Some(ObjectValue(parent_members)) =>
if is_module_member_set(parent_members) {
resolve_module_parent_member_value(
name,
parent_members,
[],
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
)
} else {
None
}
_ => None
}
}
}
///|
fn push_receiver_method_bindings(
method_cache : Array[ValueBinding],
receiver_members : Array[ValueMember],
) -> Unit {
for value_member in receiver_members {
method_cache.push({ name: value_member.name, value: value_member.value })
// PKL-148e: hidden / local class properties are stored under their
// visibility prefix. The method body references them by the bare
// name (`c`, not `@hidden$c` / `@local$c`); push the stripped form
// alongside the prefixed one so cache lookup resolves both.
if is_invisible_member_name(value_member.name) {
method_cache.push({
name: strip_member_visibility_prefix(value_member.name),
value: value_member.value,
})
}
}
method_cache.push({ name: "this", value: ObjectValue(receiver_members) })
}
///|
/// PKL-117: marker pushed onto `method_cache` so `super.method(...)`
/// can find the enclosing class. The marker key uses an `@` prefix
/// — the lexer treats `@` as its own token so the name can't
/// collide with a user-written Pkl identifier.
fn push_super_dispatch_marker(
method_cache : Array[ValueBinding],
current_class : String,
) -> Unit {
method_cache.push({
name: "@current_class",
value: StringValue(current_class),
})
}
///|
/// PKL-148e: expose sibling class methods to a method body. Apple Pkl
/// scopes class methods like locals — `function compute() = b(c)` can
/// call `function b(x) = ...` declared in the same class without
/// `this.b(c)`. Walk the parent chain so an override (or new method)
/// in a subclass shadows the parent definition, matching the
/// inheritance order. Already-bound names (parameters, properties) win
/// because they were pushed onto the cache earlier.
fn push_sibling_class_methods(
method_cache : Array[ValueBinding],
class_name : String,
class_env : Array[ClassBinding],
env : Array[ValueBinding],
cache : Array[ValueBinding],
) -> Unit {
let captured_env = capture_value_bindings(env, cache)
// Collect the inheritance chain bottom-up, then push parent → derived
// so a derived class's method ends up *after* its parent's same-named
// method in the cache. `lookup_value` returns the last match, so the
// most-derived override wins (virtual dispatch).
let chain : Array[ClassBinding] = []
let mut current : String? = Some(class_name)
let seen : Array[String] = []
while current is Some(name) {
if contains_string(seen, name) {
break
}
seen.push(name)
match lookup_class_binding(class_env, name) {
Some(class_binding) => {
chain.push(class_binding)
current = class_binding.parent_name
}
None => current = None
}
}
for i = chain.length() - 1; i >= 0; i = i - 1 {
for class_method in chain[i].methods {
match class_method.body {
Some(body) =>
method_cache.push({
name: class_method.name,
value: FunctionValue(
class_method.parameters,
body,
class_method.return_type_name,
captured_env,
fresh_function_id(),
),
})
None => ()
}
}
}
}
///|
/// Stable hidden-member key used to attach a class name to an
/// ObjectValue. Today this is only set for `new Dynamic { ... }`
/// expressions so diagnostic output can render `new Dynamic {}` and
/// `got type \`Dynamic\`` instead of the generic `Object` / `new {}`
/// form. The user-class tagging is deferred — broader adoption would
/// shift many downstream diagnostic surfaces in one go.
fn class_tag_member_name() -> String {
hidden_member_name("__class")
}
///|
fn tag_object_with_class(
members : Array[ValueMember],
class_name : String,
) -> Array[ValueMember] {
// PKL-148bh: tag every typed ObjectValue with the constructing
// class name. The marker uses the hidden prefix so the renderer
// and visible_members filter it out automatically; downstream
// dispatch paths (`eval_value_type_name`, `render_pcf_value_inline`,
// `is X` checks) read it via `find_object_class_tag`.
let tag_name = class_tag_member_name()
for m in members {
if m.name == tag_name {
return members
}
}
let tagged : Array[ValueMember] = [
{
name: tag_name,
value: StringValue(class_name),
source: None,
annotations: [],
},
]
for m in members {
tagged.push(m)
}
tagged
}
///|
fn find_object_class_tag(members : Array[ValueMember]) -> String? {
let tag_name = class_tag_member_name()
for m in members {
if m.name == tag_name {
return match m.value {
StringValue(s) => Some(s)
_ => None
}
}
}
None
}
///|
fn renderer_format_for_class_name(class_name : String) -> String? {
if class_name.has_suffix("PcfRenderer") {
Some("pcf")
} else if class_name.has_suffix("JsonRenderer") {
Some("json")
} else if class_name.has_suffix("YamlRenderer") {
Some("yaml")
} else if class_name.has_suffix("PropertiesRenderer") {
Some("properties")
} else if class_name.has_suffix("PListRenderer") {
Some("plist")
} else if class_name == "xml.Renderer" || class_name.has_suffix("XmlRenderer") {
Some("xml")
} else if class_name == "protobuf.Renderer" ||
class_name.has_suffix("ProtobufRenderer") {
Some("textproto")
} else if class_name == "jsonnet.Renderer" {
Some("jsonnet")
} else if class_name == "pklbinary.Renderer" {
Some("pklbinary")
} else {
None
}
}
///|
fn renderer_format_from_members(members : Array[ValueMember]) -> String? {
match lookup_member(members, "__rendererFormat") {
Some(StringValue(format)) => Some(format)
_ =>
match find_object_class_tag(members) {
Some(class_name) => renderer_format_for_class_name(class_name)
None => None
}
}
}
///|
fn object_class_tag_matches(
members : Array[ValueMember],
expected : String,
) -> Bool {
match find_object_class_tag(members) {
Some(class_name) => {
let class_head = match class_name.find("<") {
Some(idx) => String::unsafe_substring(class_name, start=0, end=idx)
None => class_name
}
class_head == expected || class_head.has_suffix(".\{expected}")
}
None => false
}
}
///|
fn render_directive_text(value : Value) -> String? {
match force_eval_thunk(value) {
ObjectValue(members) =>
if object_class_tag_matches(members, "RenderDirective") {
match lookup_member(members, "text") {
Some(StringValue(text)) => Some(text)
_ => None
}
} else {
None
}
_ => None
}
}
///|
/// PKL-148bh: like `eval_value_type_name`, but when the value's
/// class tag matches a user-declared class in scope, prefix the
/// surrounding module name (`#`). Stdlib types
/// (`Int`, `String`, `Listing`, …) and the `Dynamic` tag keep their
/// bare form because Apple Pkl never qualifies them.
fn qualify_value_type_name(
value : Value,
class_env : Array[ClassBinding],
module_name : String?,
) -> String {
let bare = eval_value_type_name(value)
match module_name {
Some(m) =>
if bare == "Dynamic" || bare == "Object" || is_stdlib_class_name(bare) {
bare
} else if lookup_class_binding(class_env, bare) is Some(_) {
"\{m}#\{bare}"
} else {
bare
}
None => bare
}
}
///|
fn eval_value_type_name(value : Value) -> String {
match value {
ThunkValue(_) => eval_value_type_name(force_eval_thunk(value))
IntValue(_) => "Int"
FloatValue(_) => "Float"
BoolValue(_) => "Boolean"
StringValue(_) => "String"
NullValue => "Null"
ObjectValue(members) =>
match reflect_kind(members) {
Some("Class") => "Class"
Some("TypeAlias") => "TypeAlias"
Some("Module") => "ModuleClass"
_ =>
// PKL-148bh: universal tagging emits the class name verbatim
// (`Dynamic` stays `Dynamic`; user classes surface as
// `Person` etc.). The module-qualified form
// `#` lives on the cached
// `@__module_name` marker — the diagnostic path that wants
// the qualified shape stitches it in via
// `eval_value_type_name_qualified` below.
match find_object_class_tag(members) {
Some("Module") => "ModuleClass"
Some(class_name) => class_name
None => "Object"
}
}
ListingValue(_) | DefaultedListingValue(_, _, _) => "Listing"
ListValue(_) => "List"
MappingValue(_) | DefaultedMappingValue(_, _, _) => "Mapping"
FunctionValue(_, _, _, _, _) => "Function"
DurationValue(_, _) => "Duration"
DataSizeValue(_, _) => "DataSize"
RegexValue(_) => "Regex"
BytesValue(_) => "Bytes"
PairValue(_, _) => "Pair"
IntSeqValue(_, _, _) => "IntSeq"
SetValue(_) => "Set"
MapValue(_) => "Map"
DeferredImportValue(_) => "ModuleClass"
}
}
///|
/// PKL-148: synthesize a `pkl:reflect.Class` mirror for any runtime
/// `Value`. snippetTest fixtures lean on `x.getClass().simpleName` to
/// pin a value's type at runtime — the mirror only needs `simpleName`
/// and `name` to satisfy the common usage. The `__kind` marker keeps
/// the value compatible with the existing reflect-introspection path.
fn synth_class_mirror_for_value(value : Value) -> Value {
synth_class_mirror_for_name(eval_value_type_name(value))
}
///|
/// PKL-148bh: TypeAlias mirror with a module-qualified `name` /
/// toString. When `module_name` is Some, the mirror's `name` and the
/// toString-style projection use `#` (matching
/// Apple Pkl's reflect output). When None, falls back to the bare
/// simple name.
fn synth_type_alias_mirror_for_qualified(
name : String,
module_name : String?,
) -> Value {
let qualified = match module_name {
Some(m) => "\{m}#\{name}"
None => name
}
ObjectValue([
{
name: hidden_member_name("__kind"),
value: StringValue("TypeAlias"),
source: None,
annotations: [],
},
{
name: hidden_member_name("reflectee"),
value: StringValue(name),
source: None,
annotations: [],
},
{
name: "simpleName",
value: StringValue(name),
source: None,
annotations: [],
},
{
name: "name",
value: StringValue(qualified),
source: None,
annotations: [],
},
{
name: hidden_member_name("__qualified_name"),
value: StringValue(qualified),
source: None,
annotations: [],
},
{ name: "modifiers", value: SetValue([]), source: None, annotations: [] },
])
}
///|
fn synth_class_mirror_for_name(name : String) -> Value {
synth_class_mirror_for_qualified(name, None)
}
///|
/// PKL-148bh: Class mirror with optional module qualifier. When
/// `module_name` is Some, the mirror's `name` field carries the
/// `#` form Apple Pkl uses for reflect.Class.toString
/// on a user-declared class; stdlib class mirrors keep the bare
/// simpleName for now.
fn synth_class_mirror_for_qualified(
name : String,
module_name : String?,
) -> Value {
let qualified = match module_name {
Some(m) => "\{m}#\{name}"
None => name
}
ObjectValue([
{
name: hidden_member_name("__kind"),
value: StringValue("Class"),
source: None,
annotations: [],
},
{
name: hidden_member_name("reflectee"),
value: StringValue(name),
source: None,
annotations: [],
},
{
name: "simpleName",
value: StringValue(name),
source: None,
annotations: [],
},
{
name: "name",
value: StringValue(qualified),
source: None,
annotations: [],
},
{
name: hidden_member_name("__qualified_name"),
value: StringValue(qualified),
source: None,
annotations: [],
},
// PKL-148bb: reflect.Class / reflect.TypeAlias both expose a
// `modifiers : Set` slot (annotation / visibility keywords
// declared on the type). pkl-mbt doesn't surface modifier text
// through the AST yet, so default to an empty Set — `types/modifiersForTypes`
// and other `.modifiers` consumers see the expected empty Set form.
{ name: "modifiers", value: SetValue([]), source: None, annotations: [] },
])
}
///|
/// Build the Class mirror returned by `someModule.getClass()`. Module
/// classes use the module's declared name as their display name and retain
/// their own URI; using the importing module's cache here would incorrectly
/// attribute `pkl:base` and `pkl:pklbinary` to the caller.
fn synth_module_class_mirror(name : String, uri : String) -> Value {
ObjectValue([
{
name: hidden_member_name("__kind"),
value: StringValue("Class"),
source: None,
annotations: [],
},
{
name: hidden_member_name("reflectee"),
// Keep the evaluator's module sentinel separate from the public
// display name. Reflect member lookup uses this marker to expand
// the current module's property/method metadata.
value: StringValue("module"),
source: None,
annotations: [],
},
{
name: "simpleName",
value: StringValue(name),
source: None,
annotations: [],
},
{ name: "name", value: StringValue(name), source: None, annotations: [] },
{
name: hidden_member_name("__qualified_name"),
value: StringValue(name),
source: None,
annotations: [],
},
{
name: "moduleUri",
value: StringValue(uri),
source: None,
annotations: [],
},
{ name: "modifiers", value: SetValue([]), source: None, annotations: [] },
])
}
///|
fn stdlib_module_class_display_name(uri : String) -> String? {
if uri == "pkl:base" {
Some("ModuleClass")
} else if uri.has_prefix("pkl:") {
Some("pkl." + uri[4:].to_owned())
} else {
None
}
}
///|
/// Public aliases declared by `pkl:base`. These are values of runtime type
/// `TypeAlias`, even when their constraints ultimately narrow `Int` or
/// `String`.
fn is_stdlib_type_alias_name(name : String) -> Bool {
match name {
"NonNull"
| "Int8"
| "Int16"
| "Int32"
| "UInt8"
| "UInt16"
| "UInt32"
| "UInt"
| "Comparable"
| "Char"
| "Charset"
| "Uri"
| "DurationUnit"
| "DataSizeUnit"
| "Mixin" => true
_ => false
}
}
///|
/// PKL-148e: a stdlib type name (`Int` / `Float` / `String` / etc.)
/// is usable as a class-as-value just like a user-declared `class Foo`.
/// Apple Pkl exposes them through `pkl:base` so `Int == Int`,
/// `Int == 3.getClass()`, `Int != Float` round-trip via the Class
/// mirror's `name` / `simpleName` / `reflectee` fields.
fn is_stdlib_class_name(name : String) -> Bool {
match name {
"Int"
| "Int8"
| "Int16"
| "Int32"
| "UInt"
| "UInt8"
| "UInt16"
| "UInt32"
| "Float"
| "Number"
| "String"
| "Boolean"
| "Bool"
| "Null"
| "Bytes"
| "Duration"
| "DataSize"
| "Regex"
| "Listing"
| "Mapping"
| "Set"
| "Map"
| "List"
| "Pair"
| "IntSeq"
| "Mixin"
| "Dynamic"
| "Typed"
| "Object"
| "Any"
| "Class"
| "Module"
| "TypeAlias"
| "Annotation"
| "Resource"
| "Function"
| "Function0"
| "Function1"
| "Function2"
| "Function3"
| "Function4"
| "Function5"
| "BaseValueRenderer"
| "ValueRenderer"
| "BytesRenderer"
| "PcfRenderer"
| "JsonRenderer"
| "YamlRenderer"
| "PropertiesRenderer"
| "PListRenderer"
| "ConvertProperty"
| "RenderDirective"
// PKL-148bh: `module` is Apple Pkl's annotation for "the type of
// the enclosing module" — treat as a stdlib-accepting class name
// so the eval-side rejection path doesn't blanket-reject every
// signature that mentions it (types/currentModuleType*).
| "module"
// PKL-148bh: `unknown` is Apple Pkl's "I don't care" type — same
// posture as `Any` for runtime rejection. Used in
// basic/newInAmendingModuleMethod's `function parrot(): unknown`.
| "unknown" => true
_ => false
}
}
///|
/// PKL-152: stdlib classes that Apple Pkl forbids `new`-instantiating
/// or in-place amending. List / Set / Map / Pair / IntSeq are built
/// via constructor functions; the scalar / primitive classes (Int /
/// Float / Bool / String / Bytes / Duration / DataSize / Regex /
/// Function / Class / TypeAlias / Module / Annotation)
/// have literal or reflection-only construction surfaces. The
/// `new`-instantiable bases (Listing / Mapping / Dynamic / Object /
/// Typed) and the typing aliases (Number / Any / Null / module /
/// unknown plus the UInt* / Int* widths) are intentionally absent.
fn is_external_only_class_name(name : String) -> Bool {
match name {
"Int"
| "Float"
| "Number"
| "String"
| "Boolean"
| "Bool"
| "Null"
| "Bytes"
| "Duration"
| "DataSize"
| "Regex"
| "Set"
| "Map"
| "List"
| "Pair"
| "IntSeq"
| "Class"
| "TypeAlias"
| "Module"
| "Annotation"
| "Function"
| "Function0"
| "Function1"
| "Function2"
| "Function3"
| "Function4"
| "Function5" => true
_ => false
}
}
///|
/// PKL-152: Apple Pkl marks `ValueRenderer` (and a handful of other
/// base classes) as `abstract`; instantiating them via `new` raises
/// "Cannot instantiate abstract class `X`.". The stdlib doesn't get
/// parsed through our regular ClassDeclaration path, so the abstract
/// set is hard-coded here. User-declared abstract classes route
/// through `is_abstract_user_class_name` once class-modifier
/// extraction lands.
fn is_abstract_class_name(name : String) -> Bool {
match name {
"ValueRenderer" | "FileRenderer" | "Renderer" => true
_ => false
}
}