Skip to content

Commit 989154a

Browse files
authored
Merge pull request #22136 from github/tausbn/yeast-translate-with-context
yeast: Add context scoping mechanism
2 parents d5712ef + 33da3ef commit 989154a

3 files changed

Lines changed: 159 additions & 79 deletions

File tree

shared/yeast/src/build.rs

Lines changed: 59 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -161,52 +161,75 @@ impl<'a, C> BuildCtx<'a, C> {
161161
}
162162

163163
impl<C: Clone> BuildCtx<'_, C> {
164-
/// Recursively translate a node via the framework's rule machinery.
165-
/// In a OneShot phase, applies OneShot rules to the given node and
166-
/// returns the resulting node ids. In a Repeating phase, errors
167-
/// (translation is not meaningful when input and output share a
168-
/// schema).
164+
/// Recursively translate every id in the given iterable via the
165+
/// framework's rule machinery. In a OneShot phase, applies OneShot
166+
/// rules to each id and returns the accumulated resulting node ids
167+
/// in order. In a Repeating phase, errors (translation is not
168+
/// meaningful when input and output share a schema).
169+
///
170+
/// The single-`Id` case works too, because `Id: IntoIterator<Item
171+
/// = Id>` is a singleton iterator — so `ctx.translate(some_id)?`
172+
/// returns a `Vec<Id>` containing whatever `some_id` translated to.
169173
///
170174
/// Errors if this `BuildCtx` was constructed by hand (without a
171175
/// translator handle) — for example, in unit tests that don't go
172176
/// through the rule driver.
173-
pub fn translate<I: Into<Id>>(&mut self, id: I) -> Result<Vec<Id>, String> {
174-
let id = id.into();
175-
match &self.translator {
176-
Some(t) => t.translate(self.ast, self.user_ctx, id),
177-
None => Err("translate() called on a BuildCtx without a translator handle".into()),
177+
pub fn translate<I: Into<Id>>(
178+
&mut self,
179+
ids: impl IntoIterator<Item = I>,
180+
) -> Result<Vec<Id>, String> {
181+
let translator = self
182+
.translator
183+
.as_ref()
184+
.ok_or("translate() called on a BuildCtx without a translator handle")?;
185+
let mut out = Vec::new();
186+
for id in ids {
187+
let translated = translator.translate(self.ast, self.user_ctx, id.into())?;
188+
out.extend(translated);
178189
}
190+
Ok(out)
179191
}
180192

181-
/// Translate every node in an iterator with a **fresh** user context
182-
/// (reset to `C::default()`), restoring the previous context afterwards.
193+
/// Run `f` with a temporary child [`BuildCtx`] whose `user_ctx` is
194+
/// a fresh clone of the current one, sharing everything else
195+
/// (`ast`, `captures`, `fresh`, `source_range`, `translator`) by
196+
/// re-borrow. Any mutations `f` makes to the child's `user_ctx`
197+
/// are discarded when it returns — no restore needed, because the
198+
/// mutations only ever happened on a local clone.
183199
///
184-
/// Use when descending into a subtree — a body, expression, or statement
185-
/// list — that must not inherit any of the surrounding translation
186-
/// context (for example an enclosing binding modifier). Accepts optional
187-
/// (`Option<Id>`) and repeated (`Vec<Id>`) captures (both `IntoIterator`);
188-
/// for a single `Id`, wrap it in `std::iter::once(id)`.
189-
pub fn translate_reset<I: Into<Id>>(
190-
&mut self,
191-
ids: impl IntoIterator<Item = I>,
192-
) -> Result<Vec<Id>, String>
200+
/// Use for the rare rule that needs to translate a subtree under a
201+
/// modified context *and then continue using its own (unmodified)
202+
/// context afterwards*. For rules where the modified translation
203+
/// is the last use of `ctx`, mutate `ctx` in place — the framework
204+
/// invokes each rule with a private clone of the user context, so
205+
/// mutations are discarded on rule exit anyway.
206+
///
207+
/// Example: an outer rule that translates one child subtree with a
208+
/// reset context, then continues with the outer context intact:
209+
///
210+
/// ```ignore
211+
/// let val = ctx.scoped(|ctx| {
212+
/// ctx.reset();
213+
/// ctx.translate(val)
214+
/// })?;
215+
/// // `ctx` here is untouched by the reset inside the closure.
216+
/// let other = ctx.translate(other_id)?;
217+
/// ```
218+
pub fn scoped<F, R>(&mut self, f: F) -> R
193219
where
194-
C: Default,
220+
F: for<'b> FnOnce(&mut BuildCtx<'b, C>) -> R,
195221
{
196-
let saved = std::mem::take(&mut *self.user_ctx);
197-
let mut out = Vec::new();
198-
let mut result = Ok(());
199-
for id in ids {
200-
match self.translate(id) {
201-
Ok(v) => out.extend(v),
202-
Err(e) => {
203-
result = Err(e);
204-
break;
205-
}
206-
}
207-
}
208-
*self.user_ctx = saved;
209-
result.map(|()| out)
222+
let mut child_user_ctx = self.user_ctx.clone();
223+
let mut child = BuildCtx {
224+
ast: &mut *self.ast,
225+
captures: self.captures,
226+
fresh: self.fresh,
227+
source_range: self.source_range,
228+
user_ctx: &mut child_user_ctx,
229+
translator: self.translator,
230+
};
231+
f(&mut child)
232+
// child_user_ctx dropped; the outer `self` is unaffected.
210233
}
211234
}
212235

shared/yeast/src/lib.rs

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ use query::QueryNode;
2626
/// without colliding with the impls for plain integers.
2727
///
2828
/// Use `id.0` (or `id.into()`) to obtain the raw arena index.
29+
///
30+
/// Implements [`IntoIterator`] as a singleton (`iter::once(self)`)
31+
/// so that a bare `Id` can be used interchangeably with `Option<Id>`
32+
/// / `Vec<Id>` in places that expect an iterable of ids (e.g.
33+
/// [`crate::build::BuildCtx::translate`] and the field-splice
34+
/// interpolation via [`IntoFieldIds`]).
2935
#[repr(transparent)]
3036
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Hash, Serialize)]
3137
pub struct Id(pub usize);
@@ -42,16 +48,25 @@ impl From<Id> for usize {
4248
}
4349
}
4450

51+
impl IntoIterator for Id {
52+
type Item = Id;
53+
type IntoIter = std::iter::Once<Id>;
54+
fn into_iter(self) -> Self::IntoIter {
55+
std::iter::once(self)
56+
}
57+
}
58+
4559
/// Field and Kind ids are provided by tree-sitter
4660
type FieldId = u16;
4761
type KindId = u16;
4862

4963
/// Trait for values that can be appended to a field's id list inside a
5064
/// `tree!`/`trees!`/`rule!` template (in `{expr}` placeholders).
5165
///
52-
/// `Id` pushes a single id; the blanket impl for
53-
/// `IntoIterator<Item: Into<Id>>` handles `Vec<Id>`, `Option<Id>`,
54-
/// arbitrary iterators yielding `Id`, etc.
66+
/// The blanket impl for `IntoIterator<Item: Into<Id>>` handles all
67+
/// current shapes: `Vec<Id>`, `Option<Id>`, arbitrary iterators
68+
/// yielding `Id`, and a bare `Id` itself (which is `IntoIterator`
69+
/// via a singleton).
5570
///
5671
/// This lets `{expr}` interpolate any of these shapes without a
5772
/// dedicated splice syntax — the macro emits the same trait-dispatched
@@ -60,12 +75,6 @@ pub trait IntoFieldIds {
6075
fn extend_into(self, out: &mut Vec<Id>);
6176
}
6277

63-
impl IntoFieldIds for Id {
64-
fn extend_into(self, out: &mut Vec<Id>) {
65-
out.push(self);
66-
}
67-
}
68-
6978
impl<I, T> IntoFieldIds for I
7079
where
7180
I: IntoIterator<Item = T>,
@@ -732,6 +741,16 @@ pub struct TranslatorHandle<'a, C> {
732741
inner: TranslatorImpl<'a, C>,
733742
}
734743

744+
// Manual `Copy` / `Clone` so `TranslatorHandle<'_, C>: Copy` holds
745+
// regardless of whether `C: Copy`. `TranslatorImpl` contains only
746+
// shared references, which are `Copy` unconditionally.
747+
impl<C> Copy for TranslatorHandle<'_, C> {}
748+
impl<C> Clone for TranslatorHandle<'_, C> {
749+
fn clone(&self) -> Self {
750+
*self
751+
}
752+
}
753+
735754
/// Internal phase-specific translation state. Kept private — callers
736755
/// interact with [`TranslatorHandle`] only.
737756
enum TranslatorImpl<'a, C> {
@@ -752,6 +771,16 @@ enum TranslatorImpl<'a, C> {
752771
Repeating,
753772
}
754773

774+
// Manual `Copy` / `Clone` so `TranslatorImpl<'_, C>: Copy` holds
775+
// regardless of whether `C: Copy`. All variants hold only shared
776+
// references and small `Copy` scalars.
777+
impl<C> Copy for TranslatorImpl<'_, C> {}
778+
impl<C> Clone for TranslatorImpl<'_, C> {
779+
fn clone(&self) -> Self {
780+
*self
781+
}
782+
}
783+
755784
impl<'a, C: Clone> TranslatorHandle<'a, C> {
756785
/// Recursively apply OneShot rules to `id` and return the resulting
757786
/// node ids. Errors in a Repeating phase (where translation is not
@@ -970,18 +999,21 @@ fn apply_repeating_rules_inner<C: Clone>(
970999
if Some(rule_ptr) == skip_rule {
9711000
continue;
9721001
}
973-
// Snapshot the user context before invoking the rule so that any
974-
// mutations the rule makes are visible during recursive translation
975-
// of its result, but not leaked to the parent's siblings.
976-
let snapshot = user_ctx.clone();
1002+
// Give each rule attempt a private clone of the user context.
1003+
// Any mutations the rule makes are visible to its transform and
1004+
// to the recursive translation of its result, but never leak
1005+
// back to the parent — the clone is simply dropped when we
1006+
// return. This is also `?`-safe: an error return drops `local`
1007+
// without touching the caller's `user_ctx`.
1008+
let mut local = user_ctx.clone();
9771009
// Repeating rules don't need a real translator: their captures
9781010
// aren't auto-translated (Repeating preserves the input schema),
9791011
// and `ctx.translate(id)` errors if invoked from a Repeating
9801012
// transform.
9811013
let translator = TranslatorHandle {
9821014
inner: TranslatorImpl::Repeating,
9831015
};
984-
let try_result = rule.try_rule(ast, id, fresh, user_ctx, translator)?;
1016+
let try_result = rule.try_rule(ast, id, fresh, &mut local, translator)?;
9851017
if let Some(result_node) = try_result {
9861018
// For non-repeated rules, suppress further application of *this*
9871019
// rule on the result root, so a rule whose output matches its own
@@ -993,19 +1025,17 @@ fn apply_repeating_rules_inner<C: Clone>(
9931025
results.extend(apply_repeating_rules_inner(
9941026
index,
9951027
ast,
996-
user_ctx,
1028+
&mut local,
9971029
node,
9981030
fresh,
9991031
rewrite_depth + 1,
10001032
next_skip,
10011033
)?);
10021034
}
1003-
*user_ctx = snapshot;
10041035
return Ok(results);
10051036
}
1006-
// Rule didn't match; restore any speculative changes (none expected
1007-
// since try_rule only mutates on match, but be defensive).
1008-
*user_ctx = snapshot;
1037+
// Rule didn't match; `local` is dropped as we loop to the next
1038+
// rule.
10091039
}
10101040

10111041
// Take the parent's fields by ownership: the recursion will rewrite
@@ -1087,11 +1117,13 @@ fn apply_one_shot_rules_inner<C: Clone>(
10871117

10881118
for rule in index.rules_for_kind(node_kind) {
10891119
if let Some(captures) = rule.try_match(ast, id)? {
1090-
// Snapshot the user context before invoking the rule so that any
1091-
// mutations the rule (or its transitively-translated captures)
1092-
// make are visible during this rule's transform, but not leaked
1093-
// to the parent's siblings.
1094-
let snapshot = user_ctx.clone();
1120+
// Give the rule a private clone of the user context. Any
1121+
// mutations the rule (or its transitively-translated
1122+
// captures) make are visible during this rule's transform,
1123+
// but never leak back — the clone is dropped when we
1124+
// return. `?`-safe: an error return drops `local` without
1125+
// touching the caller's `user_ctx`.
1126+
let mut local = user_ctx.clone();
10951127
// Build the translator handle the transform will use to
10961128
// recursively translate captures (or, for macro-generated
10971129
// rules, the auto-translate prefix uses it to translate every
@@ -1104,8 +1136,7 @@ fn apply_one_shot_rules_inner<C: Clone>(
11041136
matched_root: id,
11051137
},
11061138
};
1107-
let result = rule.run_transform(ast, captures, id, fresh, user_ctx, translator)?;
1108-
*user_ctx = snapshot;
1139+
let result = rule.run_transform(ast, captures, id, fresh, &mut local, translator)?;
11091140
return Ok(result);
11101141
}
11111142
}

0 commit comments

Comments
 (0)