mirror of
https://github.com/oxalica/nil.git
synced 2024-11-26 02:48:06 +03:00
Impl more exprs and "with" scopes
This commit is contained in:
parent
9ce13ad940
commit
916017b9ec
114
src/def/lower.rs
114
src/def/lower.rs
@ -1,12 +1,13 @@
|
||||
use super::{
|
||||
AstPtr, Expr, ExprId, Literal, Module, ModuleSourceMap, NameDef, NameDefId, Pat, Path,
|
||||
PathAnchor,
|
||||
AstPtr, Attrpath, Expr, ExprId, Literal, Module, ModuleSourceMap, NameDef, NameDefId, Pat,
|
||||
Path, PathAnchor,
|
||||
};
|
||||
use crate::base::{FileId, InFile};
|
||||
use la_arena::Arena;
|
||||
use rowan::ast::AstNode;
|
||||
use smol_str::SmolStr;
|
||||
use syntax::ast::{self, LiteralKind};
|
||||
use std::str;
|
||||
use syntax::ast::{self, HasStringParts, LiteralKind};
|
||||
|
||||
pub(super) fn lower(root: InFile<ast::SourceFile>) -> (Module, ModuleSourceMap) {
|
||||
let mut ctx = LowerCtx {
|
||||
@ -39,17 +40,21 @@ impl LowerCtx {
|
||||
id
|
||||
}
|
||||
|
||||
fn alloc_name_def(&mut self, node: ast::Name) -> NameDefId {
|
||||
let name = node
|
||||
.token()
|
||||
.map_or_else(Default::default, |tok| tok.text().into());
|
||||
fn alloc_name_def(&mut self, name: SmolStr, ptr: AstPtr) -> NameDefId {
|
||||
let id = self.module.name_defs.alloc(NameDef { name });
|
||||
let ptr = AstPtr::new(node.syntax());
|
||||
self.source_map.name_def_map.insert(ptr.clone(), id);
|
||||
self.source_map.name_def_map_rev.insert(id, ptr);
|
||||
id
|
||||
}
|
||||
|
||||
fn lower_name(&mut self, node: ast::Name) -> NameDefId {
|
||||
let name = node
|
||||
.token()
|
||||
.map_or_else(Default::default, |tok| tok.text().into());
|
||||
let ptr = AstPtr::new(node.syntax());
|
||||
self.alloc_name_def(name, ptr)
|
||||
}
|
||||
|
||||
fn lower_expr_opt(&mut self, expr: Option<ast::Expr>) -> ExprId {
|
||||
if let Some(expr) = expr {
|
||||
return self.lower_expr(expr);
|
||||
@ -79,12 +84,12 @@ impl LowerCtx {
|
||||
ast::Expr::Paren(e) => self.lower_expr_opt(e.expr()),
|
||||
ast::Expr::Lambda(e) => {
|
||||
let (param, pat) = e.param().map_or((None, None), |param| {
|
||||
let name = param.name().map(|n| self.alloc_name_def(n));
|
||||
let name = param.name().map(|n| self.lower_name(n));
|
||||
let pat = param.pat().map(|pat| {
|
||||
let fields = pat
|
||||
.fields()
|
||||
.map(|field| {
|
||||
let field_name = field.name().map(|n| self.alloc_name_def(n));
|
||||
let field_name = field.name().map(|n| self.lower_name(n));
|
||||
let default_expr = field.default_expr().map(|e| self.lower_expr(e));
|
||||
(field_name, default_expr)
|
||||
})
|
||||
@ -97,18 +102,52 @@ impl LowerCtx {
|
||||
let body = self.lower_expr_opt(e.body());
|
||||
self.alloc_expr(Expr::Lambda(param, pat, body), ptr)
|
||||
}
|
||||
ast::Expr::Assert(_) => todo!(),
|
||||
ast::Expr::Assert(e) => {
|
||||
let cond = self.lower_expr_opt(e.condition());
|
||||
let body = self.lower_expr_opt(e.body());
|
||||
self.alloc_expr(Expr::Assert(cond, body), ptr)
|
||||
}
|
||||
ast::Expr::IfThenElse(e) => {
|
||||
let cond = self.lower_expr_opt(e.condition());
|
||||
let then_body = self.lower_expr_opt(e.then_body());
|
||||
let else_body = self.lower_expr_opt(e.else_body());
|
||||
self.alloc_expr(Expr::IfThenElse(cond, then_body, else_body), ptr)
|
||||
}
|
||||
ast::Expr::With(e) => {
|
||||
let env = self.lower_expr_opt(e.environment());
|
||||
let body = self.lower_expr_opt(e.body());
|
||||
self.alloc_expr(Expr::With(env, body), ptr)
|
||||
}
|
||||
ast::Expr::BinaryOp(e) => {
|
||||
let lhs = self.lower_expr_opt(e.lhs());
|
||||
let op = e.op_kind();
|
||||
let rhs = self.lower_expr_opt(e.rhs());
|
||||
self.alloc_expr(Expr::Binary(op, lhs, rhs), ptr)
|
||||
}
|
||||
ast::Expr::UnaryOp(e) => {
|
||||
let op = e.op_kind();
|
||||
let arg = self.lower_expr_opt(e.arg());
|
||||
self.alloc_expr(Expr::Unary(op, arg), ptr)
|
||||
}
|
||||
ast::Expr::HasAttr(e) => {
|
||||
let set = self.lower_expr_opt(e.set());
|
||||
let attrpath = self.lower_attrpath_opt(e.attrpath());
|
||||
self.alloc_expr(Expr::HasAttr(set, attrpath), ptr)
|
||||
}
|
||||
ast::Expr::Select(e) => {
|
||||
let set = self.lower_expr_opt(e.set());
|
||||
let attrpath = self.lower_attrpath_opt(e.attrpath());
|
||||
let default_expr = e.default_expr().map(|e| self.lower_expr(e));
|
||||
self.alloc_expr(Expr::Select(set, attrpath, default_expr), ptr)
|
||||
}
|
||||
ast::Expr::String(s) => self.lower_string(&s),
|
||||
ast::Expr::IndentString(s) => self.lower_string(&s),
|
||||
ast::Expr::List(e) => {
|
||||
let elements = e.elements().map(|e| self.lower_expr(e)).collect();
|
||||
self.alloc_expr(Expr::List(elements), ptr)
|
||||
}
|
||||
ast::Expr::AttrSet(_) => todo!(),
|
||||
ast::Expr::BinaryOp(_) => todo!(),
|
||||
ast::Expr::HasAttr(_) => todo!(),
|
||||
ast::Expr::IfThenElse(_) => todo!(),
|
||||
ast::Expr::IndentString(_) => todo!(),
|
||||
ast::Expr::LetIn(_) => todo!(),
|
||||
ast::Expr::List(_) => todo!(),
|
||||
ast::Expr::Select(_) => todo!(),
|
||||
ast::Expr::String(_) => todo!(),
|
||||
ast::Expr::UnaryOp(_) => todo!(),
|
||||
ast::Expr::With(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -172,4 +211,39 @@ impl LowerCtx {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn lower_attrpath_opt(&mut self, attrpath: Option<ast::Attrpath>) -> Attrpath {
|
||||
attrpath
|
||||
.into_iter()
|
||||
.flat_map(|attrpath| attrpath.attrs())
|
||||
.map(|attr| match attr {
|
||||
ast::Attr::Dynamic(d) => self.lower_expr_opt(d.expr()),
|
||||
ast::Attr::Name(n) => {
|
||||
let name = n
|
||||
.token()
|
||||
.map_or_else(Default::default, |tok| tok.text().into());
|
||||
let ptr = AstPtr::new(n.syntax());
|
||||
self.alloc_expr(Expr::Literal(Literal::String(name)), ptr)
|
||||
}
|
||||
ast::Attr::String(s) => self.lower_string(&s),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn lower_string(&mut self, n: &impl HasStringParts) -> ExprId {
|
||||
let ptr = AstPtr::new(n.syntax());
|
||||
// Here we don't need to special case literal strings.
|
||||
// They would simply become `Expr::StringInterpolation([])`.
|
||||
let parts = n
|
||||
.string_parts()
|
||||
.filter_map(|part| {
|
||||
match part {
|
||||
ast::StringPart::Dynamic(d) => Some(self.lower_expr_opt(d.expr())),
|
||||
// Currently we don't encode literal fragments.
|
||||
ast::StringPart::Fragment(_) | ast::StringPart::Escape(_) => None,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
self.alloc_expr(Expr::StringInterpolation(parts), ptr)
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,8 @@ use ordered_float::OrderedFloat;
|
||||
use smol_str::SmolStr;
|
||||
use std::{collections::HashMap, ops, sync::Arc};
|
||||
|
||||
pub use self::scope::{ModuleScopes, ScopeData, ScopeId};
|
||||
pub use self::scope::{ModuleScopes, ResolveResult, ScopeData, ScopeId};
|
||||
pub use syntax::ast::{BinaryOpKind as BinaryOp, UnaryOpKind as UnaryOp};
|
||||
|
||||
#[salsa::query_group(DefDatabaseStorage)]
|
||||
pub trait DefDatabase: SourceDatabase {
|
||||
@ -24,7 +25,7 @@ pub trait DefDatabase: SourceDatabase {
|
||||
fn scopes(&self, file_id: FileId) -> Arc<ModuleScopes>;
|
||||
|
||||
#[salsa::invoke(ModuleScopes::resolve_name_query)]
|
||||
fn resolve_name(&self, file_id: FileId, expr_id: ExprId) -> Option<NameDefId>;
|
||||
fn resolve_name(&self, file_id: FileId, expr_id: ExprId) -> Option<ResolveResult>;
|
||||
}
|
||||
|
||||
fn module_with_source_map(
|
||||
@ -97,11 +98,59 @@ pub enum Expr {
|
||||
Missing,
|
||||
Reference(SmolStr),
|
||||
Literal(Literal),
|
||||
Apply(ExprId, ExprId),
|
||||
Lambda(Option<NameDefId>, Option<Pat>, ExprId),
|
||||
With(ExprId, ExprId),
|
||||
Assert(ExprId, ExprId),
|
||||
IfThenElse(ExprId, ExprId, ExprId),
|
||||
Binary(Option<BinaryOp>, ExprId, ExprId),
|
||||
Apply(ExprId, ExprId),
|
||||
Unary(Option<UnaryOp>, ExprId),
|
||||
HasAttr(ExprId, Attrpath),
|
||||
Select(ExprId, Attrpath, Option<ExprId>),
|
||||
StringInterpolation(Box<[ExprId]>),
|
||||
List(Box<[ExprId]>),
|
||||
// TODO
|
||||
}
|
||||
|
||||
impl Expr {
|
||||
pub(crate) fn walk_child_exprs(&self, mut f: impl FnMut(ExprId)) {
|
||||
match self {
|
||||
Self::Missing | Self::Reference(_) | Self::Literal(_) => {}
|
||||
Self::Lambda(_, pat, body) => {
|
||||
if let Some(p) = pat {
|
||||
p.fields
|
||||
.iter()
|
||||
.filter_map(|&(_, default_expr)| default_expr)
|
||||
.for_each(&mut f);
|
||||
}
|
||||
f(*body);
|
||||
}
|
||||
Self::Unary(_, a) => f(*a),
|
||||
Self::Assert(a, b) | Self::With(a, b) | Self::Binary(_, a, b) | Self::Apply(a, b) => {
|
||||
f(*a);
|
||||
f(*b);
|
||||
}
|
||||
Self::IfThenElse(a, b, c) => {
|
||||
f(*a);
|
||||
f(*b);
|
||||
f(*c);
|
||||
}
|
||||
Self::HasAttr(set, path) => {
|
||||
f(*set);
|
||||
path.iter().copied().for_each(f);
|
||||
}
|
||||
Self::Select(set, path, default_expr) => {
|
||||
f(*set);
|
||||
path.iter().copied().for_each(&mut f);
|
||||
if let &Some(e) = default_expr {
|
||||
f(e);
|
||||
}
|
||||
}
|
||||
Self::StringInterpolation(xs) | Self::List(xs) => xs.iter().copied().for_each(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct NameDef {
|
||||
pub name: SmolStr,
|
||||
@ -142,3 +191,5 @@ pub struct Pat {
|
||||
pub fields: Box<[(Option<NameDefId>, Option<ExprId>)]>,
|
||||
pub ellipsis: bool,
|
||||
}
|
||||
|
||||
pub type Attrpath = Box<[ExprId]>;
|
||||
|
231
src/def/scope.rs
231
src/def/scope.rs
@ -12,12 +12,6 @@ pub struct ModuleScopes {
|
||||
|
||||
pub type ScopeId = Idx<ScopeData>;
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ScopeData {
|
||||
parent: Option<ScopeId>,
|
||||
name_defs: HashMap<SmolStr, NameDefId>,
|
||||
}
|
||||
|
||||
impl ops::Index<ScopeId> for ModuleScopes {
|
||||
type Output = ScopeData;
|
||||
fn index(&self, index: ScopeId) -> &Self::Output {
|
||||
@ -28,9 +22,12 @@ impl ops::Index<ScopeId> for ModuleScopes {
|
||||
impl ModuleScopes {
|
||||
pub(crate) fn module_scopes_query(db: &dyn DefDatabase, file_id: FileId) -> Arc<Self> {
|
||||
let module = db.module(file_id);
|
||||
let mut this = ModuleScopes::default();
|
||||
let root = this.new_root_scope();
|
||||
this.traverse_expr(&*module, root, module.entry_expr);
|
||||
let mut this = Self::default();
|
||||
let root_scope = this.scopes.alloc(ScopeData {
|
||||
parent: None,
|
||||
kind: ScopeKind::NameDefs(Default::default()),
|
||||
});
|
||||
this.traverse_expr(&*module, module.entry_expr, root_scope);
|
||||
Arc::new(this)
|
||||
}
|
||||
|
||||
@ -38,13 +35,13 @@ impl ModuleScopes {
|
||||
db: &dyn DefDatabase,
|
||||
file_id: FileId,
|
||||
expr_id: ExprId,
|
||||
) -> Option<NameDefId> {
|
||||
) -> Option<ResolveResult> {
|
||||
let module = db.module(file_id);
|
||||
let name = match &module[expr_id] {
|
||||
Expr::Reference(name) => name,
|
||||
_ => return None,
|
||||
};
|
||||
db.scopes(file_id).resolve_anme(expr_id, name)
|
||||
db.scopes(file_id).resolve_name(expr_id, name)
|
||||
}
|
||||
|
||||
pub fn scope_by_expr(&self, expr_id: ExprId) -> Option<ScopeId> {
|
||||
@ -55,66 +52,184 @@ impl ModuleScopes {
|
||||
iter::successors(Some(scope_id), |&i| self[i].parent).map(|i| &self[i])
|
||||
}
|
||||
|
||||
pub fn resolve_anme(&self, expr_id: ExprId, name: &SmolStr) -> Option<NameDefId> {
|
||||
pub fn resolve_name(&self, expr_id: ExprId, name: &SmolStr) -> Option<ResolveResult> {
|
||||
let mut inner_env = None;
|
||||
self.ancestors(self.scope_by_expr(expr_id)?)
|
||||
.find_map(|scope| scope.resolve(name))
|
||||
}
|
||||
|
||||
fn new_root_scope(&mut self) -> ScopeId {
|
||||
self.scopes.alloc(ScopeData {
|
||||
parent: None,
|
||||
name_defs: HashMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn new_scope(&mut self, parent: ScopeId, name_defs: HashMap<SmolStr, NameDefId>) -> ScopeId {
|
||||
self.scopes.alloc(ScopeData {
|
||||
parent: Some(parent),
|
||||
name_defs,
|
||||
})
|
||||
}
|
||||
|
||||
fn traverse_expr(&mut self, module: &Module, scope_id: ScopeId, expr_id: ExprId) {
|
||||
self.scope_by_expr.insert(expr_id, scope_id);
|
||||
match module[expr_id] {
|
||||
Expr::Missing | Expr::Reference(_) | Expr::Literal(_) => {}
|
||||
Expr::Apply(func, arg) => {
|
||||
self.traverse_expr(module, scope_id, func);
|
||||
self.traverse_expr(module, scope_id, arg);
|
||||
}
|
||||
Expr::Lambda(param_opt, ref pat_opt, body) => {
|
||||
let mut name_defs = HashMap::default();
|
||||
if let Some(name_id) = param_opt {
|
||||
name_defs.insert(module[name_id].name.clone(), name_id);
|
||||
.find_map(|data| match &data.kind {
|
||||
ScopeKind::NameDefs(defs) => defs.get(name).copied(),
|
||||
ScopeKind::WithEnv(env) => {
|
||||
inner_env = inner_env.or(Some(*env));
|
||||
None
|
||||
}
|
||||
if let Some(pat) = pat_opt {
|
||||
})
|
||||
.map(ResolveResult::NameDef)
|
||||
.or_else(|| inner_env.map(ResolveResult::WithEnv))
|
||||
}
|
||||
|
||||
fn traverse_expr(&mut self, module: &Module, expr: ExprId, scope: ScopeId) {
|
||||
self.scope_by_expr.insert(expr, scope);
|
||||
|
||||
match &module[expr] {
|
||||
Expr::Lambda(param, pat, body) => {
|
||||
let mut defs = HashMap::default();
|
||||
if let &Some(name_id) = param {
|
||||
defs.insert(module[name_id].name.clone(), name_id);
|
||||
}
|
||||
if let Some(pat) = pat {
|
||||
for name_id in pat.fields.iter().filter_map(|(opt_id, _)| *opt_id) {
|
||||
name_defs.insert(module[name_id].name.clone(), name_id);
|
||||
defs.insert(module[name_id].name.clone(), name_id);
|
||||
}
|
||||
}
|
||||
let scope_id = self.new_scope(scope_id, name_defs);
|
||||
|
||||
if let Some(pat) = pat_opt {
|
||||
for expr_id in pat
|
||||
.fields
|
||||
.iter()
|
||||
.filter_map(|(_, default_expr_id)| *default_expr_id)
|
||||
{
|
||||
self.traverse_expr(module, scope_id, expr_id);
|
||||
let scope = if !defs.is_empty() {
|
||||
self.scopes.alloc(ScopeData {
|
||||
parent: Some(scope),
|
||||
kind: ScopeKind::NameDefs(defs),
|
||||
})
|
||||
} else {
|
||||
scope
|
||||
};
|
||||
|
||||
if let Some(pat) = pat {
|
||||
for default_expr in pat.fields.iter().filter_map(|(_, e)| *e) {
|
||||
self.traverse_expr(module, default_expr, scope);
|
||||
}
|
||||
}
|
||||
self.traverse_expr(module, scope_id, body);
|
||||
self.traverse_expr(module, *body, scope);
|
||||
}
|
||||
Expr::With(env, body) => {
|
||||
self.traverse_expr(module, *env, scope);
|
||||
let scope = self.scopes.alloc(ScopeData {
|
||||
parent: Some(scope),
|
||||
kind: ScopeKind::WithEnv(*env),
|
||||
});
|
||||
self.traverse_expr(module, *body, scope);
|
||||
}
|
||||
e => e.walk_child_exprs(|e| self.traverse_expr(module, e, scope)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ScopeData {
|
||||
pub fn name_defs(&self) -> impl Iterator<Item = NameDefId> + '_ {
|
||||
self.name_defs.values().copied()
|
||||
}
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ResolveResult {
|
||||
NameDef(NameDefId),
|
||||
WithEnv(ExprId),
|
||||
}
|
||||
|
||||
pub fn resolve(&self, name: &SmolStr) -> Option<NameDefId> {
|
||||
self.name_defs.get(name).copied()
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ScopeData {
|
||||
parent: Option<ScopeId>,
|
||||
kind: ScopeKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum ScopeKind {
|
||||
NameDefs(HashMap<SmolStr, NameDefId>),
|
||||
WithEnv(ExprId),
|
||||
}
|
||||
|
||||
impl ScopeData {
|
||||
pub fn name_defs(&self) -> impl Iterator<Item = (&SmolStr, NameDefId)> + '_ {
|
||||
match &self.kind {
|
||||
ScopeKind::NameDefs(defs) => Some(defs),
|
||||
ScopeKind::WithEnv(_) => None,
|
||||
}
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(|(name, &def)| (name, def))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
base::SourceDatabase,
|
||||
def::{AstPtr, DefDatabase, ResolveResult},
|
||||
tests::TestDB,
|
||||
};
|
||||
use rowan::ast::AstNode;
|
||||
use syntax::ast;
|
||||
|
||||
#[track_caller]
|
||||
fn check_scopes(src: &str, expect: &str) {
|
||||
let (db, file_id, pos) = TestDB::from_file_with_pos(src);
|
||||
let ptr = AstPtr::new(db.node_at::<ast::Expr>(file_id, pos).syntax());
|
||||
|
||||
let source_map = db.source_map(file_id);
|
||||
let expr_id = source_map.expr_map[&ptr];
|
||||
let scopes = db.scopes(file_id);
|
||||
|
||||
// "innermost var abc | middle var | outmost var"
|
||||
let scope_id = scopes.scope_by_expr(expr_id).expect("No scope data");
|
||||
let scope_defs = scopes
|
||||
.ancestors(scope_id)
|
||||
.filter_map(|scope| {
|
||||
let mut names = scope
|
||||
.name_defs()
|
||||
.map(|(name, _)| &**name)
|
||||
.collect::<Vec<_>>();
|
||||
if names.is_empty() {
|
||||
return None;
|
||||
}
|
||||
names.sort();
|
||||
Some(names.join(" "))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let got = scope_defs.join(" | ");
|
||||
assert_eq!(got, expect);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn check_resolve(src: &str, expect: Option<u32>) {
|
||||
let (db, file_id, pos) = TestDB::from_file_with_pos(src);
|
||||
let ptr = AstPtr::new(db.node_at::<ast::Expr>(file_id, pos).syntax());
|
||||
let parse = db.parse(file_id).value;
|
||||
let source_map = db.source_map(file_id);
|
||||
let expr_id = source_map.expr_map[&ptr];
|
||||
let got = db
|
||||
.resolve_name(file_id, expr_id)
|
||||
.map(|ret| match ret {
|
||||
ResolveResult::NameDef(def) => source_map
|
||||
.name_def_node(def)
|
||||
.unwrap()
|
||||
.to_node(&parse.syntax_node()),
|
||||
ResolveResult::WithEnv(env) => source_map
|
||||
.expr_node(env)
|
||||
.unwrap()
|
||||
.to_node(&parse.syntax_node()),
|
||||
})
|
||||
.map(|n| u32::from(n.text_range().start()));
|
||||
assert_eq!(got, expect);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn top_level() {
|
||||
check_scopes(r"$0a", "");
|
||||
|
||||
check_resolve(r"$0a", None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lambda() {
|
||||
check_scopes(r"(a: b: (c: 0) $0a (d: 0)) (e: 0)", "b | a");
|
||||
check_scopes(r"{ a, b ? c, ... }@d: $0x (y: x)", "a b d");
|
||||
check_scopes(r"a: { a, b ? $0c, ... }@d: y: a", "a b d | a");
|
||||
|
||||
check_resolve(r"a: b: b$0", Some(3));
|
||||
check_resolve(r"a: { a, b ? $0d, ... }@d: y: a", Some(21));
|
||||
check_resolve(r"a: { a, b ? $0y, ... }@d: y: a", None);
|
||||
check_resolve(r"a: { a, b ? $0a, ... }@d: y: a", Some(5));
|
||||
check_resolve(r"a: { x, b ? $0a, ... }@d: y: a", Some(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with() {
|
||||
check_scopes(r"a: with b; c: $0a", "c | a");
|
||||
|
||||
check_resolve(r"a: with b; c: $0a", Some(0));
|
||||
check_resolve(r"a: with b; c: $0c", Some(11));
|
||||
check_resolve(r"a: with b; c: $0x", Some(8));
|
||||
check_resolve(r"x: with a; with b; $0x", Some(0));
|
||||
check_resolve(r"x: with a; with b; $0y", Some(16));
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ use crate::tests::TestDB;
|
||||
use expect_test::expect;
|
||||
|
||||
mod lower;
|
||||
mod scope;
|
||||
|
||||
#[test]
|
||||
fn source_map() {
|
||||
|
@ -1,49 +0,0 @@
|
||||
use super::DefDatabase;
|
||||
use crate::def::AstPtr;
|
||||
use crate::tests::TestDB;
|
||||
use expect_test::{expect, Expect};
|
||||
use rowan::ast::AstNode;
|
||||
use syntax::ast;
|
||||
|
||||
#[track_caller]
|
||||
fn check_scopes(src: &str, expect: Expect) {
|
||||
let (db, file_id, pos) = TestDB::from_file_with_pos(src);
|
||||
let ptr = AstPtr::new(db.node_at::<ast::Expr>(file_id, pos).syntax());
|
||||
|
||||
let module = db.module(file_id);
|
||||
let source_map = db.source_map(file_id);
|
||||
let expr_id = source_map.expr_map[&ptr];
|
||||
let scopes = db.scopes(file_id);
|
||||
let scope_id = scopes.scope_by_expr(expr_id).expect("No scope data");
|
||||
|
||||
// "innermost var abc | middle var | outmost var"
|
||||
let scope_defs = scopes
|
||||
.ancestors(scope_id)
|
||||
.filter_map(|scope| {
|
||||
let mut names = scope
|
||||
.name_defs()
|
||||
.map(|def_id| module[def_id].name.clone())
|
||||
.collect::<Vec<_>>();
|
||||
if names.is_empty() {
|
||||
return None;
|
||||
}
|
||||
names.sort();
|
||||
Some(names.join(" "))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let got = scope_defs.join(" | ");
|
||||
|
||||
expect.assert_eq(&got);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn top_level() {
|
||||
check_scopes(r"42$0", expect![""]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lambda() {
|
||||
check_scopes(r"(a: b: (c: 0) 42$0 (d: 0)) (e: 0)", expect!["b | a"]);
|
||||
check_scopes(r"{ a, b ? c, ... }@d: $0x (y: x)", expect!["a b d"]);
|
||||
check_scopes(r"a: { a, b ? $0c, ... }@d: y: a", expect!["a b d | a"]);
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
use super::NavigationTarget;
|
||||
use crate::def::{AstPtr, DefDatabase};
|
||||
use crate::def::{AstPtr, DefDatabase, ResolveResult};
|
||||
use crate::FileId;
|
||||
use rowan::ast::AstNode;
|
||||
use rowan::TextSize;
|
||||
@ -19,21 +19,36 @@ pub(crate) fn goto_definition(
|
||||
|
||||
let source_map = db.source_map(file_id);
|
||||
let expr_id = source_map.node_expr(AstPtr::new(node.syntax()))?;
|
||||
let def_id = db.resolve_name(file_id, expr_id)?;
|
||||
let def_node = source_map
|
||||
.name_def_node(def_id)?
|
||||
.to_node(&parse.syntax_node());
|
||||
let full_node = def_node.ancestors().find(|n| {
|
||||
matches!(
|
||||
n.kind(),
|
||||
SyntaxKind::LAMBDA | SyntaxKind::ATTR_PATH_VALUE | SyntaxKind::INHERIT
|
||||
)
|
||||
})?;
|
||||
let (focus_range, full_range) = match db.resolve_name(file_id, expr_id)? {
|
||||
ResolveResult::NameDef(def) => {
|
||||
let name_node = source_map.name_def_node(def)?.to_node(&parse.syntax_node());
|
||||
let full_node = name_node.ancestors().find(|n| {
|
||||
matches!(
|
||||
n.kind(),
|
||||
SyntaxKind::LAMBDA | SyntaxKind::ATTR_PATH_VALUE | SyntaxKind::INHERIT
|
||||
)
|
||||
})?;
|
||||
(name_node.text_range(), full_node.text_range())
|
||||
}
|
||||
ResolveResult::WithEnv(env) => {
|
||||
// with expr; body
|
||||
// ^--^ focus
|
||||
// ^--------^ full
|
||||
let env_node = source_map.expr_node(env)?.to_node(&parse.syntax_node());
|
||||
let with_node = ast::With::cast(env_node.parent()?)?;
|
||||
let with_token_range = with_node.with_token()?.text_range();
|
||||
let with_header_end = with_node
|
||||
.semicolon_token()
|
||||
.map_or_else(|| env_node.text_range(), |tok| tok.text_range());
|
||||
let with_header = with_token_range.cover(with_header_end);
|
||||
(with_token_range, with_header)
|
||||
}
|
||||
};
|
||||
|
||||
Some(NavigationTarget {
|
||||
file_id,
|
||||
full_range: full_node.text_range(),
|
||||
focus_range: def_node.text_range(),
|
||||
focus_range,
|
||||
full_range,
|
||||
})
|
||||
}
|
||||
|
||||
@ -74,4 +89,9 @@ mod tests {
|
||||
check("a: ({ x ? $0a }@a: a) 1", expect!["{ x ? a }@<a>: a"]);
|
||||
check("a: ({ x ? $0x }@a: a) 1", expect!["{ <x> ? x }@a: a"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_env() {
|
||||
check("with a; $0b", expect!["<with> a;"]);
|
||||
}
|
||||
}
|
||||
|
@ -90,6 +90,7 @@ trait NodeWrapper {
|
||||
macro_rules! enums {
|
||||
($($name:ident { $($variant:ident,)* },)*) => {
|
||||
$(
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum $name {
|
||||
$($variant($variant),)*
|
||||
}
|
||||
@ -309,6 +310,7 @@ asts! {
|
||||
},
|
||||
HAS_ATTR = HasAttr {
|
||||
set: Expr,
|
||||
question_token: T![?],
|
||||
attrpath: Attrpath,
|
||||
},
|
||||
IF_THEN_ELSE = IfThenElse {
|
||||
@ -336,6 +338,7 @@ asts! {
|
||||
LET_IN = LetIn [HasBindings] {
|
||||
let_token: T![let],
|
||||
in_token: T![in],
|
||||
body: Expr,
|
||||
},
|
||||
LIST = List {
|
||||
l_brack_token: T!['['],
|
||||
@ -422,7 +425,9 @@ asts! {
|
||||
}
|
||||
},
|
||||
WITH = With {
|
||||
with_token: T![with],
|
||||
environment: Expr,
|
||||
semicolon_token: T![;],
|
||||
body[1]: Expr,
|
||||
},
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user