diff options
| author | Tulio Martins <113527485+tulio240@users.noreply.github.com> | 2024-05-30 04:56:40 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-05-30 07:56:40 +0000 |
| commit | 06a925a0ee0e1e7b93428d8b4952512a2e48ed79 (patch) | |
| tree | 02725b8b534eff798ba96949073edc6766f45fa6 /crates | |
| parent | 5f6d942519b36eb839a0a11e4ef3f1ea4013a8b5 (diff) | |
Add nested import syntax (#4228)
Co-authored-by: LuizAugustoPapa <luiz.papa@aluno.puc-rio.br>
Co-authored-by: PepinhoJp <pepinho.jp@gmail.com>
Co-authored-by: PgBiel <9021226+PgBiel@users.noreply.github.com>
Diffstat (limited to 'crates')
| -rw-r--r-- | crates/typst-syntax/src/ast.rs | 44 | ||||
| -rw-r--r-- | crates/typst-syntax/src/highlight.rs | 1 | ||||
| -rw-r--r-- | crates/typst-syntax/src/kind.rs | 3 | ||||
| -rw-r--r-- | crates/typst-syntax/src/parser.rs | 7 | ||||
| -rw-r--r-- | crates/typst/src/eval/import.rs | 69 |
5 files changed, 102 insertions, 22 deletions
diff --git a/crates/typst-syntax/src/ast.rs b/crates/typst-syntax/src/ast.rs index 01e0944d..1cd9cd42 100644 --- a/crates/typst-syntax/src/ast.rs +++ b/crates/typst-syntax/src/ast.rs @@ -2040,29 +2040,54 @@ impl<'a> ImportItems<'a> { pub fn iter(self) -> impl DoubleEndedIterator<Item = ImportItem<'a>> { self.0.children().filter_map(|child| match child.kind() { SyntaxKind::RenamedImportItem => child.cast().map(ImportItem::Renamed), - SyntaxKind::Ident => child.cast().map(ImportItem::Simple), + SyntaxKind::ImportItemPath => child.cast().map(ImportItem::Simple), _ => Option::None, }) } } +node! { + /// A path to a submodule's imported name: `a.b.c`. + ImportItemPath +} + +impl<'a> ImportItemPath<'a> { + /// An iterator over the path's components. + pub fn iter(self) -> impl DoubleEndedIterator<Item = Ident<'a>> { + self.0.children().filter_map(SyntaxNode::cast) + } + + /// The name of the imported item. This is the last segment in the path. + pub fn name(self) -> Ident<'a> { + self.iter().last().unwrap_or_default() + } +} + /// An imported item, potentially renamed to another identifier. #[derive(Debug, Copy, Clone, Hash)] pub enum ImportItem<'a> { /// A non-renamed import (the item's name in the scope is the same as its /// name). - Simple(Ident<'a>), + Simple(ImportItemPath<'a>), /// A renamed import (the item was bound to a different name in the scope /// than the one it was defined as). Renamed(RenamedImportItem<'a>), } impl<'a> ImportItem<'a> { + /// The path to the imported item. + pub fn path(self) -> ImportItemPath<'a> { + match self { + Self::Simple(path) => path, + Self::Renamed(renamed_item) => renamed_item.path(), + } + } + /// The original name of the imported item, at its source. This will be the /// equal to the bound name if the item wasn't renamed with 'as'. pub fn original_name(self) -> Ident<'a> { match self { - Self::Simple(name) => name, + Self::Simple(path) => path.name(), Self::Renamed(renamed_item) => renamed_item.original_name(), } } @@ -2071,7 +2096,7 @@ impl<'a> ImportItem<'a> { /// name, if it was renamed; otherwise, it's just its original name. pub fn bound_name(self) -> Ident<'a> { match self { - Self::Simple(name) => name, + Self::Simple(path) => path.name(), Self::Renamed(renamed_item) => renamed_item.new_name(), } } @@ -2083,17 +2108,22 @@ node! { } impl<'a> RenamedImportItem<'a> { - /// The original name of the imported item (`a` in `a as d`). - pub fn original_name(self) -> Ident<'a> { + /// The path to the imported item. + pub fn path(self) -> ImportItemPath<'a> { self.0.cast_first_match().unwrap_or_default() } + /// The original name of the imported item (`a` in `a as d` or `c.b.a as d`). + pub fn original_name(self) -> Ident<'a> { + self.path().name() + } + /// The new name of the imported item (`d` in `a as d`). pub fn new_name(self) -> Ident<'a> { self.0 .children() .filter_map(SyntaxNode::cast) - .nth(1) + .last() .unwrap_or_default() } } diff --git a/crates/typst-syntax/src/highlight.rs b/crates/typst-syntax/src/highlight.rs index b6aa9e8d..0c1f3d5f 100644 --- a/crates/typst-syntax/src/highlight.rs +++ b/crates/typst-syntax/src/highlight.rs @@ -277,6 +277,7 @@ pub fn highlight(node: &LinkedNode) -> Option<Tag> { SyntaxKind::ForLoop => None, SyntaxKind::ModuleImport => None, SyntaxKind::ImportItems => None, + SyntaxKind::ImportItemPath => None, SyntaxKind::RenamedImportItem => None, SyntaxKind::ModuleInclude => None, SyntaxKind::LoopBreak => None, diff --git a/crates/typst-syntax/src/kind.rs b/crates/typst-syntax/src/kind.rs index 4c3d178f..7505dbc6 100644 --- a/crates/typst-syntax/src/kind.rs +++ b/crates/typst-syntax/src/kind.rs @@ -262,6 +262,8 @@ pub enum SyntaxKind { ModuleImport, /// Items to import from a module: `a, b, c`. ImportItems, + /// A path to an imported name from a submodule: `a.b.c`. + ImportItemPath, /// A renamed import item: `a as d`. RenamedImportItem, /// A module include: `include "chapter1.typ"`. @@ -488,6 +490,7 @@ impl SyntaxKind { Self::ForLoop => "for-loop expression", Self::ModuleImport => "`import` expression", Self::ImportItems => "import items", + Self::ImportItemPath => "imported item path", Self::RenamedImportItem => "renamed import item", Self::ModuleInclude => "`include` expression", Self::LoopBreak => "`break` expression", diff --git a/crates/typst-syntax/src/parser.rs b/crates/typst-syntax/src/parser.rs index d8ac1198..d341bca2 100644 --- a/crates/typst-syntax/src/parser.rs +++ b/crates/typst-syntax/src/parser.rs @@ -1003,6 +1003,13 @@ fn import_items(p: &mut Parser) { p.unexpected(); } + // Nested import path: `a.b.c` + while p.eat_if(SyntaxKind::Dot) { + p.expect(SyntaxKind::Ident); + } + + p.wrap(item_marker, SyntaxKind::ImportItemPath); + // Rename imported item. if p.eat_if(SyntaxKind::As) { p.expect(SyntaxKind::Ident); diff --git a/crates/typst/src/eval/import.rs b/crates/typst/src/eval/import.rs index 58857889..fbd55b7c 100644 --- a/crates/typst/src/eval/import.rs +++ b/crates/typst/src/eval/import.rs @@ -63,23 +63,62 @@ impl Eval for ast::ModuleImport<'_> { Some(ast::Imports::Items(items)) => { let mut errors = eco_vec![]; for item in items.iter() { - let original_ident = item.original_name(); - if let Some(value) = scope.get(&original_ident) { - // Warn on `import ...: x as x` - if let ast::ImportItem::Renamed(renamed_item) = &item { - if renamed_item.original_name().as_str() - == renamed_item.new_name().as_str() - { - vm.engine.tracer.warn(warning!( - renamed_item.new_name().span(), - "unnecessary import rename to same name", - )); + let mut path = item.path().iter().peekable(); + let mut scope = scope; + + while let Some(component) = &path.next() { + let Some(value) = scope.get(component) else { + errors.push(error!(component.span(), "unresolved import")); + break; + }; + + if path.peek().is_some() { + // Nested import, as this is not the last component. + // This must be a submodule. + let Some(submodule) = value.scope() else { + let error = if matches!(value, Value::Func(function) if function.scope().is_none()) + { + error!( + component.span(), + "cannot import from user-defined functions" + ) + } else if !matches!( + value, + Value::Func(_) | Value::Module(_) | Value::Type(_) + ) { + error!( + component.span(), + "expected module, function, or type, found {}", + value.ty() + ) + } else { + panic!("unexpected nested import failure") + }; + errors.push(error); + break; + }; + + // Walk into the submodule. + scope = submodule; + } else { + // Now that we have the scope of the innermost submodule + // in the import path, we may extract the desired item from + // it. + + // Warn on `import ...: x as x` + if let ast::ImportItem::Renamed(renamed_item) = &item { + if renamed_item.original_name().as_str() + == renamed_item.new_name().as_str() + { + vm.engine.tracer.warn(warning!( + renamed_item.new_name().span(), + "unnecessary import rename to same name", + )); + } } - } - vm.define(item.bound_name(), value.clone()); - } else { - errors.push(error!(original_ident.span(), "unresolved import")); + vm.define(item.bound_name(), value.clone()); + } } } if !errors.is_empty() { |
