Spans for cross-file go-to-definition (#4539)

This commit is contained in:
Laurenz 2024-07-11 16:24:28 +02:00 committed by GitHub
parent 36042ff222
commit be516867c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 108 additions and 60 deletions

View File

@ -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)

View File

@ -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);
}

View File

@ -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()),
));

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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 {

View File

@ -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);

View File

@ -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)) => {

View File

@ -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.

View File

@ -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 {

View File

@ -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)
}

View File

@ -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.

View File

@ -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() {