Skip to content

Commit

Permalink
derive: fix lifetimes for sub-generators
Browse files Browse the repository at this point in the history
Remove inheritance from `MapChain`, and simply open a new scope.

Add lifetime to `Generator` and `Heritage` to reference its root
context.
  • Loading branch information
Kijewski committed Nov 23, 2024
1 parent 466b925 commit 5604db8
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 84 deletions.
156 changes: 88 additions & 68 deletions rinja_derive/src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use crate::{BUILT_IN_FILTERS, CRATE, CompileError, FileInfo, MsgValidEscapers};
pub(crate) fn template_to_string(
input: &TemplateInput<'_>,
contexts: &HashMap<&Arc<Path>, Context<'_>, FxBuildHasher>,
heritage: Option<&Heritage<'_>>,
heritage: Option<&Heritage<'_, '_>>,
) -> Result<String, CompileError> {
let mut buf = Buffer::new();
template_into_buffer(input, contexts, heritage, &mut buf, false)?;
Expand All @@ -35,7 +35,7 @@ pub(crate) fn template_to_string(
pub(crate) fn template_into_buffer(
input: &TemplateInput<'_>,
contexts: &HashMap<&Arc<Path>, Context<'_>, FxBuildHasher>,
heritage: Option<&Heritage<'_>>,
heritage: Option<&Heritage<'_, '_>>,
buf: &mut Buffer,
only_template: bool,
) -> Result<(), CompileError> {
Expand All @@ -57,13 +57,13 @@ pub(crate) fn template_into_buffer(
result
}

struct Generator<'a> {
struct Generator<'a, 'h> {
// The template input state: original struct AST and attributes
input: &'a TemplateInput<'a>,
// All contexts, keyed by the package-relative template path
contexts: &'a HashMap<&'a Arc<Path>, Context<'a>, FxBuildHasher>,
// The heritage contains references to blocks and their ancestry
heritage: Option<&'a Heritage<'a>>,
heritage: Option<&'h Heritage<'a, 'h>>,
// Variables accessible directly from the current scope (not redirected to context)
locals: MapChain<'a>,
// Suffix whitespace from the previous literal. Will be flushed to the
Expand All @@ -81,16 +81,16 @@ struct Generator<'a> {
is_in_filter_block: usize,
}

impl<'a> Generator<'a> {
fn new<'n>(
input: &'n TemplateInput<'_>,
contexts: &'n HashMap<&'n Arc<Path>, Context<'n>, FxBuildHasher>,
heritage: Option<&'n Heritage<'_>>,
locals: MapChain<'n>,
impl<'a, 'h> Generator<'a, 'h> {
fn new(
input: &'a TemplateInput<'a>,
contexts: &'a HashMap<&'a Arc<Path>, Context<'a>, FxBuildHasher>,
heritage: Option<&'h Heritage<'a, 'h>>,
locals: MapChain<'a>,
buf_writable_discard: bool,
is_in_filter_block: usize,
) -> Generator<'n> {
Generator {
) -> Self {
Self {
input,
contexts,
heritage,
Expand Down Expand Up @@ -131,12 +131,50 @@ impl<'a> Generator<'a> {
Ok(())
}

fn push_locals<T, F: FnOnce(&mut Self) -> Result<T, CompileError>>(
fn push_locals<T, F>(&mut self, callback: F) -> Result<T, CompileError>
where
F: FnOnce(&mut Self) -> Result<T, CompileError>,
{
self.locals.scopes.push(HashMap::default());
let res = callback(self);
self.locals.scopes.pop().unwrap();
res
}

fn with_child<'b, T, F>(
&mut self,
heritage: Option<&'b Heritage<'a, 'b>>,
callback: F,
) -> Result<T, CompileError> {
) -> Result<T, CompileError>
where
F: FnOnce(
Generator<'a, 'b>,
) -> Result<(Generator<'a, 'b>, T), (Generator<'a, 'b>, CompileError)>,
{
self.locals.scopes.push(HashMap::default());
let res = callback(self);

let buf_writable = mem::take(&mut self.buf_writable);
let locals = mem::replace(&mut self.locals, MapChain::new_empty());

let mut child = Generator::new(
self.input,
self.contexts,
heritage,
locals,
self.buf_writable.discard,
self.is_in_filter_block,
);
child.buf_writable = buf_writable;
let (child, res) = match callback(child) {
Ok((child, value)) => (child, Ok(value)),
Err((child, err)) => (child, Err(err)),
};
Generator {
locals: self.locals,
buf_writable: self.buf_writable,
..
} = child;

self.locals.scopes.pop().unwrap();
res
}
Expand Down Expand Up @@ -965,17 +1003,20 @@ impl<'a> Generator<'a> {
Some(heritage) => heritage.root,
None => child_ctx,
};
let locals = MapChain::with_parent(&self.locals);
let mut child = Self::new(
self.input,
self.contexts,
heritage.as_ref(),
locals,
self.buf_writable.discard,
self.is_in_filter_block,
);
let mut size_hint = child.handle(handle_ctx, handle_ctx.nodes, buf, AstLevel::Top)?;
size_hint += child.write_buf_writable(handle_ctx, buf)?;

let size_hint = self.with_child(heritage.as_ref(), |mut child| {
let mut size_hint = 0;
match child.handle(handle_ctx, handle_ctx.nodes, buf, AstLevel::Top) {
Ok(h) => size_hint += h,
Err(err) => return Err((child, err)),
}
match child.write_buf_writable(handle_ctx, buf) {
Ok(h) => size_hint += h,
Err(err) => return Err((child, err)),
}
Ok((child, size_hint))
})?;

self.prepare_ws(i.ws);

Ok(size_hint)
Expand Down Expand Up @@ -1151,39 +1192,26 @@ impl<'a> Generator<'a> {
.or_insert_with(|| import.clone());
}

let buf_writable = mem::take(&mut self.buf_writable);
let mut child = Self::new(
self.input,
self.contexts,
Some(heritage),
// FIXME: Fix the broken lifetimes.
//
// `transmute` is here to fix the lifetime nightmare around `&self.locals` not
// living long enough. The correct solution would be to make `MapChain` take two
// lifetimes `&'a MapChain<'a, 'b, K, V>`, making the `parent` field take
// `<'b, 'b, ...>`. Except... it doesn't work because `self` still doesn't live long
// enough here for some reason...
MapChain::with_parent(unsafe {
mem::transmute::<&MapChain<'_>, &MapChain<'_>>(&self.locals)
}),
self.buf_writable.discard,
self.is_in_filter_block,
);
child.buf_writable = buf_writable;

// Handle inner whitespace suppression spec and process block nodes
child.prepare_ws(def.ws1);
let size_hint = self.with_child(Some(heritage), |mut child| {
// Handle inner whitespace suppression spec and process block nodes
child.prepare_ws(def.ws1);

child.super_block = Some(cur);
let size_hint = child.handle(&child_ctx, &def.nodes, buf, AstLevel::Block)?;
child.super_block = Some(cur);
let size_hint = match child.handle(&child_ctx, &def.nodes, buf, AstLevel::Block) {
Ok(size_hint) => size_hint,
Err(err) => return Err((child, err)),
};

if !child.locals.is_current_empty() {
// Need to flush the buffer before popping the variable stack
child.write_buf_writable(ctx, buf)?;
}
if !child.locals.is_current_empty() {
// Need to flush the buffer before popping the variable stack
if let Err(err) = child.write_buf_writable(ctx, buf) {
return Err((child, err));
}
}

child.flush_ws(def.ws2);
self.buf_writable = child.buf_writable;
child.flush_ws(def.ws2);
Ok((child, size_hint))
})?;

// Restore original block context and set whitespace suppression for
// succeeding whitespace according to the outer WS spec
Expand Down Expand Up @@ -2639,7 +2667,7 @@ enum EvaluatedResult {
}

impl<'a> Conds<'a> {
fn compute_branches(generator: &Generator<'a>, i: &'a If<'a>) -> Self {
fn compute_branches(generator: &Generator<'a, '_>, i: &'a If<'a>) -> Self {
let mut conds = Vec::with_capacity(i.branches.len());
let mut ws_before = None;
let mut ws_after = None;
Expand Down Expand Up @@ -2763,25 +2791,18 @@ impl LocalMeta {
}

struct MapChain<'a> {
parent: Option<&'a MapChain<'a>>,
scopes: Vec<HashMap<Cow<'a, str>, LocalMeta, FxBuildHasher>>,
}

impl<'a> MapChain<'a> {
fn with_parent(parent: &'a MapChain<'_>) -> MapChain<'a> {
MapChain {
parent: Some(parent),
scopes: vec![HashMap::default()],
}
fn new_empty() -> Self {
Self { scopes: vec![] }
}

/// Iterates the scopes in reverse and returns `Some(LocalMeta)`
/// from the first scope where `key` exists.
fn get<'b>(&'b self, key: &str) -> Option<&'b LocalMeta> {
let mut scopes = self.scopes.iter().rev();
scopes
.find_map(|set| set.get(key))
.or_else(|| self.parent.and_then(|set| set.get(key)))
self.scopes.iter().rev().find_map(|set| set.get(key))
}

fn is_current_empty(&self) -> bool {
Expand Down Expand Up @@ -2819,7 +2840,6 @@ impl<'a> MapChain<'a> {
impl Default for MapChain<'_> {
fn default() -> Self {
Self {
parent: None,
scopes: vec![HashMap::default()],
}
}
Expand Down
33 changes: 17 additions & 16 deletions rinja_derive/src/heritage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,35 @@ use rustc_hash::FxBuildHasher;
use crate::config::Config;
use crate::{CompileError, FileInfo};

pub(crate) struct Heritage<'a> {
pub(crate) root: &'a Context<'a>,
pub(crate) blocks: BlockAncestry<'a>,
pub(crate) struct Heritage<'a, 'h> {
pub(crate) root: &'h Context<'a>,
pub(crate) blocks: BlockAncestry<'a, 'h>,
}

impl Heritage<'_> {
pub(crate) fn new<'n>(
mut ctx: &'n Context<'n>,
contexts: &'n HashMap<&'n Arc<Path>, Context<'n>, FxBuildHasher>,
) -> Heritage<'n> {
let mut blocks: BlockAncestry<'n> = ctx
impl<'a, 'h> Heritage<'a, 'h> {
pub(crate) fn new(
mut root: &'h Context<'a>,
contexts: &'a HashMap<&'a Arc<Path>, Context<'a>, FxBuildHasher>,
) -> Self {
let mut blocks: BlockAncestry<'a, 'h> = root
.blocks
.iter()
.map(|(name, def)| (*name, vec![(ctx, *def)]))
.map(|(name, def)| (*name, vec![(root, *def)]))
.collect();

while let Some(path) = &ctx.extends {
ctx = &contexts[path];
for (name, def) in &ctx.blocks {
blocks.entry(name).or_default().push((ctx, def));
while let Some(path) = &root.extends {
root = &contexts[path];
for (name, def) in &root.blocks {
blocks.entry(name).or_default().push((root, def));
}
}

Heritage { root: ctx, blocks }
Self { root, blocks }
}
}

type BlockAncestry<'a> = HashMap<&'a str, Vec<(&'a Context<'a>, &'a BlockDef<'a>)>, FxBuildHasher>;
type BlockAncestry<'a, 'h> =
HashMap<&'a str, Vec<(&'h Context<'a>, &'a BlockDef<'a>)>, FxBuildHasher>;

#[derive(Clone)]
pub(crate) struct Context<'a> {
Expand Down

0 comments on commit 5604db8

Please sign in to comment.