summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/typst-ide/src/analyze.rs2
-rw-r--r--crates/typst-ide/src/complete.rs8
-rw-r--r--crates/typst-ide/src/definition.rs14
-rw-r--r--crates/typst-ide/src/matchers.rs20
-rw-r--r--crates/typst-ide/src/tooltip.rs2
-rw-r--r--crates/typst-syntax/src/span.rs9
-rw-r--r--crates/typst/src/eval/call.rs22
-rw-r--r--crates/typst/src/eval/import.rs8
-rw-r--r--crates/typst/src/eval/vm.rs2
-rw-r--r--crates/typst/src/foundations/dict.rs2
-rw-r--r--crates/typst/src/foundations/mod.rs2
-rw-r--r--crates/typst/src/foundations/scope.rs67
-rw-r--r--docs/src/lib.rs10
13 files changed, 108 insertions, 60 deletions
diff --git a/crates/typst-ide/src/analyze.rs b/crates/typst-ide/src/analyze.rs
index ecb73dce..c3779556 100644
--- a/crates/typst-ide/src/analyze.rs
+++ b/crates/typst-ide/src/analyze.rs
@@ -49,7 +49,6 @@ pub fn analyze_expr(
pub fn analyze_import(world: &dyn World, source: &LinkedNode) -> Option<Value> {
// 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);
@@ -73,6 +72,7 @@ pub fn analyze_import(world: &dyn World, source: &LinkedNode) -> Option<Value> {
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 c4f86d04..f6c96d00 100644
--- a/crates/typst-ide/src/complete.rs
+++ b/crates/typst-ide/src/complete.rs
@@ -386,12 +386,12 @@ fn field_access_completions(
value: &Value,
styles: &Option<Styles>,
) {
- 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);
}
}
@@ -557,7 +557,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);
}
@@ -1345,7 +1345,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 45262781..4323226d 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 757e5ab6..1daec819 100644
--- a/crates/typst-ide/src/matchers.rs
+++ b/crates/typst-ide/src/matchers.rs
@@ -77,12 +77,8 @@ pub fn named_items<T>(
// ```
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<T>(
// ```
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 c78c02d8..02fb3ec6 100644
--- a/crates/typst-ide/src/tooltip.rs
+++ b/crates/typst-ide/src/tooltip.rs
@@ -126,7 +126,7 @@ fn closure_tooltip(leaf: &LinkedNode) -> Option<Tooltip> {
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 8138a316..4d5dd9c3 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<FileId, EcoString> {
let Some(file) = self.id() else {
diff --git a/crates/typst/src/eval/call.rs b/crates/typst/src/eval/call.rs
index ed972c00..ee4c4787 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, HintedString, SourceDiagnostic, SourceResult,
@@ -442,9 +442,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.
@@ -552,13 +554,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() {
@@ -570,7 +573,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,
+ );
}
}
}
@@ -593,7 +601,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 da07e666..68187a96 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 27960eb6..4d346870 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 991f3f7a..6e61838e 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 eb9f6d66..b7783dda 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 0313df7a..b118540e 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::utils::Static;
use crate::Library;
@@ -152,6 +154,23 @@ impl Scope {
/// Bind a value to a name.
#[track_caller]
pub fn define(&mut self, name: impl Into<EcoString>, 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<EcoString>,
+ value: impl IntoValue,
+ span: Span,
+ ) {
let name = name.into();
#[cfg(debug_assertions)]
@@ -159,8 +178,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.
@@ -191,19 +226,6 @@ impl Scope {
self.define(module.name().clone(), module);
}
- /// Define a captured, immutable binding.
- pub fn define_captured(
- &mut self,
- var: impl Into<EcoString>,
- 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)
@@ -217,14 +239,19 @@ impl Scope {
.map(|res| res.map_err(HintedString::from))
}
+ /// Get the span of a definition.
+ pub fn get_span(&self, var: &str) -> Option<Span> {
+ Some(self.map.get(var)?.span)
+ }
+
/// Get the category of a definition.
pub fn get_category(&self, var: &str) -> Option<Category> {
self.map.get(var)?.category
}
/// Iterate over all definitions.
- pub fn iter(&self) -> impl Iterator<Item = (&EcoString, &Value)> {
- self.map.iter().map(|(k, v)| (k, v.read()))
+ pub fn iter(&self) -> impl Iterator<Item = (&EcoString, &Value, Span)> {
+ self.map.iter().map(|(k, v)| (k, v.read(), v.span))
}
}
@@ -264,6 +291,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<Category>,
}
@@ -288,8 +317,8 @@ pub enum Capturer {
impl Slot {
/// Create a new slot.
- fn new(value: Value, kind: Kind, category: Option<Category>) -> Self {
- Self { value, kind, category }
+ fn new(value: Value, span: Span, kind: Kind, category: Option<Category>) -> Self {
+ Self { value, span, kind, category }
}
/// Read the value.
diff --git a/docs/src/lib.rs b/docs/src/lib.rs
index 7575817a..030cb0a9 100644
--- a/docs/src/lib.rs
+++ b/docs/src/lib.rs
@@ -44,8 +44,8 @@ static GROUPS: Lazy<Vec<GroupData>> = 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();
}
}
@@ -249,7 +249,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;
}
@@ -463,7 +463,7 @@ fn casts(
fn scope_models(resolver: &dyn Resolver, name: &str, scope: &Scope) -> Vec<FuncModel> {
scope
.iter()
- .filter_map(|(_, value)| {
+ .filter_map(|(_, value, _)| {
let Value::Func(func) = value else { return None };
Some(func_model(resolver, func, &[name], true))
})
@@ -649,7 +649,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() {