//===----------------------------------------------------------------------===// // Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //===----------------------------------------------------------------------===// /// Type-safe deferred references to values not known at evaluation time. /// /// References are an advanced API design tool that enables library authors to express /// domain-specific references to values that may not be known during evaluation. /// They are particularly suited for configuring execution systems where tasks have well-typed /// inputs and output. /// /// WARNING: The API and semantics of references are subject to change in a future release. /// The Pkl team is soliciting feedback from authors of libraries considering adopting references. /// For questions and feedback, please reach out via /// [GitHub Discussions](https://github.com/apple/pkl/discussions) or /// [create an issue](https://github.com/apple/pkl/issues/new). @ModuleInfo { minPklVersion = "0.32.0" } module pkl.ref /// Creates a deferred [Reference] to a value of type [class] in the given [domain]. /// /// References may only be constructed for single, non-generic class types. /// To create a reference to other types (generic classes, union, nullable, constrained, /// typealiases, etc.), it may be necessary to create a wrapper class with a property of the desired /// type. /// Example: /// ``` /// referenceListingString: Reference> = Reference(domain, Holder, data).$ /// /// class Holder { /// $: Listing /// } /// ``` external const function Reference( domain: D(this is Domain), `class`: Class, data: Any, ): Reference /// A reference to a value that may not exist at evaluation time. /// /// References are an advanced API design tool that enables library authors to express /// domain-specific references to values that may not be known during evaluation. /// /// Instances are created with the [Reference()] constructor method. /// References consist of four parts: /// * [Domain]: determines which references are compatible and how references are rendered /// as strings. /// * Data: an arbitrary value that may contain domain-specific information about the /// referenced value. /// * Path: a [List] of [Access] values indicating how the reference was accessed (by property or /// subscript). /// * Referent type: the type of the value that the reference refers to. /// /// Every time a reference is accessed, either via qualified property access /// (`.`) or subscript (`[]`), a new reference is returned. /// The new reference shares the same domain and data as the original reference. /// The new reference's path extends the original reference's with an [Access] instance describing /// the accessed property name or subscript key. /// The new reference's referent type is the type of the accessed property or subscript value of the /// original referent type. /// Properties with the `external` modifier may not be referenced. /// Any type constraints within the referent type are erased and type constraints are not allowed /// in the referent (second) type argument of any Reference type annotations. /// /// Example: /// ``` /// import "pkl:ref" /// /// /// Define a domain class for this configuration system: /// class Domain extends ref.Domain { /// /// Define how this domain renders references to strings: /// function renderReference(reference: ref.Reference): String = /// ( /// List(reference.getData().toString()) /// + reference.getPath().map((it) -> it.property ?? it.key.toString()) /// ).join("/") /// } /// /// class Resource { /// name: String /// /// Types that provide references will often vend a "root" Reference via a `fixed` property. /// /// Here, this references [Outputs] and the `data` identifies the enclosing [Resource]. /// fixed outputs: ref.Reference = ref.Reference(new Domain {}, Outputs, name) /// } /// /// class Outputs { /// a: String /// b: String(length < 5) /// c: Listing /// d: Mapping /// e: Foo | Bar /// } /// /// class Foo { /// x: Int /// /// function toString(): String = "foo:\(x)" /// } /// /// class Bar { /// x: Float /// /// function toString(): String = "bar:\(x)" /// } /// /// local outputs: ref.Reference = new Resource { name = "root" }.outputs /// /// /// Simple property access. /// /// getPath() == List(new ref.Access { property = "a" }) /// aRef: ref.Reference = outputs.a /// /// /// Type constraint erasure. /// /// getPath() == List(new ref.Access { property = "b" }) /// bRef: ref.Reference = outputs.b /// /// /// Simple subscript access. /// /// getPath() == List(new ref.Access { property = "c" }, new ref.Access { key = 10 }) /// cRef: ref.Reference = outputs.c[10] /// /// /// Simple property access: /// /// getPath() == List(new ref.Access { property = "d" }, new ref.Access { key = new Foo { x = 1 } }) /// /// The type constraint on the Mapping value type argument is also erased. /// dRef: ref.Reference = outputs.d[new Foo { x = 1 }] /// /// /// Simple property access: /// /// getPath() == List(new ref.Access { property = "e" }, new ref.Access { property = "x" }) /// eRef: ref.Reference = outputs.e.x /// /// // Render references as strings: /// output { /// renderer { /// converters { /// [ref.Reference] = (it) -> it.toString() /// } /// } /// } /// ``` /// /// Output: /// ``` /// aRef = "root/a" /// bRef = "root/b" /// cRef = "root/c/10" /// dRef = "root/d/foo:1" /// eRef = "root/e/x" /// ``` external class Reference { /// The domain the reference belongs to. external function getDomain(): D /// Arbitrary data attached to the reference during creation. /// /// Used for domain-specific purposes such as controlling how the reference is rendered. external function getData(): Any /// The path of property and subscript access applied to the reference. external function getPath(): List /// Render the reference to a string using its domain's ([getDomain()]) /// [Domain.referencetoString()] method. function toString(): String = getDomain().renderReference(this) } /// Represents a configuration domain that a [Reference] may exist within. /// /// Library authors supporting references should declare a subclass of [Domain] to be shared by all /// inter-compatible [Reference] instances. /// /// A domain also determines how [Reference] values are transformed into strings; see /// [renderReference()]. abstract class Domain { /// Must be overridden by domain classes to determine how [Reference] instances are transformed to /// strings by [Reference.toString()] and string interpolation. abstract function renderReference(reference: Reference): String } /// Represents a property or subscript access as part of a [Reference]'s path. class Access { /// Indicates if this represents a property access. fixed isProperty: Boolean = property != null /// Indicates if this represents a subscript access. fixed isSubscript: Boolean = property == null /// The property that was accessed. /// /// If non-null, this is a property access. If `null` this is a subscript access. property: String(key == null)? /// The subscript key that was accessed. /// /// May be null even when [property] is null, which represents a subscript access with key `null`. key: Any }