mirror of
https://github.com/apple/pkl.git
synced 2026-06-27 15:56:23 +02:00
158f709ed4
This adjusts the doc comments in ref.pkl * Fix incorrect code snippets * Clean up examples * Remove some sections * Make phrasing consistent with the rest of the stdlib
235 lines
8.0 KiB
Plaintext
235 lines
8.0 KiB
Plaintext
//===----------------------------------------------------------------------===//
|
|
// 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. These include:
|
|
///
|
|
/// * CI pipelines
|
|
/// * Build graphs
|
|
/// * Workflow tools
|
|
///
|
|
/// _**WARNING**_: This module is _experimental_ and not ready for production use.
|
|
@ModuleInfo { minPklVersion = "0.32.0" }
|
|
@Since { version = "0.32.0" }
|
|
module pkl.ref
|
|
|
|
/// Creates a deferred [Reference] to a value of type [class] in the given [domain] and [data].
|
|
///
|
|
/// 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.), use a wrapper class with a property of the desired type.
|
|
///
|
|
/// Example:
|
|
/// ```
|
|
/// myReference: Reference<MyDomain, Listing<String>> = Reference(domain, Holder, data).$
|
|
///
|
|
/// class Holder {
|
|
/// $: Listing<String>
|
|
/// }
|
|
/// ```
|
|
external const function Reference<D, T>(
|
|
domain: D(this is Domain),
|
|
`class`: Class<T>,
|
|
data: Any,
|
|
): Reference<D, T>
|
|
|
|
/// References are an advanced API design tool that enables library authors to express
|
|
/// domain-specific values, whose actual underlying values are not known to Pkl during evaluation.
|
|
///
|
|
/// References provide two features.
|
|
///
|
|
/// First, they allow users to convey expressions in the target format using plain expressions in
|
|
/// Pkl itself.
|
|
///
|
|
/// Second, they provide type safety:
|
|
///
|
|
/// 1. Pkl will typecheck a reference's _domain_ and _referent type_.
|
|
/// 2. Dot access and subscript access is checked.
|
|
///
|
|
/// Instances are either created through the [Reference()] constructor method,
|
|
/// or synthetically through member access.
|
|
///
|
|
/// References consist of four parts:
|
|
///
|
|
/// 1. [domain][getDomain()]: determines which references are compatible and how references are
|
|
/// rendered as strings.
|
|
/// 2. [data][getData()]: an arbitrary value that may contain domain-specific information about
|
|
/// the referenced value.
|
|
/// 3. [path][getPath()]: a `List<Access>` of values indicating how the reference was accessed
|
|
/// (by property or subscript).
|
|
/// 4. Referent type: the type of the value that the reference refers to.
|
|
/// This type is internally held, and not exposed as an in-language value.
|
|
///
|
|
/// ## Member access
|
|
///
|
|
/// A reference contains synthetic members.
|
|
/// These synthetic members may be accessed using the dot and subscript operators, and are
|
|
/// generated when the member access itself is executed.
|
|
///
|
|
/// ```
|
|
/// myRef.name
|
|
/// myRef["bar"]
|
|
/// ```
|
|
///
|
|
/// Members are synthesized based on the referent type:
|
|
/// Given the following declaration:
|
|
///
|
|
/// ```
|
|
/// class Bird { name: String }
|
|
/// ```
|
|
///
|
|
/// A `Reference<MyDomain, Bird>` will contain synthetic property
|
|
/// `name: Reference<MyDomain, String>`.
|
|
///
|
|
/// The synthesized reference contains the same [domain][getDomain()] and [data][getData()] as the
|
|
/// original reference.
|
|
/// This reference's path extends the original reference's with an [Access] instance describing
|
|
/// the accessed property name or subscript key.
|
|
///
|
|
/// **Limitations**
|
|
///
|
|
/// 1. Properties with the `external` modifier may not be accessed.
|
|
/// 2. Properties defined inside external classes may not be accessed.
|
|
/// 3. The [Listing.default], [Mapping.default], and [Dynamic.default] properties may not be
|
|
/// accessed.
|
|
/// 4. Constrained types cannot be referent types.
|
|
/// - Declaring `Reference<MyDomain, String(length.isOdd)>` will throw an error.
|
|
/// - Member access will return a new reference whose referent type has its constraints erased.
|
|
///
|
|
/// ## Domain
|
|
///
|
|
/// The [Domain] parameter identifies the system that this reference exists inside.
|
|
/// Additionally, it defines how references should be rendered when Pkl produces textual output.
|
|
///
|
|
/// Example:
|
|
/// ```
|
|
/// import "pkl:ref"
|
|
///
|
|
/// class MyDomain extends ref.Domain {
|
|
/// // Define how this domain renders references to strings:
|
|
/// function renderReference(reference: ref.Reference<MyDomain, Any>): String =
|
|
/// reference.getData().toString()
|
|
/// + "/"
|
|
/// + reference.getPath().map((it) -> it.property ?? it.key.toString()).join("/")
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// A typealias can be used to improve ergonomics:
|
|
///
|
|
/// ```
|
|
/// typealias MyReference<T> = ref.Reference<MyDomain, T>
|
|
/// ```
|
|
///
|
|
/// ## Example
|
|
///
|
|
/// ```
|
|
/// import "pkl:ref"
|
|
///
|
|
/// class Foo extends ref.Domain {
|
|
/// function renderReference(reference: ref.Reference<Foo, Any>) =
|
|
/// "{{ "
|
|
/// + reference.getData().toString()
|
|
/// + reference
|
|
/// .getPath()
|
|
/// .map((it) -> if (it.isProperty) "/\(it.property)" else "[\(it.key)]")
|
|
/// .join("")
|
|
/// + " }}"
|
|
/// }
|
|
///
|
|
/// typealias FooReference<T> = ref.Reference<Foo, T>
|
|
///
|
|
/// class Outputs {
|
|
/// file: String
|
|
///
|
|
/// properties: Mapping<String, String>
|
|
/// }
|
|
///
|
|
/// local outputs: FooReference<Outputs> = ref.Reference(new Foo {}, Outputs, "outputs")
|
|
///
|
|
/// // Dot access; gives a `Reference<Foo, String>`
|
|
/// myFile = outputs.file
|
|
///
|
|
/// // Subscript access; gives a `Reference<Foo, String>`
|
|
/// myProperty = outputs.properties["myProperty"]
|
|
///
|
|
/// output {
|
|
/// renderer {
|
|
/// converters {
|
|
/// [ref.Reference] = (it) -> it.toString()
|
|
/// }
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// Output:
|
|
/// ```
|
|
/// myFile = "{{ outputs/file }}"
|
|
/// myProperty = "{{ outputs/properties[myProperty] }}"
|
|
/// ```
|
|
external class Reference<out D, out T> {
|
|
/// Returns the domain this reference belongs to.
|
|
external function getDomain(): D
|
|
|
|
/// Returns 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
|
|
|
|
/// Returns the path of property and subscript accesses applied to this reference.
|
|
external function getPath(): List<Access>
|
|
|
|
/// Returns a string representation of this reference using its domain's
|
|
/// [Domain.renderReference()] method.
|
|
function toString(): String = getDomain().renderReference(this)
|
|
}
|
|
|
|
/// A configuration domain that a [Reference] may exist within.
|
|
///
|
|
/// Only references with the same [Domain] are compatible with each other.
|
|
///
|
|
/// A domain also determines how [Reference] values are transformed into strings; see
|
|
/// [renderReference()].
|
|
abstract class Domain {
|
|
/// Determines how [Reference] instances are transformed to strings by [Reference.toString()] and
|
|
/// string interpolation.
|
|
abstract function renderReference(reference: Reference<Domain, Any>): String
|
|
}
|
|
|
|
/// A property or subscript access as part of a [Reference]'s path.
|
|
class Access {
|
|
/// Tells if this represents a property access.
|
|
fixed isProperty: Boolean = property != null
|
|
|
|
/// Tells 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`
|
|
/// (e.g. `foo[null]`).
|
|
key: Any
|
|
}
|