Skip to main content

typst_eval/
rules.rs

1use typst_library::diag::{At, SourceResult, warning};
2use typst_library::foundations::{
3    Element, Func, Recipe, Selector, ShowableSelector, StyleChain, Styles, Transformation,
4};
5use typst_library::layout::{BlockElem, PageElem};
6use typst_library::model::ParElem;
7use typst_syntax::ast::{self, AstNode};
8
9use crate::{Eval, Vm, hint_if_shadowed_std};
10
11impl Eval for ast::SetRule<'_> {
12    type Output = Styles;
13
14    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
15        if let Some(condition) = self.condition()
16            && !condition.eval(vm)?.cast::<bool>().at(condition.span())?
17        {
18            return Ok(Styles::new());
19        }
20
21        let target_expr = self.target();
22        let target = target_expr
23            .eval(vm)?
24            .cast::<Func>()
25            .map_err(|err| hint_if_shadowed_std(vm, &target_expr, err))
26            .and_then(|func| {
27                func.to_element().ok_or_else(|| {
28                    "only element functions can be used in set rules".into()
29                })
30            })
31            .at(target_expr.span())?;
32        let args = self.args().eval(vm)?.spanned(self.span());
33        Ok(target.set(&mut vm.engine, args)?.spanned(self.span()).liftable())
34    }
35}
36
37impl Eval for ast::ShowRule<'_> {
38    type Output = Recipe;
39
40    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
41        let selector = self
42            .selector()
43            .map(|sel| {
44                sel.eval(vm)?
45                    .cast::<ShowableSelector>()
46                    .map_err(|err| hint_if_shadowed_std(vm, &sel, err))
47                    .at(sel.span())
48            })
49            .transpose()?
50            .map(|selector| selector.0);
51
52        let transform = self.transform();
53        let transform = match transform {
54            ast::Expr::SetRule(set) => Transformation::Style(set.eval(vm)?),
55            expr => expr.eval(vm)?.cast::<Transformation>().at(transform.span())?,
56        };
57
58        let recipe = Recipe::new(selector, transform, self.span());
59        check_show_page_rule(vm, &recipe);
60        check_show_par_set_block(vm, &recipe);
61
62        Ok(recipe)
63    }
64}
65
66/// Warns that `show page` rules currently have no effect.
67fn check_show_page_rule(vm: &mut Vm, recipe: &Recipe) {
68    if let Some(Selector::Elem(elem, _)) = recipe.selector()
69        && *elem == Element::of::<PageElem>()
70    {
71        vm.engine.sink.warn(warning!(
72            recipe.span(),
73            "`show page` is not supported and has no effect";
74            hint: "customize pages with `set page(..)` instead";
75        ));
76    }
77}
78
79/// Migration hint for `show par: set block(spacing: ..)`.
80fn check_show_par_set_block(vm: &mut Vm, recipe: &Recipe) {
81    if let Some(Selector::Elem(elem, _)) = recipe.selector()
82        && *elem == Element::of::<ParElem>()
83        && let Transformation::Style(styles) = recipe.transform()
84        && let styles = StyleChain::new(styles)
85        && (styles.has(BlockElem::above) || styles.has(BlockElem::below))
86    {
87        vm.engine.sink.warn(warning!(
88            recipe.span(),
89            "`show par: set block(spacing: ..)` has no effect anymore";
90            hint: "write `set par(spacing: ..)` instead";
91            hint: "this is specific to paragraphs as they are not considered blocks \
92                   anymore";
93        ))
94    }
95}