diff --git a/library/src/compute/foundations.rs b/library/src/compute/foundations.rs index 4e8b9d82..162ba8c4 100644 --- a/library/src/compute/foundations.rs +++ b/library/src/compute/foundations.rs @@ -113,5 +113,5 @@ pub fn eval(vm: &Vm, args: &mut Args) -> SourceResult { let source = Source::synthesized(text, span); let route = model::Route::default(); let module = model::eval(vm.world(), route.track(), &source)?; - Ok(Value::Content(module.content)) + Ok(Value::Content(module.content())) } diff --git a/src/ide/highlight.rs b/src/ide/highlight.rs index 1f52ae95..321bf9a6 100644 --- a/src/ide/highlight.rs +++ b/src/ide/highlight.rs @@ -156,7 +156,7 @@ pub fn highlight(node: &LinkedNode) -> Option { SyntaxKind::Return => Some(Category::Keyword), SyntaxKind::Import => Some(Category::Keyword), SyntaxKind::Include => Some(Category::Keyword), - SyntaxKind::From => Some(Category::Keyword), + SyntaxKind::As => Some(Category::Keyword), SyntaxKind::Markup { .. } if node.parent_kind() == Some(&SyntaxKind::TermItem) @@ -198,6 +198,17 @@ pub fn highlight(node: &LinkedNode) -> Option { | SyntaxKind::Frac, ) => Some(Category::Interpolated), Some(SyntaxKind::FuncCall) => Some(Category::Function), + Some(SyntaxKind::FieldAccess) + if node + .parent() + .and_then(|p| p.parent()) + .filter(|gp| gp.kind() == &SyntaxKind::Parenthesized) + .and_then(|gp| gp.parent()) + .map_or(false, |ggp| ggp.kind() == &SyntaxKind::FuncCall) + && node.next_sibling().is_none() => + { + Some(Category::Function) + } Some(SyntaxKind::MethodCall) if node.prev_sibling().is_some() => { Some(Category::Function) } diff --git a/src/lib.rs b/src/lib.rs index 4045c02d..e6410d84 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,7 +66,7 @@ pub fn compile(world: &(dyn World + 'static), source: &Source) -> SourceResult dict.at(&field).at(span)?.clone(), Value::Content(content) => content .field(&field) - .ok_or_else(|| format!("unknown field {field:?}")) + .ok_or_else(|| format!("unknown field `{field}`")) + .at(span)?, + Value::Module(module) => module + .scope() + .get(&field) + .cloned() + .ok_or_else(|| { + format!("module `{}` does not contain `{field}`", module.name()) + }) .at(span)?, v => bail!( self.target().span(), @@ -1163,19 +1162,22 @@ impl Eval for ast::ModuleImport { type Output = Value; fn eval(&self, vm: &mut Vm) -> SourceResult { - let span = self.path().span(); - let path = self.path().eval(vm)?.cast::().at(span)?; - let module = import(vm, &path, span)?; + let span = self.source().span(); + let source = self.source().eval(vm)?; + let module = import(vm, source, span)?; match self.imports() { - ast::Imports::Wildcard => { - for (var, value) in module.scope.iter() { + None => { + vm.scopes.top.define(module.name().clone(), module); + } + Some(ast::Imports::Wildcard) => { + for (var, value) in module.scope().iter() { vm.scopes.top.define(var.clone(), value.clone()); } } - ast::Imports::Items(idents) => { + Some(ast::Imports::Items(idents)) => { for ident in idents { - if let Some(value) = module.scope.get(&ident) { + if let Some(value) = module.scope().get(&ident) { vm.scopes.top.define(ident.take(), value.clone()); } else { bail!(ident.span(), "unresolved import"); @@ -1192,17 +1194,23 @@ impl Eval for ast::ModuleInclude { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult { - let span = self.path().span(); - let path = self.path().eval(vm)?.cast::().at(span)?; - let module = import(vm, &path, span)?; - Ok(module.content) + let span = self.source().span(); + let source = self.source().eval(vm)?; + let module = import(vm, source, span)?; + Ok(module.content()) } } /// Process an import of a module relative to the current location. -fn import(vm: &Vm, path: &str, span: Span) -> SourceResult { +fn import(vm: &Vm, source: Value, span: Span) -> SourceResult { + let path = match source { + Value::Str(path) => path, + Value::Module(module) => return Ok(module), + v => bail!(span, "expected path or module, found {}", v.type_name()), + }; + // Load the source file. - let full = vm.locate(path).at(span)?; + let full = vm.locate(&path).at(span)?; let id = vm.world.resolve(&full).at(span)?; // Prevent cyclic importing. diff --git a/src/model/func.rs b/src/model/func.rs index 878b717f..98dc527c 100644 --- a/src/model/func.rs +++ b/src/model/func.rs @@ -443,8 +443,8 @@ impl<'a> CapturesVisitor<'a> { // An import contains items, but these are active only after the // path is evaluated. Some(ast::Expr::Import(expr)) => { - self.visit(expr.path().as_untyped()); - if let ast::Imports::Items(items) = expr.imports() { + self.visit(expr.source().as_untyped()); + if let Some(ast::Imports::Items(items)) = expr.imports() { for item in items { self.bind(item); } @@ -525,8 +525,8 @@ mod tests { test("#for x in y {} #x", &["x", "y"]); // Import. - test("#import x, y from z", &["z"]); - test("#import x, y, z from x + y", &["x", "y"]); + test("#import z: x, y", &["z"]); + test("#import x + y: x, y, z", &["x", "y"]); // Blocks. test("{ let x = 1; { let y = 2; y }; x + y }", &["y"]); diff --git a/src/model/mod.rs b/src/model/mod.rs index 6ba8014c..d84fe464 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -19,6 +19,7 @@ mod content; mod eval; mod func; mod methods; +mod module; mod ops; mod realize; mod scope; @@ -36,6 +37,7 @@ pub use self::dict::*; pub use self::eval::*; pub use self::func::*; pub use self::library::*; +pub use self::module::*; pub use self::realize::*; pub use self::scope::*; pub use self::str::*; diff --git a/src/model/module.rs b/src/model/module.rs new file mode 100644 index 00000000..c7cb7faf --- /dev/null +++ b/src/model/module.rs @@ -0,0 +1,62 @@ +use std::fmt::{self, Debug, Formatter}; +use std::path::Path; +use std::sync::Arc; + +use super::{Content, Scope}; +use crate::util::EcoString; + +/// An evaluated module, ready for importing or typesetting. +#[derive(Clone, Hash)] +pub struct Module(Arc); + +/// The internal representation. +#[derive(Clone, Hash)] +struct Repr { + /// The module's name. + name: EcoString, + /// The top-level definitions that were bound in this module. + scope: Scope, + /// The module's layoutable contents. + content: Content, +} + +impl Module { + /// Create a new, empty module with the given `name`. + pub fn new(name: impl Into) -> Self { + Self(Arc::new(Repr { + name: name.into(), + scope: Scope::new(), + content: Content::empty(), + })) + } + + /// Create a new module from an evalauted file. + pub fn evaluated(path: &Path, scope: Scope, content: Content) -> Self { + let name = path.file_stem().unwrap_or_default().to_string_lossy().into(); + Self(Arc::new(Repr { name, scope, content })) + } + + /// Get the module's name. + pub fn name(&self) -> &EcoString { + &self.0.name + } + + /// Access the module's scope. + pub fn scope(&self) -> &Scope { + &self.0.scope + } + + /// Extract the module's content. + pub fn content(self) -> Content { + match Arc::try_unwrap(self.0) { + Ok(repr) => repr.content, + Err(arc) => arc.content.clone(), + } + } +} + +impl Debug for Module { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "", self.name()) + } +} diff --git a/src/model/value.rs b/src/model/value.rs index 0716985d..8103b211 100644 --- a/src/model/value.rs +++ b/src/model/value.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use siphasher::sip128::{Hasher128, SipHasher}; use super::{ - format_str, ops, Args, Array, Cast, CastInfo, Content, Dict, Func, Label, Str, + format_str, ops, Args, Array, Cast, CastInfo, Content, Dict, Func, Label, Module, Str, }; use crate::diag::StrResult; use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel, RgbaColor}; @@ -52,6 +52,8 @@ pub enum Value { Func(Func), /// Captured arguments to a function. Args(Args), + /// A module. + Module(Module), /// A dynamic value. Dyn(Dynamic), } @@ -86,6 +88,7 @@ impl Value { Self::Dict(_) => Dict::TYPE_NAME, Self::Func(_) => Func::TYPE_NAME, Self::Args(_) => Args::TYPE_NAME, + Self::Module(_) => Module::TYPE_NAME, Self::Dyn(v) => v.type_name(), } } @@ -109,6 +112,7 @@ impl Value { Self::Str(v) => item!(text)(v.into()), Self::Content(v) => v, Self::Func(_) => Content::empty(), + Self::Module(module) => module.content(), _ => item!(raw)(self.repr().into(), Some("typc".into()), false), } } @@ -150,6 +154,7 @@ impl Debug for Value { Self::Dict(v) => Debug::fmt(v, f), Self::Func(v) => Debug::fmt(v, f), Self::Args(v) => Debug::fmt(v, f), + Self::Module(v) => Debug::fmt(v, f), Self::Dyn(v) => Debug::fmt(v, f), } } @@ -189,6 +194,7 @@ impl Hash for Value { Self::Dict(v) => v.hash(state), Self::Func(v) => v.hash(state), Self::Args(v) => v.hash(state), + Self::Module(v) => v.hash(state), Self::Dyn(v) => v.hash(state), } } @@ -402,6 +408,7 @@ primitive! { Content: "content", primitive! { Array: "array", Array } primitive! { Dict: "dictionary", Dict } primitive! { Func: "function", Func } +primitive! { Module: "module", Module } primitive! { Args: "arguments", Args } #[cfg(test)] diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index ccad77c2..4ef0b2a6 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -1430,29 +1430,26 @@ impl ForPattern { } node! { - /// A module import: `import a, b, c from "utils.typ"`. + /// A module import: `import "utils.typ": a, b, c`. ModuleImport } impl ModuleImport { - /// The items to be imported. - pub fn imports(&self) -> Imports { - self.0 - .children() - .find_map(|node| match node.kind() { - SyntaxKind::Star => Some(Imports::Wildcard), - SyntaxKind::ImportItems => { - let items = node.children().filter_map(SyntaxNode::cast).collect(); - Some(Imports::Items(items)) - } - _ => None, - }) - .expect("module import is missing items") + /// The module or path from which the items should be imported. + pub fn source(&self) -> Expr { + self.0.cast_last_child().expect("module import is missing source") } - /// The path to the file that should be imported. - pub fn path(&self) -> Expr { - self.0.cast_last_child().expect("module import is missing path") + /// The items to be imported. + pub fn imports(&self) -> Option { + self.0.children().find_map(|node| match node.kind() { + SyntaxKind::Star => Some(Imports::Wildcard), + SyntaxKind::ImportItems => { + let items = node.children().filter_map(SyntaxNode::cast).collect(); + Some(Imports::Items(items)) + } + _ => None, + }) } } @@ -1471,8 +1468,8 @@ node! { } impl ModuleInclude { - /// The path to the file that should be included. - pub fn path(&self) -> Expr { + /// The module or path from which the content should be included. + pub fn source(&self) -> Expr { self.0.cast_last_child().expect("module include is missing path") } } diff --git a/src/syntax/kind.rs b/src/syntax/kind.rs index 27a8da0f..54d5c81d 100644 --- a/src/syntax/kind.rs +++ b/src/syntax/kind.rs @@ -125,8 +125,8 @@ pub enum SyntaxKind { Import, /// The `include` keyword. Include, - /// The `from` keyword. - From, + /// The `as` keyword. + As, /// Markup of which all lines must have a minimal indentation. /// @@ -387,7 +387,7 @@ impl SyntaxKind { Self::Return => "keyword `return`", Self::Import => "keyword `import`", Self::Include => "keyword `include`", - Self::From => "keyword `from`", + Self::As => "keyword `as`", Self::Markup { .. } => "markup", Self::Text(_) => "text", Self::Linebreak => "linebreak", @@ -514,7 +514,7 @@ impl Hash for SyntaxKind { Self::Return => {} Self::Import => {} Self::Include => {} - Self::From => {} + Self::As => {} Self::Markup { min_indent } => min_indent.hash(state), Self::Text(s) => s.hash(state), Self::Linebreak => {} diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs index a0b1e7d1..74be792f 100644 --- a/src/syntax/parser.rs +++ b/src/syntax/parser.rs @@ -237,7 +237,7 @@ impl<'s> Parser<'s> { self.tokens.set_mode(match kind { Group::Bracket | Group::Strong | Group::Emph => TokenMode::Markup, Group::Math | Group::MathRow(_, _) => TokenMode::Math, - Group::Brace | Group::Paren | Group::Expr | Group::Imports => TokenMode::Code, + Group::Brace | Group::Paren | Group::Expr => TokenMode::Code, }); match kind { @@ -249,7 +249,6 @@ impl<'s> Parser<'s> { Group::Math => self.assert(SyntaxKind::Dollar), Group::MathRow(l, _) => self.assert(SyntaxKind::Atom(l.into())), Group::Expr => self.repeek(), - Group::Imports => self.repeek(), } } @@ -274,7 +273,6 @@ impl<'s> Parser<'s> { Group::Math => Some((SyntaxKind::Dollar, true)), Group::MathRow(_, r) => Some((SyntaxKind::Atom(r.into()), true)), Group::Expr => Some((SyntaxKind::Semicolon, false)), - Group::Imports => None, } { if self.current.as_ref() == Some(&end) { // If another group closes after a group with the missing @@ -346,7 +344,6 @@ impl<'s> Parser<'s> { .next() .map_or(false, |group| group.kind == Group::Math), Some(SyntaxKind::Semicolon) => self.inside(Group::Expr), - Some(SyntaxKind::From) => self.inside(Group::Imports), Some(SyntaxKind::Atom(s)) => match s.as_str() { ")" => self.inside(Group::MathRow('(', ')')), "}" => self.inside(Group::MathRow('{', '}')), @@ -377,7 +374,6 @@ impl<'s> Parser<'s> { match self.groups.last().map(|group| group.kind) { Some(Group::Strong | Group::Emph) => n >= 2, - Some(Group::Imports) => n >= 1, Some(Group::Expr) if n >= 1 => { // Allow else and method call to continue on next line. self.groups.iter().nth_back(1).map(|group| group.kind) @@ -541,8 +537,6 @@ pub enum Group { MathRow(char, char), /// A group ended by a semicolon or a line break: `;`, `\n`. Expr, - /// A group for import items, ended by a semicolon, line break or `from`. - Imports, } impl Group { diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index 900e0e67..7f557fac 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -1052,27 +1052,26 @@ fn for_pattern(p: &mut Parser) -> ParseResult { fn module_import(p: &mut Parser) -> ParseResult { p.perform(SyntaxKind::ModuleImport, |p| { p.assert(SyntaxKind::Import); + expr(p)?; - if !p.eat_if(SyntaxKind::Star) { - // This is the list of identifiers scenario. - p.perform(SyntaxKind::ImportItems, |p| { - p.start_group(Group::Imports); - let marker = p.marker(); - let items = collection(p, false).1; - if items == 0 { - p.expected("import items"); - } - p.end_group(); + if !p.eat_if(SyntaxKind::Colon) || p.eat_if(SyntaxKind::Star) { + return Ok(()); + } - marker.filter_children(p, |n| match n.kind() { - SyntaxKind::Ident(_) | SyntaxKind::Comma => Ok(()), - _ => Err("expected identifier"), - }); + // This is the list of identifiers scenario. + p.perform(SyntaxKind::ImportItems, |p| { + let marker = p.marker(); + let items = collection(p, false).1; + if items == 0 { + p.expected("import items"); + } + marker.filter_children(p, |n| match n.kind() { + SyntaxKind::Ident(_) | SyntaxKind::Comma => Ok(()), + _ => Err("expected identifier"), }); - }; + }); - p.expect(SyntaxKind::From)?; - expr(p) + Ok(()) }) } diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs index 98b6d8a8..02bbd3a4 100644 --- a/src/syntax/tokens.rs +++ b/src/syntax/tokens.rs @@ -684,7 +684,7 @@ fn keyword(ident: &str) -> Option { "return" => SyntaxKind::Return, "import" => SyntaxKind::Import, "include" => SyntaxKind::Include, - "from" => SyntaxKind::From, + "as" => SyntaxKind::As, _ => return None, }) } diff --git a/tests/ref/compiler/import.png b/tests/ref/compiler/import.png index dddbe408..89880086 100644 Binary files a/tests/ref/compiler/import.png and b/tests/ref/compiler/import.png differ diff --git a/tests/src/benches.rs b/tests/src/benches.rs index 2c693c7c..610b89a8 100644 --- a/tests/src/benches.rs +++ b/tests/src/benches.rs @@ -72,7 +72,8 @@ fn bench_typeset(iai: &mut Iai) { let world = BenchWorld::new(); let route = typst::model::Route::default(); let module = typst::model::eval(world.track(), route.track(), &world.source).unwrap(); - iai.run(|| typst::model::typeset(world.track(), &module.content)); + let content = module.content(); + iai.run(|| typst::model::typeset(world.track(), &content)); } fn bench_compile(iai: &mut Iai) { diff --git a/tests/src/tests.rs b/tests/src/tests.rs index 9dc7aa6d..16dea380 100644 --- a/tests/src/tests.rs +++ b/tests/src/tests.rs @@ -432,7 +432,7 @@ fn test_part( let world = (world as &dyn World).track(); let route = typst::model::Route::default(); let module = typst::model::eval(world, route.track(), source).unwrap(); - println!("Model:\n{:#?}\n", module.content); + println!("Model:\n{:#?}\n", module.content()); } let (mut frames, errors) = match typst::compile(world, source) { diff --git a/tests/typ/compiler/block.typ b/tests/typ/compiler/block.typ index 45f63f8e..7cf1f8be 100644 --- a/tests/typ/compiler/block.typ +++ b/tests/typ/compiler/block.typ @@ -79,7 +79,7 @@ --- // Double block creates a scope. {{ - import b from "module.typ" + import "module.typ": b test(b, 1) }} diff --git a/tests/typ/compiler/closure.typ b/tests/typ/compiler/closure.typ index 2c6c1ea0..c7321204 100644 --- a/tests/typ/compiler/closure.typ +++ b/tests/typ/compiler/closure.typ @@ -62,7 +62,7 @@ { let b = "module.typ" let f() = { - import b from b + import b: b b } test(f(), 1) diff --git a/tests/typ/compiler/field.typ b/tests/typ/compiler/field.typ index 1235c84e..78439ae0 100644 --- a/tests/typ/compiler/field.typ +++ b/tests/typ/compiler/field.typ @@ -31,7 +31,7 @@ {false.ok} --- -// Error: 29-32 unknown field "fun" +// Error: 29-32 unknown field `fun` #show heading: node => node.fun = A diff --git a/tests/typ/compiler/import.typ b/tests/typ/compiler/import.typ index 0620403d..6f2ac459 100644 --- a/tests/typ/compiler/import.typ +++ b/tests/typ/compiler/import.typ @@ -1,116 +1,118 @@ // Test module imports. +// Ref: false --- -// Test importing semantics. - -// A named import. -#import item from "module.typ" -#test(item(1, 2), 3) +// Test basic syntax and semantics. +// Ref: true // Test that this will be overwritten. #let value = [foo] // Import multiple things. -#import fn, value from "module.typ" +#import "module.typ": fn, value #fn[Like and Subscribe!] #value +// Should output `bye`. +// Stop at semicolon. +#import "module.typ": a, c;bye + +--- +// An item import. +#import "module.typ": item +#test(item(1, 2), 3) + // Code mode { - import b from "module.typ" + import "module.typ": b test(b, 1) } // A wildcard import. -#import * from "module.typ" +#import "module.typ": * // It exists now! -#d +#test(d, 3) +--- +// A module import without items. +#import "module.typ" +#test(module.b, 1) +#test((module.item)(1, 2), 3) + +--- // Who needs whitespace anyways? -#import*from"module.typ" - -// Should output `bye`. -// Stop at semicolon. -#import a, c from "module.typ";bye +#import"module.typ":* // Allow the trailing comma. -#import a, c, from "module.typ" +#import "module.typ": a, c, --- -// Error: 19-21 failed to load file (is a directory) -#import name from "" +// Error: 9-11 failed to load file (is a directory) +#import "": name --- -// Error: 16-27 file not found (searched at typ/compiler/lib/0.2.1) -#import * from "lib/0.2.1" +// Error: 9-20 file not found (searched at typ/compiler/lib/0.2.1) +#import "lib/0.2.1" --- // Some non-text stuff. -// Error: 16-37 file is not valid utf-8 -#import * from "../../res/rhino.png" +// Error: 9-30 file is not valid utf-8 +#import "../../res/rhino.png" --- // Unresolved import. -// Error: 9-21 unresolved import -#import non_existing from "module.typ" +// Error: 23-35 unresolved import +#import "module.typ": non_existing --- // Cyclic import of this very file. -// Error: 16-30 cyclic import -#import * from "./import.typ" +// Error: 9-23 cyclic import +#import "./import.typ" --- // Cyclic import in other file. -#import * from "./modules/cycle1.typ" +#import "./modules/cycle1.typ": * This is never reached. --- -// Error: 8 expected import items -// Error: 8 expected keyword `from` +// Error: 8 expected expression #import -// Error: 9-19 expected identifier, found string -// Error: 19 expected keyword `from` -#import "file.typ" - -// Error: 16-19 expected identifier, found string -// Error: 22 expected keyword `from` -#import afrom, "b", c - -// Error: 9 expected import items -#import from "module.typ" - -// Error: 9-10 expected expression, found assignment operator -// Error: 10 expected import items -#import = from "module.typ" - -// Error: 15 expected expression -#import * from - -// An additional trailing comma. -// Error: 17-18 expected expression, found comma -#import a, b, c,, from "module.typ" - -// Error: 1-6 unexpected keyword `from` -#from "module.typ" - -// Error: 2:2 expected semicolon or line break -#import * from "module.typ -"target - -// Error: 28 expected semicolon or line break -#import * from "module.typ" ยง 0.2.1 - -// A star in the list. -// Error: 12-13 expected expression, found star -#import a, *, b from "module.typ" - -// An item after a star. -// Error: 10 expected keyword `from` -#import *, a from "module.typ" +--- +// Error: 26-29 expected identifier, found string +#import "module.typ": a, "b", c --- -// Error: 9-13 expected identifier, found named pair -#import a: 1 from "" +// Error: 22 expected import items +#import "module.typ": + +--- +// Error: 23-24 expected expression, found assignment operator +// Error: 24 expected import items +#import "module.typ": = + +--- +// An additional trailing comma. +// Error: 31-32 expected expression, found comma +#import "module.typ": a, b, c,, + +--- +// Error: 2:2 expected semicolon or line break +#import "module.typ +"stuff + +--- +// A star in the list. +// Error: 26-27 expected expression, found star +#import "module.typ": a, *, b + +--- +// An item after a star. +// Error: 24 expected semicolon or line break +#import "module.typ": *, a + +--- +// Error: 13-17 expected identifier, found named pair +#import "": a: 1 diff --git a/tests/typ/compiler/modules/cycle1.typ b/tests/typ/compiler/modules/cycle1.typ index a9c00f5e..02067b71 100644 --- a/tests/typ/compiler/modules/cycle1.typ +++ b/tests/typ/compiler/modules/cycle1.typ @@ -1,6 +1,6 @@ // Ref: false -#import * from "cycle2.typ" +#import "cycle2.typ": * #let inaccessible = "wow" This is the first element of an import cycle. diff --git a/tests/typ/compiler/modules/cycle2.typ b/tests/typ/compiler/modules/cycle2.typ index 204da519..191647db 100644 --- a/tests/typ/compiler/modules/cycle2.typ +++ b/tests/typ/compiler/modules/cycle2.typ @@ -1,6 +1,6 @@ // Ref: false -#import * from "cycle1.typ" +#import "cycle1.typ": * #let val = "much cycle" This is the second element of an import cycle. diff --git a/tests/typ/compiler/show-node.typ b/tests/typ/compiler/show-node.typ index 5dd7ad98..46663c68 100644 --- a/tests/typ/compiler/show-node.typ +++ b/tests/typ/compiler/show-node.typ @@ -78,7 +78,7 @@ Another text. = Heading --- -// Error: 25-29 unknown field "page" +// Error: 25-29 unknown field `page` #show heading: it => it.page = Heading