-
Notifications
You must be signed in to change notification settings - Fork 2k
yeast: Add context scoping mechanism #22136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -45,12 +45,29 @@ impl SwiftContext { | |||
| /// | ||||
| /// True exactly when an enclosing binding has published its modifier into | ||||
| /// `outer_modifiers`. This is reliable because non-binding subtrees | ||||
| /// (bodies, initializer values, ...) are translated with a reset context | ||||
| /// (see `BuildCtx::translate_reset`), so a bare identifier only sees a | ||||
| /// (bodies, initializer values, ...) are translated after resetting the | ||||
| /// context (see `reset`), so a bare identifier only sees a | ||||
| /// non-empty `outer_modifiers` when it really is a binding. | ||||
| fn in_binding_pattern(&self) -> bool { | ||||
| !self.outer_modifiers.is_empty() | ||||
| } | ||||
|
|
||||
| /// Clear the context fields that must not propagate into an | ||||
| /// expression / statement / body subtree. | ||||
| /// | ||||
| /// Mirrors `Default::default()` for `SwiftContext` today, but is a | ||||
| /// named method so future context fields can opt in or out of | ||||
| /// clearing here per-field. | ||||
| /// | ||||
| /// Called before recursively translating a body / initializer | ||||
| /// slot. Most rules mutate `ctx` in place — the framework's | ||||
| /// rule-boundary snapshot/restore cleans up on exit. Rules that | ||||
| /// need the outer context intact *after* the reset-and-translate | ||||
| /// (see e.g. the `property_binding` willSet/didSet rule) wrap the | ||||
| /// mutation in `ctx.scoped(...)` instead. | ||||
| fn reset(&mut self) { | ||||
| *self = SwiftContext::default(); | ||||
| } | ||||
| } | ||||
|
|
||||
| /// Build a freshly-created `chained_declaration` modifier node if | ||||
|
|
@@ -239,7 +256,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> { | |||
| name: (identifier #{name}) | ||||
| type: {ty} | ||||
| accessor_kind: (accessor_kind "get") | ||||
| body: (block stmt: {ctx.translate_reset(body)?})) | ||||
| body: (block stmt: {ctx.reset(); ctx.translate(body)?})) | ||||
| ), | ||||
| // Stored property with willSet/didSet observers (initializer | ||||
| // optional) → a `variable_declaration` followed by one | ||||
|
|
@@ -260,12 +277,20 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> { | |||
| observers: (willset_didset_block willset: _? @@ws didset: _? @@ds)) | ||||
| => | ||||
| {{ | ||||
| // The initializer value must not inherit the binding context | ||||
| // (it may contain patterns, e.g. a switch expression), so | ||||
| // translate it with a reset context. The observers keep the | ||||
| // context: each willSet/didSet accessor emits the binding | ||||
| // modifier and resets its own body. | ||||
| let val = ctx.translate_reset(val)?; | ||||
| // The initializer value must not inherit the binding | ||||
| // context (it may contain patterns, e.g. a switch | ||||
| // expression), so translate it inside a `ctx.scoped` | ||||
| // block — the block receives a temporary `ctx` whose | ||||
| // `user_ctx` is a clone; mutations to it are discarded | ||||
| // when the block returns, so the outer `ctx` is intact | ||||
| // for the observer loop below. The observers keep the | ||||
| // outer context: each willSet/didSet accessor emits | ||||
| // the binding modifier and, in turn, resets the | ||||
| // context for its own body. | ||||
| let val = ctx.scoped(|ctx| { | ||||
| ctx.reset(); | ||||
| ctx.translate(val) | ||||
| })?; | ||||
|
Comment on lines
+291
to
+294
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This begs the question: why do we need
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The difference here is that we want to use Thus, we have to introduce a temporary context -- or at least it's the easiest way to get the desired behaviour. (I guess technically we could do the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let me rephrase, because that's not really helping me. I assume that a context here roughly corresponds to some type of scope in Swift. If so, from the perspective of the Swift AST, what is different here than in the other cases where we had
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The context is a mechanism for passing information from a node down to its descendants. For instance, In some cases we have an outer type that we want to propagate down into some descendant. In this case, we set the appropriate part of the
In the previous code, we had two different kinds of This had the effect of translating But in this case, there's no reason to restore the context -- it gets dropped anyway afterwards, which is why we can just rewrite it to use The difficult case is the following (eliding the unimportant bits): // previously: val = translate_reset(val)?;
let val = ctx.scoped(|ctx| {
ctx.reset();
ctx.translate(val)
})?;
let var_decl = tree!(
(variable_declaration
modifier: {ctx.outer_modifiers.clone()}
modifier: {chained_modifier(&mut ctx)}
pattern: (name_pattern identifier: (identifier #{name}))
type: {ty}
value: {val})
);
// Publish the property name for the observer rules.
ctx.property_name = Some(tree!((identifier #{name})));
// Observers are subsequent outputs of this flattening
// rule, so they always get `chained_declaration`.
ctx.is_chained = true;
let mut result = vec![var_decl];
for obs in ws.into_iter().chain(ds) {
result.extend(ctx.translate(obs)?);
}
resultThe behaviour is the same as before -- we just express it slightly differently. The important thing is that when we do
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is still too abstract. What Swift construct are we trying to transform/translate here?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's translating the I don't see why this is relevant to the present PR, however. The behaviour of the rule is unchanged -- all I've done is change the Perhaps we should have a meeting where we go through what the rules are doing? (Though I should point out that I cannot speak to the motivation for representing things the way they are currently. For that you'll have to talk to asgerf.)
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I would like to understand whether this change is correct, but I find this very hard based only only the code and the abstract descriptions you have given.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, let's break it down. The change uses pub fn scoped<F, R>(&mut self, f: F) -> R
where
F: for<'b> FnOnce(&mut BuildCtx<'b, C>) -> R,
{
let mut child_user_ctx = self.user_ctx.clone();
let mut child = BuildCtx {
ast: &mut *self.ast,
captures: self.captures,
fresh: self.fresh,
source_range: self.source_range,
user_ctx: &mut child_user_ctx,
translator: self.translator,
};
f(&mut child)
// child_user_ctx dropped; the outer `self` is unaffected.
}We know what the function pub fn scoped<F, R>(&mut self, f: F) -> R
where
F: for<'b> FnOnce(&mut BuildCtx<'b, C>) -> R,
{
let mut child_user_ctx = self.user_ctx.clone();
let mut child = BuildCtx {
ast: &mut *self.ast,
captures: self.captures,
fresh: self.fresh,
source_range: self.source_range,
user_ctx: &mut child_user_ctx,
translator: self.translator,
};
child.reset();
child.translate(val)
// child_user_ctx dropped; the outer `self` is unaffected.
}Now, pub fn scoped<F, R>(&mut self, f: F) -> R
where
F: for<'b> FnOnce(&mut BuildCtx<'b, C>) -> R,
{
let mut child_user_ctx = self.user_ctx.clone();
let mut child = BuildCtx {
ast: &mut *self.ast,
captures: self.captures,
fresh: self.fresh,
source_range: self.source_range,
user_ctx: &mut BuildCtx::default(), // <-- empty context
translator: self.translator,
};
child.translate(val)
// child_user_ctx dropped; the outer `self` is unaffected.
}In other words, we call Finally, pub fn translate<I: Into<Id>>(
&mut self,
ids: impl IntoIterator<Item = I>,
) -> Result<Vec<Id>, String> {
let translator = self
.translator
.as_ref()
.ok_or("translate() called on a BuildCtx without a translator handle")?;
let mut out = Vec::new();
for id in ids {
let translated = translator.translate(self.ast, self.user_ctx, id.into())?;
out.extend(translated);
}
Ok(out)
}Now let's consider pub fn translate_reset<I: Into<Id>>(
&mut self,
ids: impl IntoIterator<Item = I>,
) -> Result<Vec<Id>, String>
where
C: Default,
{
let saved = std::mem::take(&mut *self.user_ctx);
let mut out = Vec::new();
let mut result = Ok(());
for id in ids {
match self.translate(id) {
Ok(v) => out.extend(v),
Err(e) => {
result = Err(e);
break;
}
}
}
*self.user_ctx = saved;
result.map(|()| out)
}Here, we first save (Now, you may wonder: can't For the cases where Does this help? If not, we should pair on doing a deeper dive into the code, since I'm not sure I can explain it more clearly through the present medium.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks. This helps, but in an unexpected way: it confirms what I understood from your code changes, so that's nice ❤️. I'm convinced at this point that the behavior is unchanged. |
||||
|
|
||||
| let var_decl = tree!( | ||||
| (variable_declaration | ||||
|
|
@@ -295,7 +320,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> { | |||
| // The enclosing `property_declaration` leads `ctx.outer_modifiers` | ||||
| // with the `let`/`var` binding modifier, so the auto-translated name | ||||
| // pattern (the LHS) becomes a binding, while the initializer value is | ||||
| // translated with a reset context (see `translate_reset`). | ||||
| // translated with a reset context (see `SwiftContext::reset`). | ||||
| rule!( | ||||
| (property_binding | ||||
| name: @pattern | ||||
|
|
@@ -307,7 +332,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> { | |||
| modifier: {chained_modifier(&mut ctx)} | ||||
| pattern: {pattern} | ||||
| type: {ty} | ||||
| value: {ctx.translate_reset(val)?}) // reset context: the initializer must not see the binding | ||||
| value: {ctx.reset(); ctx.translate(val)?}) | ||||
| ), | ||||
| // property_declaration: flatten declarators (each may translate | ||||
| // to multiple nodes — variable_declaration and/or | ||||
|
|
@@ -1118,7 +1143,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> { | |||
| name: {ctx.property_name.ok_or("computed_getter outside property_binding context")?} | ||||
| type: {ctx.property_type} | ||||
| accessor_kind: (accessor_kind "get") | ||||
| body: (block stmt: {ctx.translate_reset(body)?})) | ||||
| body: (block stmt: {ctx.reset(); ctx.translate(body)?})) | ||||
| ), | ||||
| // Computed setter with explicit parameter name. | ||||
| rule!( | ||||
|
|
@@ -1131,7 +1156,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> { | |||
| type: {ctx.property_type} | ||||
| accessor_kind: (accessor_kind "set") | ||||
| parameter: (parameter pattern: (name_pattern identifier: (identifier #{param}))) | ||||
| body: (block stmt: {ctx.translate_reset(body)?})) | ||||
| body: (block stmt: {ctx.reset(); ctx.translate(body)?})) | ||||
| ), | ||||
| // Computed setter without explicit parameter name; body optional. | ||||
| rule!( | ||||
|
|
@@ -1143,7 +1168,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> { | |||
| name: {ctx.property_name.ok_or("computed_setter outside property_binding context")?} | ||||
| type: {ctx.property_type} | ||||
| accessor_kind: (accessor_kind "set") | ||||
| body: (block stmt: {ctx.translate_reset(body)?})) | ||||
| body: (block stmt: {ctx.reset(); ctx.translate(body)?})) | ||||
| ), | ||||
| // Computed modify → accessor_declaration | ||||
| rule!( | ||||
|
|
@@ -1155,7 +1180,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> { | |||
| name: {ctx.property_name.ok_or("computed_modify outside property_binding context")?} | ||||
| type: {ctx.property_type} | ||||
| accessor_kind: (accessor_kind "modify") | ||||
| body: (block stmt: {ctx.translate_reset(body)?})) | ||||
| body: (block stmt: {ctx.reset(); ctx.translate(body)?})) | ||||
| ), | ||||
| // willset/didset block — spread to children (only reachable as a | ||||
| // fallback; the outer property_binding manual rule normally | ||||
|
|
@@ -1173,7 +1198,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> { | |||
| modifier: {chained_modifier(&mut ctx)} | ||||
| name: {ctx.property_name.ok_or("willset_clause outside property_binding context")?} | ||||
| accessor_kind: (accessor_kind "willSet") | ||||
| body: (block stmt: {ctx.translate_reset(body)?})) | ||||
| body: (block stmt: {ctx.reset(); ctx.translate(body)?})) | ||||
| ), | ||||
| // didset clause → accessor_declaration (body optional). | ||||
| rule!( | ||||
|
|
@@ -1184,7 +1209,7 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> { | |||
| modifier: {chained_modifier(&mut ctx)} | ||||
| name: {ctx.property_name.ok_or("didset_clause outside property_binding context")?} | ||||
| accessor_kind: (accessor_kind "didSet") | ||||
| body: (block stmt: {ctx.translate_reset(body)?})) | ||||
| body: (block stmt: {ctx.reset(); ctx.translate(body)?})) | ||||
| ), | ||||
| // Preprocessor conditionals — unsupported | ||||
| rule!((diagnostic) => (unsupported_node)), | ||||
|
|
||||
Uh oh!
There was an error while loading. Please reload this page.