diff --git a/crates/typst-ide/src/analyze.rs b/crates/typst-ide/src/analyze.rs index 47214481d..1347e264d 100644 --- a/crates/typst-ide/src/analyze.rs +++ b/crates/typst-ide/src/analyze.rs @@ -52,7 +52,6 @@ pub fn analyze_expr( pub fn analyze_import(world: &dyn World, source: &LinkedNode) -> Option { // Use span in the node for resolving imports with relative paths. let source_span = source.span(); - let (source, _) = analyze_expr(world, source).into_iter().next()?; if source.scope().is_some() { return Some(source); @@ -76,6 +75,7 @@ pub fn analyze_import(world: &dyn World, source: &LinkedNode) -> Option { Scopes::new(Some(world.library())), Span::detached(), ); + typst::eval::import(&mut vm, source, source_span, true) .ok() .map(Value::Module) diff --git a/crates/typst-ide/src/complete.rs b/crates/typst-ide/src/complete.rs index af85fcfa9..8a9416d15 100644 --- a/crates/typst-ide/src/complete.rs +++ b/crates/typst-ide/src/complete.rs @@ -378,12 +378,12 @@ fn field_access_completions( value: &Value, styles: &Option, ) { - for (name, value) in value.ty().scope().iter() { + for (name, value, _) in value.ty().scope().iter() { ctx.value_completion(Some(name.clone()), value, true, None); } if let Some(scope) = value.scope() { - for (name, value) in scope.iter() { + for (name, value, _) in scope.iter() { ctx.value_completion(Some(name.clone()), value, true, None); } } @@ -549,7 +549,7 @@ fn import_item_completions<'a>( ctx.snippet_completion("*", "*", "Import everything."); } - for (name, value) in scope.iter() { + for (name, value, _) in scope.iter() { if existing.iter().all(|item| item.original_name().as_str() != name) { ctx.value_completion(Some(name.clone()), value, false, None); } @@ -1337,7 +1337,7 @@ impl<'a> CompletionContext<'a> { ); let scope = if in_math { self.math } else { self.global }; - for (name, value) in scope.iter() { + for (name, value, _) in scope.iter() { if filter(value) && !defined.contains(name) { self.value_completion(Some(name.clone()), value, parens, None); } diff --git a/crates/typst-ide/src/definition.rs b/crates/typst-ide/src/definition.rs index 15c326a0c..14146b6b2 100644 --- a/crates/typst-ide/src/definition.rs +++ b/crates/typst-ide/src/definition.rs @@ -22,9 +22,7 @@ pub fn definition( let root = LinkedNode::new(source.root()); let leaf = root.leaf_at(cursor, side)?; - let target = deref_target(leaf.clone())?; - - let mut use_site = match target { + let mut use_site = match deref_target(leaf.clone())? { DerefTarget::VarAccess(node) | DerefTarget::Callee(node) => node, DerefTarget::IncludePath(path) | DerefTarget::ImportPath(path) => { let import_item = @@ -82,10 +80,10 @@ pub fn definition( .then_some(site.span()) .unwrap_or_else(Span::detached), )), - NamedItem::Import(name, span, value) => Some(Definition::item( + NamedItem::Import(name, name_span, value) => Some(Definition::item( name.clone(), Span::detached(), - span, + name_span, value.cloned(), )), } @@ -99,14 +97,14 @@ pub fn definition( | Some(SyntaxKind::MathFrac) | Some(SyntaxKind::MathAttach) ); - let library = world.library(); + let library = world.library(); let scope = if in_math { library.math.scope() } else { library.global.scope() }; - for (item_name, value) in scope.iter() { + for (item_name, value, span) in scope.iter() { if *item_name == name { return Some(Definition::item( name, - Span::detached(), + span, Span::detached(), Some(value.clone()), )); diff --git a/crates/typst-ide/src/matchers.rs b/crates/typst-ide/src/matchers.rs index 757e5ab65..1daec8193 100644 --- a/crates/typst-ide/src/matchers.rs +++ b/crates/typst-ide/src/matchers.rs @@ -77,12 +77,8 @@ pub fn named_items( // ``` Some(ast::Imports::Wildcard) => { if let Some(scope) = source.and_then(|(value, _)| value.scope()) { - for (name, value) in scope.iter() { - let item = NamedItem::Import( - name, - Span::detached(), - Some(value), - ); + for (name, value, span) in scope.iter() { + let item = NamedItem::Import(name, span, Some(value)); if let Some(res) = recv(item) { return Some(res); } @@ -94,9 +90,17 @@ pub fn named_items( // ``` Some(ast::Imports::Items(items)) => { for item in items.iter() { - let name = item.bound_name(); + let original = item.original_name(); + let bound = item.bound_name(); + let scope = source.and_then(|(value, _)| value.scope()); + let span = scope + .and_then(|s| s.get_span(&original)) + .unwrap_or(Span::detached()) + .or(bound.span()); + + let value = scope.and_then(|s| s.get(&original)); if let Some(res) = - recv(NamedItem::Import(name.get(), name.span(), None)) + recv(NamedItem::Import(bound.get(), span, value)) { return Some(res); } diff --git a/crates/typst-ide/src/tooltip.rs b/crates/typst-ide/src/tooltip.rs index 60a5f9760..8e1de778e 100644 --- a/crates/typst-ide/src/tooltip.rs +++ b/crates/typst-ide/src/tooltip.rs @@ -125,7 +125,7 @@ fn closure_tooltip(leaf: &LinkedNode) -> Option { let captures = visitor.finish(); let mut names: Vec<_> = - captures.iter().map(|(name, _)| eco_format!("`{name}`")).collect(); + captures.iter().map(|(name, ..)| eco_format!("`{name}`")).collect(); if names.is_empty() { return None; } diff --git a/crates/typst-syntax/src/span.rs b/crates/typst-syntax/src/span.rs index 8138a3166..4d5dd9c37 100644 --- a/crates/typst-syntax/src/span.rs +++ b/crates/typst-syntax/src/span.rs @@ -83,6 +83,15 @@ impl Span { self.0.get() & ((1 << Self::BITS) - 1) } + /// Return `other` if `self` is detached and `self` otherwise. + pub fn or(self, other: Self) -> Self { + if self.is_detached() { + other + } else { + self + } + } + /// Resolve a file location relative to this span's source. pub fn resolve_path(self, path: &str) -> Result { let Some(file) = self.id() else { diff --git a/crates/typst/src/eval/call.rs b/crates/typst/src/eval/call.rs index ddc054166..3811ee609 100644 --- a/crates/typst/src/eval/call.rs +++ b/crates/typst/src/eval/call.rs @@ -1,5 +1,5 @@ use comemo::{Tracked, TrackedMut}; -use ecow::{eco_format, EcoVec}; +use ecow::{eco_format, EcoString, EcoVec}; use crate::diag::{bail, error, At, HintedStrResult, SourceResult, Trace, Tracepoint}; use crate::engine::Engine; @@ -410,9 +410,11 @@ impl<'a> CapturesVisitor<'a> { // Identifiers that shouldn't count as captures because they // actually bind a new name are handled below (individually through // the expressions that contain them). - Some(ast::Expr::Ident(ident)) => self.capture(&ident, Scopes::get), + Some(ast::Expr::Ident(ident)) => { + self.capture(ident.get(), ident.span(), Scopes::get) + } Some(ast::Expr::MathIdent(ident)) => { - self.capture(&ident, Scopes::get_in_math) + self.capture(ident.get(), ident.span(), Scopes::get_in_math) } // Code and content blocks create a scope. @@ -520,13 +522,14 @@ impl<'a> CapturesVisitor<'a> { /// Bind a new internal variable. fn bind(&mut self, ident: ast::Ident) { - self.internal.top.define(ident.get().clone(), Value::None); + self.internal.top.define_ident(ident, Value::None); } /// Capture a variable if it isn't internal. fn capture( &mut self, - ident: &str, + ident: &EcoString, + span: Span, getter: impl FnOnce(&'a Scopes<'a>, &str) -> HintedStrResult<&'a Value>, ) { if self.internal.get(ident).is_err() { @@ -538,7 +541,12 @@ impl<'a> CapturesVisitor<'a> { return; }; - self.captures.define_captured(ident, value.clone(), self.capturer); + self.captures.define_captured( + ident.clone(), + value.clone(), + self.capturer, + span, + ); } } } @@ -561,7 +569,7 @@ mod tests { visitor.visit(&root); let captures = visitor.finish(); - let mut names: Vec<_> = captures.iter().map(|(k, _)| k).collect(); + let mut names: Vec<_> = captures.iter().map(|(k, ..)| k).collect(); names.sort(); assert_eq!(names, result); diff --git a/crates/typst/src/eval/import.rs b/crates/typst/src/eval/import.rs index 588578891..1ffbfa748 100644 --- a/crates/typst/src/eval/import.rs +++ b/crates/typst/src/eval/import.rs @@ -31,7 +31,7 @@ impl Eval for ast::ModuleImport<'_> { } } - if let Some(new_name) = &new_name { + if let Some(new_name) = new_name { if let ast::Expr::Ident(ident) = self.source() { if ident.as_str() == new_name.as_str() { // Warn on `import x as x` @@ -43,7 +43,7 @@ impl Eval for ast::ModuleImport<'_> { } // Define renamed module on the scope. - vm.scopes.top.define(new_name.as_str(), source.clone()); + vm.scopes.top.define_ident(new_name, source.clone()); } let scope = source.scope().unwrap(); @@ -56,8 +56,8 @@ impl Eval for ast::ModuleImport<'_> { } } Some(ast::Imports::Wildcard) => { - for (var, value) in scope.iter() { - vm.scopes.top.define(var.clone(), value.clone()); + for (var, value, span) in scope.iter() { + vm.scopes.top.define_spanned(var.clone(), value.clone(), span); } } Some(ast::Imports::Items(items)) => { diff --git a/crates/typst/src/eval/vm.rs b/crates/typst/src/eval/vm.rs index be43b5bae..76b12fc13 100644 --- a/crates/typst/src/eval/vm.rs +++ b/crates/typst/src/eval/vm.rs @@ -47,7 +47,7 @@ impl<'a> Vm<'a> { if self.inspected == Some(var.span()) { self.trace(value.clone()); } - self.scopes.top.define(var.get().clone(), value); + self.scopes.top.define_ident(var, value); } /// Trace a value. diff --git a/crates/typst/src/foundations/dict.rs b/crates/typst/src/foundations/dict.rs index e77d33ff1..ed024d4f3 100644 --- a/crates/typst/src/foundations/dict.rs +++ b/crates/typst/src/foundations/dict.rs @@ -261,7 +261,7 @@ pub struct ToDict(Dict); cast! { ToDict, - v: Module => Self(v.scope().iter().map(|(k, v)| (Str::from(k.clone()), v.clone())).collect()), + v: Module => Self(v.scope().iter().map(|(k, v, _)| (Str::from(k.clone()), v.clone())).collect()), } impl Debug for Dict { diff --git a/crates/typst/src/foundations/mod.rs b/crates/typst/src/foundations/mod.rs index eb9f6d661..b7783dda9 100644 --- a/crates/typst/src/foundations/mod.rs +++ b/crates/typst/src/foundations/mod.rs @@ -290,7 +290,7 @@ pub fn eval( let dict = scope; let mut scope = Scope::new(); for (key, value) in dict { - scope.define(key, value); + scope.define_spanned(key, value, span); } crate::eval::eval_string(engine.world, &text, span, mode, scope) } diff --git a/crates/typst/src/foundations/scope.rs b/crates/typst/src/foundations/scope.rs index caa82e136..befecbf5b 100644 --- a/crates/typst/src/foundations/scope.rs +++ b/crates/typst/src/foundations/scope.rs @@ -9,6 +9,8 @@ use crate::foundations::{ Element, Func, IntoValue, Module, NativeElement, NativeFunc, NativeFuncData, NativeType, Type, Value, }; +use crate::syntax::ast::{self, AstNode}; +use crate::syntax::Span; use crate::util::Static; use crate::Library; @@ -129,6 +131,23 @@ impl Scope { /// Bind a value to a name. #[track_caller] pub fn define(&mut self, name: impl Into, value: impl IntoValue) { + self.define_spanned(name, value, Span::detached()) + } + + /// Bind a value to a name defined by an identifier. + #[track_caller] + pub fn define_ident(&mut self, ident: ast::Ident, value: impl IntoValue) { + self.define_spanned(ident.get().clone(), value, ident.span()) + } + + /// Bind a value to a name. + #[track_caller] + pub fn define_spanned( + &mut self, + name: impl Into, + value: impl IntoValue, + span: Span, + ) { let name = name.into(); #[cfg(debug_assertions)] @@ -136,8 +155,24 @@ impl Scope { panic!("duplicate definition: {name}"); } - self.map - .insert(name, Slot::new(value.into_value(), Kind::Normal, self.category)); + self.map.insert( + name, + Slot::new(value.into_value(), span, Kind::Normal, self.category), + ); + } + + /// Define a captured, immutable binding. + pub fn define_captured( + &mut self, + name: EcoString, + value: Value, + capturer: Capturer, + span: Span, + ) { + self.map.insert( + name, + Slot::new(value.into_value(), span, Kind::Captured(capturer), self.category), + ); } /// Define a native function through a Rust type that shadows the function. @@ -168,19 +203,6 @@ impl Scope { self.define(module.name().clone(), module); } - /// Define a captured, immutable binding. - pub fn define_captured( - &mut self, - var: impl Into, - value: impl IntoValue, - capturer: Capturer, - ) { - self.map.insert( - var.into(), - Slot::new(value.into_value(), Kind::Captured(capturer), self.category), - ); - } - /// Try to access a variable immutably. pub fn get(&self, var: &str) -> Option<&Value> { self.map.get(var).map(Slot::read) @@ -194,14 +216,19 @@ impl Scope { .map(|res| res.map_err(HintedString::from)) } + /// Get the span of a definition. + pub fn get_span(&self, var: &str) -> Option { + Some(self.map.get(var)?.span) + } + /// Get the category of a definition. pub fn get_category(&self, var: &str) -> Option { self.map.get(var)?.category } /// Iterate over all definitions. - pub fn iter(&self) -> impl Iterator { - self.map.iter().map(|(k, v)| (k, v.read())) + pub fn iter(&self) -> impl Iterator { + self.map.iter().map(|(k, v)| (k, v.read(), v.span)) } } @@ -241,6 +268,8 @@ struct Slot { value: Value, /// The kind of slot, determines how the value can be accessed. kind: Kind, + /// A span associated with the stored value. + span: Span, /// The category of the slot. category: Option, } @@ -265,8 +294,8 @@ pub enum Capturer { impl Slot { /// Create a new slot. - fn new(value: Value, kind: Kind, category: Option) -> Self { - Self { value, kind, category } + fn new(value: Value, span: Span, kind: Kind, category: Option) -> Self { + Self { value, span, kind, category } } /// Read the value. diff --git a/docs/src/lib.rs b/docs/src/lib.rs index 6e00f9ee8..d52019a08 100644 --- a/docs/src/lib.rs +++ b/docs/src/lib.rs @@ -48,8 +48,8 @@ static GROUPS: Lazy> = Lazy::new(|| { .module() .scope() .iter() - .filter(|(_, v)| matches!(v, Value::Func(_))) - .map(|(k, _)| k.clone()) + .filter(|(_, v, _)| matches!(v, Value::Func(_))) + .map(|(k, _, _)| k.clone()) .collect(); } } @@ -253,7 +253,7 @@ fn category_page(resolver: &dyn Resolver, category: Category) -> PageModel { // Add values and types. let scope = module.scope(); - for (name, value) in scope.iter() { + for (name, value, _) in scope.iter() { if scope.get_category(name) != Some(category) { continue; } @@ -467,7 +467,7 @@ fn casts( fn scope_models(resolver: &dyn Resolver, name: &str, scope: &Scope) -> Vec { scope .iter() - .filter_map(|(_, value)| { + .filter_map(|(_, value, _)| { let Value::Func(func) = value else { return None }; Some(func_model(resolver, func, &[name], true)) }) @@ -653,7 +653,7 @@ fn symbols_page(resolver: &dyn Resolver, parent: &str, group: &GroupData) -> Pag /// Produce a symbol list's model. fn symbols_model(resolver: &dyn Resolver, group: &GroupData) -> SymbolsModel { let mut list = vec![]; - for (name, value) in group.module().scope().iter() { + for (name, value, _) in group.module().scope().iter() { let Value::Symbol(symbol) = value else { continue }; let complete = |variant: &str| { if variant.is_empty() {