Remove deprecated things and compatibility behaviours (#5591)

This commit is contained in:
Laurenz 2024-12-17 10:25:15 +01:00 committed by GitHub
parent 51020fcf3c
commit ed67220e4b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 35 additions and 411 deletions

View File

@ -119,7 +119,6 @@ pub(super) fn define(global: &mut Scope, inputs: Dict, features: &Features) {
global.define_func::<panic>();
global.define_func::<assert>();
global.define_func::<eval>();
global.define_func::<style>();
if features.is_enabled(Feature::Html) {
global.define_func::<target>();
}

View File

@ -36,11 +36,6 @@ pub fn join(lhs: Value, rhs: Value) -> StrResult<Value> {
(Symbol(a), Content(b)) => Content(TextElem::packed(a.get()) + b),
(Array(a), Array(b)) => Array(a + b),
(Dict(a), Dict(b)) => Dict(a + b),
// Type compatibility.
(Type(a), Str(b)) => Str(format_str!("{a}{b}")),
(Str(a), Type(b)) => Str(format_str!("{a}{b}")),
(a, b) => mismatch!("cannot join {} with {}", a, b),
})
}
@ -157,10 +152,6 @@ pub fn add(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
(Datetime(a), Duration(b)) => Datetime(a + b),
(Duration(a), Datetime(b)) => Datetime(b + a),
// Type compatibility.
(Type(a), Str(b)) => Str(format_str!("{a}{b}")),
(Str(a), Type(b)) => Str(format_str!("{a}{b}")),
(Dyn(a), Dyn(b)) => {
// Alignments can be summed.
if let (Some(&a), Some(&b)) =
@ -469,9 +460,6 @@ pub fn equal(lhs: &Value, rhs: &Value) -> bool {
rat == rel.rel && rel.abs.is_zero()
}
// Type compatibility.
(Type(ty), Str(str)) | (Str(str), Type(ty)) => ty.compat_name() == str.as_str(),
_ => false,
}
}
@ -569,10 +557,6 @@ pub fn contains(lhs: &Value, rhs: &Value) -> Option<bool> {
(Str(a), Dict(b)) => Some(b.contains(a)),
(a, Array(b)) => Some(b.contains(a.clone())),
// Type compatibility.
(Type(a), Str(b)) => Some(b.as_str().contains(a.compat_name())),
(Type(a), Dict(b)) => Some(b.contains(a.compat_name())),
_ => Option::None,
}
}

View File

@ -3,75 +3,19 @@ use std::fmt::{self, Debug, Formatter};
use std::hash::{Hash, Hasher};
use std::{mem, ptr};
use comemo::{Track, Tracked};
use comemo::Tracked;
use ecow::{eco_vec, EcoString, EcoVec};
use smallvec::SmallVec;
use typst_syntax::Span;
use typst_utils::LazyHash;
use crate::diag::{warning, SourceResult, Trace, Tracepoint};
use crate::diag::{SourceResult, Trace, Tracepoint};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, func, ty, Content, Context, Element, Func, NativeElement, Packed, Repr,
Selector, Show,
cast, ty, Content, Context, Element, Func, NativeElement, Repr, Selector,
};
use crate::introspection::Locatable;
use crate::text::{FontFamily, FontList, TextElem};
/// Provides access to active styles.
///
/// **Deprecation planned.** Use [context] instead.
///
/// ```example
/// #let thing(body) = style(styles => {
/// let size = measure(body, styles)
/// [Width of "#body" is #size.width]
/// })
///
/// #thing[Hey] \
/// #thing[Welcome]
/// ```
#[func]
pub fn style(
/// The engine.
engine: &mut Engine,
/// The call site span.
span: Span,
/// A function to call with the styles. Its return value is displayed
/// in the document.
///
/// This function is called once for each time the content returned by
/// `style` appears in the document. That makes it possible to generate
/// content that depends on the style context it appears in.
func: Func,
) -> Content {
engine.sink.warn(warning!(
span, "`style` is deprecated";
hint: "use a `context` expression instead"
));
StyleElem::new(func).pack().spanned(span)
}
/// Executes a style access.
#[elem(Locatable, Show)]
struct StyleElem {
/// The function to call with the styles.
#[required]
func: Func,
}
impl Show for Packed<StyleElem> {
#[typst_macros::time(name = "style", span = self.span())]
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
let context = Context::new(self.location(), Some(styles));
Ok(self
.func()
.call(engine, context.track(), [styles.to_map()])?
.display())
}
}
/// A list of style properties.
#[ty(cast)]
#[derive(Default, PartialEq, Clone, Hash)]

View File

@ -44,16 +44,6 @@ use crate::foundations::{
/// #type(int) \
/// #type(type)
/// ```
///
/// # Compatibility
/// In Typst 0.7 and lower, the `type` function returned a string instead of a
/// type. Compatibility with the old way will remain for a while to give package
/// authors time to upgrade, but it will be removed at some point.
///
/// - Checks like `{int == "integer"}` evaluate to `{true}`
/// - Adding/joining a type and string will yield a string
/// - The `{in}` operator on a type and a dictionary will evaluate to `{true}`
/// if the dictionary has a string key matching the type's name
#[ty(scope, cast)]
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct Type(Static<NativeTypeData>);
@ -111,14 +101,6 @@ impl Type {
}
}
// Type compatibility.
impl Type {
/// The type's backward-compatible name.
pub fn compat_name(&self) -> &str {
self.long_name()
}
}
#[scope]
impl Type {
/// Determines a value's type.

View File

@ -7,7 +7,7 @@ use smallvec::{smallvec, SmallVec};
use typst_syntax::Span;
use typst_utils::NonZeroExt;
use crate::diag::{bail, warning, At, HintedStrResult, SourceResult};
use crate::diag::{bail, At, HintedStrResult, SourceResult};
use crate::engine::{Engine, Route, Sink, Traced};
use crate::foundations::{
cast, elem, func, scope, select_where, ty, Args, Array, Construct, Content, Context,
@ -353,7 +353,7 @@ impl Counter {
}
/// Shared implementation of displaying between `counter.display` and
/// `DisplayElem`, which will be deprecated.
/// `CounterDisplayElem`.
fn display_impl(
&self,
engine: &mut Engine,
@ -441,11 +441,6 @@ impl Counter {
/// Displays the current value of the counter with a numbering and returns
/// the formatted output.
///
/// _Compatibility:_ For compatibility with Typst 0.10 and lower, this
/// function also works without an established context. Then, it will create
/// opaque contextual content rather than directly returning the output of
/// the numbering. This behaviour will be removed in a future release.
#[func(contextual)]
pub fn display(
self,
@ -474,19 +469,8 @@ impl Counter {
#[default(false)]
both: bool,
) -> SourceResult<Value> {
if let Ok(loc) = context.location() {
self.display_impl(engine, loc, numbering, both, context.styles().ok())
} else {
engine.sink.warn(warning!(
span, "`counter.display` without context is deprecated";
hint: "use it in a `context` expression instead"
));
Ok(CounterDisplayElem::new(self, numbering, both)
.pack()
.spanned(span)
.into_value())
}
let loc = context.location().at(span)?;
self.display_impl(engine, loc, numbering, both, context.styles().ok())
}
/// Retrieves the value of the counter at the given location. Always returns
@ -495,10 +479,6 @@ impl Counter {
/// The `selector` must match exactly one element in the document. The most
/// useful kinds of selectors for this are [labels]($label) and
/// [locations]($location).
///
/// _Compatibility:_ For compatibility with Typst 0.10 and lower, this
/// function also works without a known context if the `selector` is a
/// location. This behaviour will be removed in a future release.
#[func(contextual)]
pub fn at(
&self,
@ -526,21 +506,8 @@ impl Counter {
context: Tracked<Context>,
/// The callsite span.
span: Span,
/// _Compatibility:_ This argument is deprecated. It only exists for
/// compatibility with Typst 0.10 and lower and shouldn't be used
/// anymore.
#[default]
location: Option<Location>,
) -> SourceResult<CounterState> {
if location.is_none() {
context.location().at(span)?;
} else {
engine.sink.warn(warning!(
span, "calling `counter.final` with a location is deprecated";
hint: "try removing the location argument"
));
}
context.introspect().at(span)?;
let sequence = self.sequence(engine)?;
let (mut state, page) = sequence.last().unwrap().clone();
if self.is_page() {
@ -761,8 +728,6 @@ impl Count for Packed<CounterUpdateElem> {
}
/// Executes a display of a counter.
///
/// **Deprecation planned.**
#[elem(Construct, Locatable, Show)]
pub struct CounterDisplayElem {
/// The counter.
@ -788,7 +753,6 @@ impl Construct for CounterDisplayElem {
}
impl Show for Packed<CounterDisplayElem> {
#[typst_macros::time(name = "counter.display", span = self.span())]
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
Ok(self
.counter

View File

@ -1,13 +1,9 @@
use comemo::{Track, Tracked};
use typst_syntax::Span;
use comemo::Tracked;
use crate::diag::{warning, HintedStrResult, SourceResult};
use crate::diag::HintedStrResult;
use crate::engine::Engine;
use crate::foundations::{
cast, elem, func, Content, Context, Func, LocatableSelector, NativeElement, Packed,
Show, StyleChain, Value,
};
use crate::introspection::{Locatable, Location};
use crate::foundations::{func, Context, LocatableSelector};
use crate::introspection::Location;
/// Determines the location of an element in the document.
///
@ -26,23 +22,12 @@ use crate::introspection::{Locatable, Location};
///
/// = Introduction <intro>
/// ```
///
/// # Compatibility
/// In Typst 0.10 and lower, the `locate` function took a closure that made the
/// current location in the document available (like [`here`] does now). This
/// usage pattern is deprecated. Compatibility with the old way will remain for
/// a while to give package authors time to upgrade. To that effect, `locate`
/// detects whether it received a selector or a user-defined function and
/// adjusts its semantics accordingly. This behaviour will be removed in the
/// future.
#[func(contextual)]
pub fn locate(
/// The engine.
engine: &mut Engine,
/// The callsite context.
context: Tracked<Context>,
/// The span of the `locate` call.
span: Span,
/// A selector that should match exactly one element. This element will be
/// located.
///
@ -50,70 +35,7 @@ pub fn locate(
/// - [`here`] to locate the current context,
/// - a [`location`] retrieved from some queried element via the
/// [`location()`]($content.location) method on content.
selector: LocateInput,
) -> HintedStrResult<LocateOutput> {
Ok(match selector {
LocateInput::Selector(selector) => {
LocateOutput::Location(selector.resolve_unique(engine.introspector, context)?)
}
LocateInput::Func(func) => {
engine.sink.warn(warning!(
span, "`locate` with callback function is deprecated";
hint: "use a `context` expression instead"
));
LocateOutput::Content(LocateElem::new(func).pack().spanned(span))
}
})
}
/// Compatible input type.
pub enum LocateInput {
Selector(LocatableSelector),
Func(Func),
}
cast! {
LocateInput,
v: Func => {
if v.element().is_some() {
Self::Selector(Value::Func(v).cast()?)
} else {
Self::Func(v)
}
},
v: LocatableSelector => Self::Selector(v),
}
/// Compatible output type.
pub enum LocateOutput {
Location(Location),
Content(Content),
}
cast! {
LocateOutput,
self => match self {
Self::Location(v) => v.into_value(),
Self::Content(v) => v.into_value(),
},
v: Location => Self::Location(v),
v: Content => Self::Content(v),
}
/// Executes a `locate` call.
#[elem(Locatable, Show)]
struct LocateElem {
/// The function to call with the location.
#[required]
func: Func,
}
impl Show for Packed<LocateElem> {
#[typst_macros::time(name = "locate", span = self.span())]
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
let location = self.location().unwrap();
let context = Context::new(Some(location), Some(styles));
Ok(self.func().call(engine, context.track(), [location])?.display())
}
selector: LocatableSelector,
) -> HintedStrResult<Location> {
selector.resolve_unique(engine.introspector, context)
}

View File

@ -1,10 +1,8 @@
use comemo::Tracked;
use typst_syntax::Span;
use crate::diag::{warning, HintedStrResult};
use crate::diag::HintedStrResult;
use crate::engine::Engine;
use crate::foundations::{func, Array, Context, LocatableSelector, Value};
use crate::introspection::Location;
/// Finds elements in the document.
///
@ -142,8 +140,6 @@ pub fn query(
engine: &mut Engine,
/// The callsite context.
context: Tracked<Context>,
/// The span of the `query` call.
span: Span,
/// Can be
/// - an element function like a `heading` or `figure`,
/// - a `{<label>}`,
@ -152,20 +148,8 @@ pub fn query(
///
/// Only [locatable]($location/#locatable) element functions are supported.
target: LocatableSelector,
/// _Compatibility:_ This argument is deprecated. It only exists for
/// compatibility with Typst 0.10 and lower and shouldn't be used anymore.
#[default]
location: Option<Location>,
) -> HintedStrResult<Array> {
if location.is_none() {
context.introspect()?;
} else {
engine.sink.warn(warning!(
span, "calling `query` with a location is deprecated";
hint: "try removing the location argument"
));
}
context.introspect()?;
let vec = engine.introspector.query(&target.0);
Ok(vec.into_iter().map(Value::Content).collect())
}

View File

@ -2,7 +2,7 @@ use comemo::{Track, Tracked, TrackedMut};
use ecow::{eco_format, eco_vec, EcoString, EcoVec};
use typst_syntax::Span;
use crate::diag::{bail, warning, At, SourceResult};
use crate::diag::{bail, At, SourceResult};
use crate::engine::{Engine, Route, Sink, Traced};
use crate::foundations::{
cast, elem, func, scope, select_where, ty, Args, Construct, Content, Context, Func,
@ -305,10 +305,6 @@ impl State {
/// The `selector` must match exactly one element in the document. The most
/// useful kinds of selectors for this are [labels]($label) and
/// [locations]($location).
///
/// _Compatibility:_ For compatibility with Typst 0.10 and lower, this
/// function also works without a known context if the `selector` is a
/// location. This behaviour will be removed in a future release.
#[typst_macros::time(name = "state.at", span = span)]
#[func(contextual)]
pub fn at(
@ -336,21 +332,8 @@ impl State {
context: Tracked<Context>,
/// The callsite span.
span: Span,
/// _Compatibility:_ This argument is deprecated. It only exists for
/// compatibility with Typst 0.10 and lower and shouldn't be used
/// anymore.
#[default]
location: Option<Location>,
) -> SourceResult<Value> {
if location.is_none() {
context.location().at(span)?;
} else {
engine.sink.warn(warning!(
span, "calling `state.final` with a location is deprecated";
hint: "try removing the location argument"
));
}
context.introspect().at(span)?;
let sequence = self.sequence(engine)?;
Ok(sequence.last().unwrap().clone())
}
@ -375,30 +358,6 @@ impl State {
) -> Content {
StateUpdateElem::new(self.key, update).pack().spanned(span)
}
/// Displays the current value of the state.
///
/// **Deprecation planned:** Use [`get`]($state.get) instead.
#[func]
pub fn display(
self,
/// The engine.
engine: &mut Engine,
/// The span of the `display` call.
span: Span,
/// A function which receives the value of the state and can return
/// arbitrary content which is then displayed. If this is omitted, the
/// value is directly displayed.
#[default]
func: Option<Func>,
) -> Content {
engine.sink.warn(warning!(
span, "`state.display` is deprecated";
hint: "use `state.get` in a `context` expression instead"
));
StateDisplayElem::new(self, func).pack().spanned(span)
}
}
impl Repr for State {
@ -446,38 +405,3 @@ impl Show for Packed<StateUpdateElem> {
Ok(Content::empty())
}
}
/// Executes a display of a state.
///
/// **Deprecation planned.**
#[elem(Construct, Locatable, Show)]
struct StateDisplayElem {
/// The state.
#[required]
#[internal]
state: State,
/// The function to display the state with.
#[required]
#[internal]
func: Option<Func>,
}
impl Show for Packed<StateDisplayElem> {
#[typst_macros::time(name = "state.display", span = self.span())]
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
let location = self.location().unwrap();
let context = Context::new(Some(location), Some(styles));
let value = self.state().at_loc(engine, location)?;
Ok(match self.func() {
Some(func) => func.call(engine, context.track(), [value])?.display(),
None => value.display(),
})
}
}
impl Construct for StateDisplayElem {
fn construct(_: &mut Engine, args: &mut Args) -> SourceResult<Content> {
bail!(args.span, "cannot be constructed manually");
}
}

View File

@ -1,11 +1,9 @@
use comemo::Tracked;
use typst_syntax::Span;
use crate::diag::{warning, At, SourceResult};
use crate::diag::{At, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
dict, func, Content, Context, Dict, Resolve, Smart, StyleChain, Styles,
};
use crate::foundations::{dict, func, Content, Context, Dict, Resolve, Smart};
use crate::introspection::{Locator, LocatorLink};
use crate::layout::{Abs, Axes, Length, Region, Size};
@ -76,23 +74,9 @@ pub fn measure(
height: Smart<Length>,
/// The content whose size to measure.
content: Content,
/// _Compatibility:_ This argument is deprecated. It only exists for
/// compatibility with Typst 0.10 and lower and shouldn't be used anymore.
#[default]
styles: Option<Styles>,
) -> SourceResult<Dict> {
let styles = match &styles {
Some(styles) => {
engine.sink.warn(warning!(
span, "calling `measure` with a styles argument is deprecated";
hint: "try removing the styles argument"
));
StyleChain::new(styles)
}
None => context.styles().at(span)?,
};
// Create a pod region with the available space.
let styles = context.styles().at(span)?;
let pod = Region::new(
Axes::new(
width.resolve(styles).unwrap_or(Abs::inf()),

View File

@ -137,10 +137,6 @@ pub struct OutlineElem {
/// `{n => n * 2em}` would be equivalent to just specifying `{2em}`, while
/// `{n => [→ ] * n}` would indent with one arrow per nesting level.
///
/// *Migration hints:* Specifying `{true}` (equivalent to `{auto}`) or
/// `{false}` (equivalent to `{none}`) for this option is deprecated and
/// will be removed in a future release.
///
/// ```example
/// #set heading(numbering: "1.a.")
///
@ -294,7 +290,6 @@ pub trait Outlinable: Refable {
/// Defines how an outline is indented.
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum OutlineIndent {
Bool(bool),
Rel(Rel<Length>),
Func(Func),
}
@ -310,10 +305,10 @@ impl OutlineIndent {
) -> SourceResult<()> {
match indent {
// 'none' | 'false' => no indenting
None | Some(Smart::Custom(OutlineIndent::Bool(false))) => {}
None => {}
// 'auto' | 'true' => use numbering alignment for indenting
Some(Smart::Auto | Smart::Custom(OutlineIndent::Bool(true))) => {
Some(Smart::Auto) => {
// Add hidden ancestors numberings to realize the indent.
let mut hidden = Content::empty();
for ancestor in ancestors {
@ -368,11 +363,9 @@ impl OutlineIndent {
cast! {
OutlineIndent,
self => match self {
Self::Bool(v) => v.into_value(),
Self::Rel(v) => v.into_value(),
Self::Func(v) => v.into_value()
},
v: bool => OutlineIndent::Bool(v),
v: Rel<Length> => OutlineIndent::Rel(v),
v: Func => OutlineIndent::Func(v),
}

View File

@ -59,13 +59,13 @@ _Thanks to [@PgBiel](https://github.com/PgBiel) for his work on tables!_
- When context is available, [`counter.display`] now directly returns the result
of applying the numbering instead of yielding opaque content. It should not be
used anymore without context. (Deprecation planned)
- The [`state.display`] function should not be used anymore, use [`state.get`]
- The `state.display` function should not be used anymore, use [`state.get`]
instead (Deprecation planned)
- The `location` argument of [`query`], [`counter.final`], and [`state.final`]
should not be used anymore (Deprecation planned)
- The [`styles`]($measure.styles) argument of the `measure` function should not
be used anymore (Deprecation planned)
- The [`style`] function should not be used anymore, use context instead
- The `styles` argument of the `measure` function should not be used anymore
(Deprecation planned)
- The `style` function should not be used anymore, use context instead
(Deprecation planned)
- The correct context is now also provided in various other places where it is
available, e.g. in show rules, layout callbacks, and numbering functions in

View File

@ -380,11 +380,11 @@ description: Changes in Typst 0.12.0
- [`counter.display`] without an established context
- [`counter.final`] with a location
- [`state.final`] with a location
- [`state.display`]
- `state.display`
- [`query`] with a location as the second argument
- [`locate`] with a callback function
- [`measure`] with styles
- [`style`]
- `style`
## Development
- Added `typst-kit` crate which provides useful APIs for `World` implementors

View File

@ -83,7 +83,7 @@ description: Changes in early, unversioned Typst
- New [`measure`] function
- Measure the layouted size of elements
- To be used in combination with the new [`style`] function that lets you
- To be used in combination with the new `style` function that lets you
generate different content based on the style context something is inserted
into (because that affects the measured size of content)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 380 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@ -37,46 +37,6 @@
// Error: 11-12 variables from outside the context expression are read-only and cannot be modified
#context (i = 1)
--- context-compatibility-locate ---
#let s = state("x", 0)
#let compute(expr) = [
#s.update(x =>
eval(expr.replace("x", str(x)))
)
// Warning: 17-28 `state.display` is deprecated
// Hint: 17-28 use `state.get` in a `context` expression instead
New value is #s.display().
]
// Warning: 1:2-6:3 `locate` with callback function is deprecated
// Hint: 1:2-6:3 use a `context` expression instead
#locate(loc => {
// Warning: 14-32 calling `query` with a location is deprecated
// Hint: 14-32 try removing the location argument
let elem = query(<here>, loc).first()
test(s.at(elem.location()), 13)
})
#compute("10") \
#compute("x + 3") \
*Here.* <here> \
#compute("x * 2") \
#compute("x - 5")
--- context-compatibility-styling ---
// Warning: 2-53 `style` is deprecated
// Hint: 2-53 use a `context` expression instead
// Warning: 18-39 calling `measure` with a styles argument is deprecated
// Hint: 18-39 try removing the styles argument
#style(styles => measure([it], styles).width < 20pt)
--- context-compatibility-counter-display ---
#counter(heading).update(10)
// Warning: 2-44 `counter.display` without context is deprecated
// Hint: 2-44 use it in a `context` expression instead
#counter(heading).display(n => test(n, 10))
--- context-delayed-warning ---
// Ensure that the warning that triggers in the first layout iteration is not
// surfaced since it goes away in the second one. Just like errors in show

View File

@ -3,14 +3,6 @@
#test(type(ltr), direction)
#test(type(10 / 3), float)
--- type-string-compatibility ---
#test(type(10), int)
#test(type(10), "integer")
#test("is " + type(10), "is integer")
#test(int in ("integer", "string"), true)
#test(int in "integers or strings", true)
#test(str in "integers or strings", true)
--- issue-3110-type-constructor ---
// Let the error message report the type name.
// Error: 2-9 type content does not have a constructor

View File

@ -43,8 +43,6 @@ A
#set outline(fill: none)
#context test(outline.indent, none)
#outline(indent: false)
#outline(indent: true)
#outline(indent: none)
#outline(indent: auto)
#outline(indent: 2em)
@ -62,8 +60,6 @@ A
#show heading: none
#set outline(fill: none)
#outline(indent: false)
#outline(indent: true)
#outline(indent: none)
#outline(indent: auto)
#outline(indent: n => 2em * n)

View File

@ -13,12 +13,8 @@ fi
#let c = counter("mycounter")
#c.update(1)
// Warning: 1:2-7:3 `locate` with callback function is deprecated
// Hint: 1:2-7:3 use a `context` expression instead
#locate(loc => [
#context [
#c.update(2)
#c.at(loc) \
// Warning: 12-36 `locate` with callback function is deprecated
// Hint: 12-36 use a `context` expression instead
Second: #locate(loc => c.at(loc))
])
#c.get() \
Second: #context c.get()
]