use super::{LineIndex, OpenDocument, completion_programs, file_uri_path};
use lsp_types::{Hover, HoverContents, Location, MarkupContent, MarkupKind, Uri};
use std::collections::{BTreeMap, BTreeSet};
use std::fs;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use tess::ast::{
BinaryOp, Cardinality, Declaration, DeriveDecl, Effect, Expr, ExprKind, FragmentDecl,
FragmentRef, InvariantAssertion, Parameter, Program, RuleDecl, TypeRef, UnaryOp,
};
use tess::compiler::ValueType;
use tess::source::Spanned;
use tess::{
CompiledProgram, SourceFile, Span, compile_project_with_dependency_graph_and_overrides, parse,
};
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
enum SymbolKey {
Type(String),
Field(String, String),
Variant(String, String),
Derive(String),
Decision(String),
Rule(String),
Fragment(String),
Fixture(String),
Case(String),
Invariant(String),
Local {
uri: String,
owner: usize,
name: String,
},
}
#[derive(Clone, Debug)]
struct NavigationSpan {
uri: String,
span: Span,
}
#[derive(Clone, Debug)]
struct SymbolDefinition {
name: String,
kind: &'static str,
detail: String,
location: NavigationSpan,
}
#[derive(Clone, Debug)]
struct SymbolOccurrence {
key: SymbolKey,
location: NavigationSpan,
}
#[derive(Clone, Debug)]
struct SourceDocument {
uri: Uri,
text: String,
}
#[derive(Clone, Debug)]
struct CallableInfo {
parameters: Vec<TypeRef>,
return_type: Option<TypeRef>,
}
#[derive(Clone, Debug, Default)]
struct EntityInfo {
fields: BTreeMap<String, TypeRef>,
}
#[derive(Debug, Default)]
pub(super) struct NavigationIndex {
definitions: BTreeMap<SymbolKey, SymbolDefinition>,
occurrences: Vec<SymbolOccurrence>,
documents: BTreeMap<String, SourceDocument>,
entities: BTreeMap<String, EntityInfo>,
enums: BTreeSet<String>,
variant_owners: BTreeMap<String, Vec<String>>,
derives: BTreeMap<String, CallableInfo>,
decisions: BTreeMap<String, CallableInfo>,
active_fragment: Option<String>,
}
impl NavigationIndex {
pub(super) fn hover(&self, uri: &Uri, cursor: usize) -> Option<Hover> {
let (occurrence, definition) = self.target_at(uri, cursor)?;
let document = self.documents.get(&occurrence.location.uri)?;
Some(Hover {
contents: HoverContents::Markup(MarkupContent {
kind: MarkupKind::Markdown,
value: format!(
"**{} `{}`**\n\n```tess\n{}\n```",
definition.kind, definition.name, definition.detail
),
}),
range: Some(LineIndex::new(&document.text).range(occurrence.location.span)),
})
}
pub(super) fn definition(&self, uri: &Uri, cursor: usize) -> Option<Location> {
let (_, definition) = self.target_at(uri, cursor)?;
let document = self.documents.get(&definition.location.uri)?;
Some(Location {
uri: document.uri.clone(),
range: LineIndex::new(&document.text).range(definition.location.span),
})
}
pub(super) fn references(
&self,
uri: &Uri,
cursor: usize,
include_declaration: bool,
) -> Vec<Location> {
let Some((target, definition)) = self.target_at(uri, cursor) else {
return Vec::new();
};
self.occurrences
.iter()
.filter(|occurrence| occurrence.key == target.key)
.filter(|occurrence| {
include_declaration
|| occurrence.location.uri != definition.location.uri
|| occurrence.location.span != definition.location.span
})
.filter_map(|occurrence| {
let document = self.documents.get(&occurrence.location.uri)?;
Some(Location {
uri: document.uri.clone(),
range: LineIndex::new(&document.text).range(occurrence.location.span),
})
})
.collect()
}
fn target_at(
&self,
uri: &Uri,
cursor: usize,
) -> Option<(&SymbolOccurrence, &SymbolDefinition)> {
self.occurrences
.iter()
.filter(|occurrence| occurrence.location.uri == uri.as_str())
.filter(|occurrence| contains_cursor(occurrence.location.span, cursor))
.filter_map(|occurrence| {
self.definitions
.get(&occurrence.key)
.map(|definition| (occurrence, definition))
})
.min_by(|(left, _), (right, _)| {
span_len(left.location.span)
.cmp(&span_len(right.location.span))
.then_with(|| left.key.cmp(&right.key))
})
}
fn collect_definitions(&mut self, program: &Program, mapper: &SpanMapper<'_>) {
for declaration in &program.declarations {
match declaration {
Declaration::Enum(value) => {
self.enums.insert(value.name.value.clone());
self.add_definition(
SymbolKey::Type(value.name.value.clone()),
value.name.value.clone(),
"enum",
format!(
"enum {} · {} variants",
value.name.value,
value.variants.len()
),
value.name.span,
mapper,
);
for variant in &value.variants {
self.variant_owners
.entry(variant.value.clone())
.or_default()
.push(value.name.value.clone());
self.add_definition(
SymbolKey::Variant(value.name.value.clone(), variant.value.clone()),
variant.value.clone(),
"enum variant",
format!("{}.{}", value.name.value, variant.value),
variant.span,
mapper,
);
}
}
Declaration::Entity(value) => {
let fields = value
.fields
.iter()
.map(|field| (field.name.value.clone(), field.ty.clone()))
.collect();
self.entities
.insert(value.name.value.clone(), EntityInfo { fields });
self.add_definition(
SymbolKey::Type(value.name.value.clone()),
value.name.value.clone(),
"record",
format!(
"record {} · {} fields",
value.name.value,
value.fields.len()
),
value.name.span,
mapper,
);
for field in &value.fields {
self.add_definition(
SymbolKey::Field(value.name.value.clone(), field.name.value.clone()),
field.name.value.clone(),
"field",
format!(
"{}: {}",
field.name.value,
super::field_detail(&value.name.value, field)
),
field.name.span,
mapper,
);
}
}
Declaration::Derive(value) => {
self.collect_derive_definition(value, None, mapper);
}
Declaration::Decision(value) => {
let info = CallableInfo {
parameters: parameter_types(&value.parameters),
return_type: Some(value.return_type.value.clone()),
};
self.decisions.insert(value.name.value.clone(), info);
self.add_definition(
SymbolKey::Decision(value.name.value.clone()),
value.name.value.clone(),
"dec",
format!(
"dec {}{}: {}{}",
value.name.value,
super::parameter_list(&value.parameters),
super::type_name(&value.return_type.value),
cardinality_suffix(value.cardinality)
),
value.name.span,
mapper,
);
}
Declaration::Fragment(value) => self.collect_fragment_definitions(value, mapper),
Declaration::Rule(value) => self.collect_rule_definition(value, None, mapper),
Declaration::Fixture(value) => self.add_definition(
SymbolKey::Fixture(value.name.value.clone()),
value.name.value.clone(),
"fixture",
format!(
"fixture {} {} · {} fields",
value.name.value,
value.entity.value,
value.fields.len()
),
value.name.span,
mapper,
),
Declaration::Case(value) => self.add_definition(
SymbolKey::Case(value.name.value.clone()),
value.name.value.clone(),
"test",
format!("test {}", value.name.value),
value.name.span,
mapper,
),
Declaration::Invariant(value) => self.add_definition(
SymbolKey::Invariant(value.name.value.clone()),
value.name.value.clone(),
"assert",
format!("assert {}", value.name.value),
value.name.span,
mapper,
),
}
}
for owners in self.variant_owners.values_mut() {
owners.sort();
owners.dedup();
}
}
fn collect_fragment_definitions(&mut self, fragment: &FragmentDecl, mapper: &SpanMapper<'_>) {
self.add_definition(
SymbolKey::Fragment(fragment.id.value.clone()),
fragment.id.value.clone(),
"fragment",
format!(
"{} · {} functions · {} rules",
super::fragment_detail(fragment),
fragment.derives.len(),
fragment.rules.len()
),
fragment.id.span,
mapper,
);
if fragment.locator.span != fragment.id.span {
self.add_occurrence(
SymbolKey::Fragment(fragment.id.value.clone()),
fragment.locator.span,
mapper,
);
}
for derive in &fragment.derives {
self.collect_derive_definition(derive, Some(&fragment.id.value), mapper);
}
for rule in &fragment.rules {
self.collect_rule_definition(rule, Some(&fragment.id.value), mapper);
}
}
fn collect_derive_definition(
&mut self,
derive: &DeriveDecl,
fragment_id: Option<&str>,
mapper: &SpanMapper<'_>,
) {
let id = fragment_id.map_or_else(
|| derive.name.value.clone(),
|fragment_id| format!("{fragment_id}.{}", derive.name.value),
);
let info = CallableInfo {
parameters: parameter_types(&derive.parameters),
return_type: super::resolved_derive_return_type(derive).cloned(),
};
self.derives.insert(id.clone(), info);
let signature = if derive.implicit_return_type {
format!(
"fn {}{}: {}",
derive.name.value,
super::parameter_list(&derive.parameters),
super::derive_return_detail(derive)
)
} else {
format!(
"fn {}{} {}",
derive.name.value,
super::parameter_list(&derive.parameters),
super::derive_return_detail(derive)
)
};
self.add_definition(
SymbolKey::Derive(id.clone()),
id,
"fn",
signature,
derive.name.span,
mapper,
);
}
fn collect_rule_definition(
&mut self,
rule: &RuleDecl,
fragment_id: Option<&str>,
mapper: &SpanMapper<'_>,
) {
let id = fragment_id.map_or_else(
|| rule.name.value.clone(),
|fragment_id| format!("{fragment_id}.{}", rule.name.value),
);
self.add_definition(
SymbolKey::Rule(id.clone()),
id,
"rule",
format!(
"rule {}{}",
rule.name.value,
super::parameter_list(&rule.parameters)
),
rule.name.span,
mapper,
);
}
fn collect_occurrences(
&mut self,
program: &Program,
semantic: Option<&CompiledProgram>,
mapper: &SpanMapper<'_>,
) {
for declaration in &program.declarations {
match declaration {
Declaration::Enum(_) | Declaration::Entity(_) => {}
Declaration::Derive(value) => {
self.index_derive(value, None, semantic, mapper);
}
Declaration::Decision(value) => {
self.add_type_ref(&value.return_type.value, value.return_type.span, mapper);
self.parameter_scope(&value.parameters, value.span, "parameter", mapper);
}
Declaration::Fragment(value) => {
for reference in &value.refs {
self.index_fragment_ref(reference, mapper);
}
for derive in &value.derives {
self.index_derive(derive, Some(&value.id.value), semantic, mapper);
}
for rule in &value.rules {
self.index_rule(rule, Some(&value.id.value), semantic, mapper);
}
}
Declaration::Rule(value) => self.index_rule(value, None, semantic, mapper),
Declaration::Fixture(value) => {
self.add_type_name(&value.entity.value, value.entity.span, mapper);
let scope = BTreeMap::new();
for field in &value.fields {
self.add_occurrence(
SymbolKey::Field(value.entity.value.clone(), field.name.value.clone()),
field.name.span,
mapper,
);
let expected = self
.field_type(&value.entity.value, &field.name.value)
.cloned();
self.index_expr(&field.value, expected.as_ref(), &scope, semantic, mapper);
}
}
Declaration::Case(value) => {
let mut scope = BTreeMap::new();
for binding in &value.bindings {
let key = self.add_local_definition(
value.span,
&binding.name.value,
&binding.entity.value,
"binding",
binding.name.span,
mapper,
);
self.add_type_name(&binding.entity.value, binding.entity.span, mapper);
if let Some(fixture) = &binding.fixture {
self.add_occurrence(
SymbolKey::Fixture(fixture.value.clone()),
fixture.span,
mapper,
);
}
scope.insert(
binding.name.value.clone(),
(key, TypeRef::Named(binding.entity.value.clone())),
);
for field in &binding.fields {
self.add_occurrence(
SymbolKey::Field(
binding.entity.value.clone(),
field.name.value.clone(),
),
field.name.span,
mapper,
);
let expected = self
.field_type(&binding.entity.value, &field.name.value)
.cloned();
self.index_expr(
&field.value,
expected.as_ref(),
&scope,
semantic,
mapper,
);
}
}
for expectation in &value.expectations {
self.index_decision_use(
&expectation.decision,
&expectation.arguments,
Some(&expectation.value),
&scope,
semantic,
mapper,
);
}
}
Declaration::Invariant(value) => {
let scope = self.parameter_scope(
&value.variables,
value.span,
"quantified parameter",
mapper,
);
match &value.assertion {
InvariantAssertion::Cardinality {
decision,
arguments,
..
} => self.index_decision_use(
decision, arguments, None, &scope, semantic, mapper,
),
InvariantAssertion::Implication {
condition,
expectation,
..
} => {
self.index_expr(
condition,
Some(&TypeRef::Bool),
&scope,
semantic,
mapper,
);
self.index_decision_use(
&expectation.decision,
&expectation.arguments,
Some(&expectation.value),
&scope,
semantic,
mapper,
);
}
}
}
}
}
}
fn index_derive(
&mut self,
derive: &DeriveDecl,
fragment_id: Option<&str>,
semantic: Option<&CompiledProgram>,
mapper: &SpanMapper<'_>,
) {
let previous_fragment =
std::mem::replace(&mut self.active_fragment, fragment_id.map(str::to_owned));
if !derive.implicit_return_type {
self.add_type_ref(&derive.return_type.value, derive.return_type.span, mapper);
}
let scope = self.parameter_scope(&derive.parameters, derive.span, "parameter", mapper);
self.index_expr(
&derive.expression,
super::resolved_derive_return_type(derive),
&scope,
semantic,
mapper,
);
for basis in &derive.basis {
self.index_fragment_ref(basis, mapper);
}
self.active_fragment = previous_fragment;
}
fn index_rule(
&mut self,
rule: &RuleDecl,
fragment_id: Option<&str>,
semantic: Option<&CompiledProgram>,
mapper: &SpanMapper<'_>,
) {
let previous_fragment =
std::mem::replace(&mut self.active_fragment, fragment_id.map(str::to_owned));
let scope = self.parameter_scope(&rule.parameters, rule.span, "parameter", mapper);
self.index_expr(
&rule.condition,
Some(&TypeRef::Bool),
&scope,
semantic,
mapper,
);
match &rule.effect {
Effect::Decide {
decision,
arguments,
value,
..
} => {
self.index_decision_use(decision, arguments, Some(value), &scope, semantic, mapper);
}
Effect::Override { rule, .. } => {
let target = if rule.value.contains('.') {
rule.value.clone()
} else if let Some(fragment_id) = fragment_id {
let local = format!("{fragment_id}.{}", rule.value);
if self
.definitions
.contains_key(&SymbolKey::Rule(local.clone()))
{
local
} else {
rule.value.clone()
}
} else {
rule.value.clone()
};
self.add_qualified_occurrence(
SymbolKey::Rule(target.clone()),
&target,
rule.span,
mapper,
);
}
Effect::Invalid { .. } => {}
}
for basis in &rule.basis {
self.index_fragment_ref(basis, mapper);
}
self.active_fragment = previous_fragment;
}
fn index_fragment_ref(&mut self, fragment: &FragmentRef, mapper: &SpanMapper<'_>) {
self.add_occurrence(
SymbolKey::Fragment(fragment.id.value.clone()),
fragment.id.span,
mapper,
);
}
fn index_decision_use(
&mut self,
decision: &Spanned<String>,
arguments: &[Expr],
value: Option<&Expr>,
scope: &Scope,
semantic: Option<&CompiledProgram>,
mapper: &SpanMapper<'_>,
) {
let decision_name = unique_leaf_key(&self.decisions, &decision.value).cloned();
if let Some(decision_name) = &decision_name {
self.add_qualified_occurrence(
SymbolKey::Decision(decision_name.clone()),
decision_name,
decision.span,
mapper,
);
}
let info = decision_name
.as_ref()
.and_then(|name| self.decisions.get(name))
.cloned();
for (index, argument) in arguments.iter().enumerate() {
let expected = info.as_ref().and_then(|info| info.parameters.get(index));
self.index_expr(argument, expected, scope, semantic, mapper);
}
if let Some(value) = value {
let expected = info.as_ref().and_then(|info| info.return_type.as_ref());
self.index_expr(value, expected, scope, semantic, mapper);
}
}
fn index_expr(
&mut self,
expression: &Expr,
expected: Option<&TypeRef>,
scope: &Scope,
semantic: Option<&CompiledProgram>,
mapper: &SpanMapper<'_>,
) -> Option<TypeRef> {
let semantic_type = semantic
.and_then(|program| program.type_of_span(expression.span))
.and_then(value_type_ref);
match &expression.kind {
ExprKind::Literal(literal) => semantic_type.or_else(|| literal_type(literal)),
ExprKind::Name(name) => {
if let Some((key, ty)) = scope.get(name) {
self.add_occurrence(key.clone(), expression.span, mapper);
return Some(ty.clone());
}
if let Some((enum_name, variant)) = self.qualified_enum_variant(name, scope) {
if let Some((owner, member)) = mapper.split_qualified_span(expression.span) {
self.add_mapped_occurrence(
SymbolKey::Type(enum_name.clone()),
owner,
mapper,
);
self.add_mapped_occurrence(
SymbolKey::Variant(enum_name.clone(), variant),
member,
mapper,
);
} else {
self.add_occurrence(
SymbolKey::Variant(enum_name.clone(), variant),
expression.span,
mapper,
);
}
return Some(TypeRef::Named(enum_name));
}
let enum_name = semantic_type
.as_ref()
.and_then(named_enum)
.or_else(|| expected.and_then(named_enum))
.map(str::to_owned)
.or_else(|| self.unique_public_variant_owner(name));
if let Some(enum_name) = enum_name {
self.add_occurrence(
SymbolKey::Variant(enum_name.clone(), name.clone()),
expression.span,
mapper,
);
return Some(TypeRef::Named(enum_name));
}
semantic_type.or_else(|| expected.cloned())
}
ExprKind::Field { receiver, field } => {
if let Some(path) = dotted_expression_path(expression) {
if let Some((enum_name, variant)) = self.qualified_enum_variant(&path, scope) {
self.add_occurrence(
SymbolKey::Type(enum_name.clone()),
receiver.span,
mapper,
);
self.add_occurrence(
SymbolKey::Variant(enum_name.clone(), variant),
field.span,
mapper,
);
return semantic_type.or(Some(TypeRef::Named(enum_name)));
}
}
let receiver_type = self.index_expr(receiver, None, scope, semantic, mapper);
let owner = receiver_type.as_ref().and_then(named_type);
if let Some(owner) = owner {
self.add_occurrence(
SymbolKey::Field(owner.to_owned(), field.value.clone()),
field.span,
mapper,
);
return semantic_type.or_else(|| self.field_type(owner, &field.value).cloned());
}
semantic_type
}
ExprKind::Call { callee, arguments } => {
let derive_name = self.resolve_derive_name(&callee.value);
if let Some((derive_name, info)) = derive_name
.and_then(|name| self.derives.get(&name).cloned().map(|info| (name, info)))
{
self.add_qualified_occurrence(
SymbolKey::Derive(derive_name.clone()),
&derive_name,
callee.span,
mapper,
);
for (index, argument) in arguments.iter().enumerate() {
self.index_expr(
argument,
info.parameters.get(index),
scope,
semantic,
mapper,
);
}
semantic_type.or(info.return_type)
} else {
let builtin = builtin_signature(&callee.value, expected);
for (index, argument) in arguments.iter().enumerate() {
self.index_expr(
argument,
builtin
.as_ref()
.and_then(|(parameters, _)| parameters.get(index)),
scope,
semantic,
mapper,
);
}
semantic_type.or_else(|| builtin.map(|(_, result)| result))
}
}
ExprKind::Unary { operator, operand } => {
let operand_expected = match operator {
UnaryOp::Not => Some(&TypeRef::Bool),
UnaryOp::Negate => expected,
};
let operand_type =
self.index_expr(operand, operand_expected, scope, semantic, mapper);
semantic_type.or(match operator {
UnaryOp::Not => Some(TypeRef::Bool),
UnaryOp::Negate => operand_type,
})
}
ExprKind::Binary {
left,
operator,
right,
} => {
let left_hint = self.infer_expr_type(right, scope, semantic);
let right_hint = self.infer_expr_type(left, scope, semantic);
let (left_expected, right_expected) =
if matches!(operator, BinaryOp::And | BinaryOp::Or) {
(Some(TypeRef::Bool), Some(TypeRef::Bool))
} else {
(left_hint, right_hint)
};
let left_type = self.index_expr(
left,
left_expected.as_ref().or(expected),
scope,
semantic,
mapper,
);
let right_type = self.index_expr(
right,
right_expected.as_ref().or(left_type.as_ref()),
scope,
semantic,
mapper,
);
semantic_type.or_else(|| {
if matches!(
operator,
BinaryOp::Equal
| BinaryOp::NotEqual
| BinaryOp::Greater
| BinaryOp::GreaterEqual
| BinaryOp::Less
| BinaryOp::LessEqual
| BinaryOp::And
| BinaryOp::Or
) {
Some(TypeRef::Bool)
} else if *operator == BinaryOp::Divide
|| matches!(left_type, Some(TypeRef::Decimal))
|| matches!(right_type, Some(TypeRef::Decimal))
{
Some(TypeRef::Decimal)
} else {
left_type.or(right_type)
}
})
}
}
}
fn infer_expr_type(
&self,
expression: &Expr,
scope: &Scope,
semantic: Option<&CompiledProgram>,
) -> Option<TypeRef> {
if let Some(value) = semantic
.and_then(|program| program.type_of_span(expression.span))
.and_then(value_type_ref)
{
return Some(value);
}
match &expression.kind {
ExprKind::Literal(literal) => literal_type(literal),
ExprKind::Name(name) => scope
.get(name)
.map(|(_, ty)| ty.clone())
.or_else(|| {
self.qualified_enum_variant(name, scope)
.map(|(owner, _)| TypeRef::Named(owner))
})
.or_else(|| self.unique_public_variant_owner(name).map(TypeRef::Named)),
ExprKind::Field { receiver, field } => {
if let Some(path) = dotted_expression_path(expression) {
if let Some((owner, _)) = self.qualified_enum_variant(&path, scope) {
return Some(TypeRef::Named(owner));
}
}
self.infer_expr_type(receiver, scope, semantic)
.as_ref()
.and_then(named_type)
.and_then(|owner| self.field_type(owner, &field.value))
.cloned()
}
ExprKind::Call { callee, .. } => self
.resolve_derive_name(&callee.value)
.and_then(|name| self.derives.get(&name))
.and_then(|info| info.return_type.clone())
.or_else(|| builtin_signature(&callee.value, None).map(|(_, result)| result)),
ExprKind::Unary { operator, operand } => match operator {
UnaryOp::Not => Some(TypeRef::Bool),
UnaryOp::Negate => self.infer_expr_type(operand, scope, semantic),
},
ExprKind::Binary {
left,
operator,
right,
} => {
if matches!(
operator,
BinaryOp::Equal
| BinaryOp::NotEqual
| BinaryOp::Greater
| BinaryOp::GreaterEqual
| BinaryOp::Less
| BinaryOp::LessEqual
| BinaryOp::And
| BinaryOp::Or
) {
Some(TypeRef::Bool)
} else if *operator == BinaryOp::Divide {
Some(TypeRef::Decimal)
} else {
let left = self.infer_expr_type(left, scope, semantic);
let right = self.infer_expr_type(right, scope, semantic);
if matches!(left, Some(TypeRef::Decimal))
|| matches!(right, Some(TypeRef::Decimal))
{
Some(TypeRef::Decimal)
} else {
left.or(right)
}
}
}
}
}
fn parameter_scope(
&mut self,
parameters: &[Parameter],
owner: Span,
kind: &'static str,
mapper: &SpanMapper<'_>,
) -> Scope {
parameters
.iter()
.map(|parameter| {
let key = self.add_local_definition(
owner,
¶meter.name.value,
¶meter.ty.value,
kind,
parameter.name.span,
mapper,
);
self.add_type_name(¶meter.ty.value, parameter.ty.span, mapper);
(
parameter.name.value.clone(),
(key, type_ref_from_name(¶meter.ty.value)),
)
})
.collect()
}
fn add_local_definition(
&mut self,
owner: Span,
name: &str,
ty: &str,
kind: &'static str,
span: Span,
mapper: &SpanMapper<'_>,
) -> SymbolKey {
let owner = mapper.map(owner).unwrap_or_else(|| NavigationSpan {
uri: String::new(),
span: owner,
});
let key = SymbolKey::Local {
uri: owner.uri,
owner: owner.span.start,
name: name.to_owned(),
};
self.add_definition(
key.clone(),
name.to_owned(),
kind,
format!("{name}: {ty}"),
span,
mapper,
);
key
}
fn add_type_ref(&mut self, ty: &TypeRef, span: Span, mapper: &SpanMapper<'_>) {
if let TypeRef::Named(name) = ty {
self.add_occurrence(SymbolKey::Type(name.clone()), span, mapper);
}
}
fn add_type_name(&mut self, name: &str, span: Span, mapper: &SpanMapper<'_>) {
if !is_builtin_type(name) {
self.add_occurrence(SymbolKey::Type(name.to_owned()), span, mapper);
}
}
fn field_type(&self, owner: &str, field: &str) -> Option<&TypeRef> {
let owner = self.resolve_type_name(owner)?;
self.entities.get(&owner)?.fields.get(field)
}
fn qualified_enum_variant(&self, path: &str, scope: &Scope) -> Option<(String, String)> {
let (owner, variant) = path.rsplit_once('.')?;
let root = owner.split('.').next()?;
let owner = unique_leaf_set(&self.enums, owner)?;
if scope.contains_key(root)
|| !self
.variant_owners
.get(variant)
.is_some_and(|owners| owners.iter().any(|candidate| candidate == owner))
{
return None;
}
Some((owner.to_owned(), variant.to_owned()))
}
fn unique_public_variant_owner(&self, variant: &str) -> Option<String> {
let mut owners = self
.variant_owners
.get(variant)?
.iter()
.filter(|owner| !is_private_symbol(owner));
let owner = owners.next()?.clone();
owners.next().is_none().then_some(owner)
}
fn resolve_type_name(&self, name: &str) -> Option<String> {
let SymbolKey::Type(name) = self.resolve_symbol_key(&SymbolKey::Type(name.to_owned()))?
else {
return None;
};
Some(name)
}
fn resolve_symbol_key(&self, key: &SymbolKey) -> Option<SymbolKey> {
if self.definitions.contains_key(key) {
return Some(key.clone());
}
match key {
SymbolKey::Type(name) => self.resolve_named_key(SymbolNamespace::Type, name),
SymbolKey::Field(owner, field) => {
let owner = self.resolve_type_name(owner)?;
let key = SymbolKey::Field(owner, field.clone());
self.definitions.contains_key(&key).then_some(key)
}
SymbolKey::Variant(owner, variant) => {
let owner = self.resolve_type_name(owner)?;
let key = SymbolKey::Variant(owner, variant.clone());
self.definitions.contains_key(&key).then_some(key)
}
SymbolKey::Derive(name) => self.resolve_named_key(SymbolNamespace::Derive, name),
SymbolKey::Decision(name) => self.resolve_named_key(SymbolNamespace::Decision, name),
SymbolKey::Rule(name) => self.resolve_named_key(SymbolNamespace::Rule, name),
SymbolKey::Fragment(name) => self.resolve_named_key(SymbolNamespace::Fragment, name),
SymbolKey::Fixture(name) => self.resolve_named_key(SymbolNamespace::Fixture, name),
SymbolKey::Case(name) => self.resolve_named_key(SymbolNamespace::Case, name),
SymbolKey::Invariant(name) => self.resolve_named_key(SymbolNamespace::Invariant, name),
SymbolKey::Local { .. } => None,
}
}
fn resolve_named_key(&self, namespace: SymbolNamespace, name: &str) -> Option<SymbolKey> {
if name.contains('.') {
return None;
}
let mut matches = self.definitions.keys().filter(|candidate| {
symbol_name(candidate, namespace).is_some_and(|candidate| {
!is_private_symbol(candidate) && candidate.rsplit('.').next() == Some(name)
})
});
let candidate = matches.next()?.clone();
matches.next().is_none().then_some(candidate)
}
fn resolve_derive_name(&self, name: &str) -> Option<String> {
if !name.contains('.') {
if let Some(fragment) = self.active_fragment.as_deref() {
let local = format!("{fragment}.{name}");
if self.derives.contains_key(&local) {
return Some(local);
}
}
}
if builtin_signature(name, None).is_some() {
return None;
}
unique_leaf_key(&self.derives, name).cloned()
}
fn add_definition(
&mut self,
key: SymbolKey,
name: String,
kind: &'static str,
detail: String,
span: Span,
mapper: &SpanMapper<'_>,
) {
let Some(location) = mapper.map(span) else {
return;
};
self.add_document(mapper, &location.uri);
self.definitions
.entry(key.clone())
.or_insert_with(|| SymbolDefinition {
name,
kind,
detail,
location: location.clone(),
});
self.occurrences.push(SymbolOccurrence { key, location });
}
fn add_occurrence(&mut self, key: SymbolKey, span: Span, mapper: &SpanMapper<'_>) {
let Some(location) = mapper.map(span) else {
return;
};
self.add_mapped_occurrence(key, location, mapper);
}
fn add_qualified_occurrence(
&mut self,
key: SymbolKey,
qualified_name: &str,
span: Span,
mapper: &SpanMapper<'_>,
) {
if qualified_name.contains('.') {
if let Some((prefix, member)) = mapper.split_qualified_span(span) {
if let Some((fragment, _)) = qualified_name.rsplit_once('.') {
if self
.definitions
.contains_key(&SymbolKey::Fragment(fragment.to_owned()))
{
self.add_mapped_occurrence(
SymbolKey::Fragment(fragment.to_owned()),
prefix,
mapper,
);
}
}
self.add_mapped_occurrence(key, member, mapper);
return;
}
}
self.add_occurrence(key, span, mapper);
}
fn add_mapped_occurrence(
&mut self,
key: SymbolKey,
location: NavigationSpan,
mapper: &SpanMapper<'_>,
) {
let Some(key) = self.resolve_symbol_key(&key) else {
return;
};
self.add_document(mapper, &location.uri);
self.occurrences.push(SymbolOccurrence { key, location });
}
fn add_document(&mut self, mapper: &SpanMapper<'_>, uri: &str) {
if self.documents.contains_key(uri) {
return;
}
if let Some(document) = mapper.document(uri) {
self.documents.insert(uri.to_owned(), document.clone());
}
}
fn finish(mut self) -> Self {
self.occurrences.sort_by(|left, right| {
(
&left.location.uri,
left.location.span.start,
left.location.span.end,
&left.key,
)
.cmp(&(
&right.location.uri,
right.location.span.start,
right.location.span.end,
&right.key,
))
});
self.occurrences.dedup_by(|left, right| {
left.key == right.key
&& left.location.uri == right.location.uri
&& left.location.span == right.location.span
});
self
}
}
type Scope = BTreeMap<String, (SymbolKey, TypeRef)>;
#[derive(Clone, Copy)]
enum SymbolNamespace {
Type,
Derive,
Decision,
Rule,
Fragment,
Fixture,
Case,
Invariant,
}
fn symbol_name(key: &SymbolKey, namespace: SymbolNamespace) -> Option<&str> {
match (namespace, key) {
(SymbolNamespace::Type, SymbolKey::Type(name))
| (SymbolNamespace::Derive, SymbolKey::Derive(name))
| (SymbolNamespace::Decision, SymbolKey::Decision(name))
| (SymbolNamespace::Rule, SymbolKey::Rule(name))
| (SymbolNamespace::Fragment, SymbolKey::Fragment(name))
| (SymbolNamespace::Fixture, SymbolKey::Fixture(name))
| (SymbolNamespace::Case, SymbolKey::Case(name))
| (SymbolNamespace::Invariant, SymbolKey::Invariant(name)) => Some(name),
_ => None,
}
}
fn unique_leaf_key<'a, T>(values: &'a BTreeMap<String, T>, name: &str) -> Option<&'a String> {
if let Some((key, _)) = values.get_key_value(name) {
return Some(key);
}
if name.contains('.') {
return None;
}
let mut matches = values.keys().filter(|candidate| {
!is_private_symbol(candidate) && candidate.rsplit('.').next() == Some(name)
});
let value = matches.next()?;
matches.next().is_none().then_some(value)
}
fn unique_leaf_set<'a>(values: &'a BTreeSet<String>, name: &str) -> Option<&'a String> {
if let Some(value) = values.get(name) {
return Some(value);
}
if name.contains('.') {
return None;
}
let mut matches = values.iter().filter(|candidate| {
!is_private_symbol(candidate) && candidate.rsplit('.').next() == Some(name)
});
let value = matches.next()?;
matches.next().is_none().then_some(value)
}
fn is_private_symbol(name: &str) -> bool {
name.split('.').any(|segment| segment.starts_with("@pkg_"))
}
struct SpanMapper<'a> {
source: &'a SourceFile,
sources: &'a BTreeMap<String, SourceDocument>,
}
impl SpanMapper<'_> {
fn map(&self, span: Span) -> Option<NavigationSpan> {
let source_name = self.source.name_at(span.start);
let document = self.sources.get(source_name)?;
Some(NavigationSpan {
uri: document.uri.as_str().to_owned(),
span: self.source.local_span(span)?,
})
}
fn document(&self, uri: &str) -> Option<&SourceDocument> {
self.sources
.values()
.find(|document| document.uri.as_str() == uri)
}
fn split_qualified_span(&self, span: Span) -> Option<(NavigationSpan, NavigationSpan)> {
let location = self.map(span)?;
let document = self.document(&location.uri)?;
let source = document.text.get(location.span.start..location.span.end)?;
let dot = source.rfind('.')?;
let owner = source[..dot].trim();
let member = source[dot + 1..].trim();
if owner.is_empty() || member.is_empty() {
return None;
}
let owner_start = source[..dot].find(owner)?;
let member_start = dot + 1 + source[dot + 1..].find(member)?;
Some((
NavigationSpan {
uri: location.uri.clone(),
span: Span::new(
location.span.start + owner_start,
location.span.start + owner_start + owner.len(),
),
},
NavigationSpan {
uri: location.uri,
span: Span::new(
location.span.start + member_start,
location.span.start + member_start + member.len(),
),
},
))
}
}
fn dotted_expression_path(expression: &Expr) -> Option<String> {
match &expression.kind {
ExprKind::Name(name) => Some(name.clone()),
ExprKind::Field { receiver, field } => Some(format!(
"{}.{}",
dotted_expression_path(receiver)?,
field.value
)),
_ => None,
}
}
pub(super) fn navigation_index(
uri: &Uri,
document: &OpenDocument,
documents: &BTreeMap<String, OpenDocument>,
) -> Option<NavigationIndex> {
compiled_index(uri, documents).or_else(|| recovery_index(uri, document, documents))
}
fn compiled_index(
uri: &Uri,
documents: &BTreeMap<String, OpenDocument>,
) -> Option<NavigationIndex> {
let requested_path = file_uri_path(uri)?;
let canonical_requested = requested_path.canonicalize().ok()?;
let target = crate::project::resolve_document_target(&canonical_requested).ok()?;
let overrides = open_overrides(documents);
let output = compile_project_with_dependency_graph_and_overrides(
&target.entry,
&target.root,
&target.dependencies,
&overrides,
)
.ok()?;
if !output.files.iter().any(|path| path == &canonical_requested) {
return None;
}
let program = output.compile.program.as_ref()?;
let sources = project_sources(&output.files, &overrides, uri, documents);
let mapper = SpanMapper {
source: &output.source,
sources: &sources,
};
let mut index = NavigationIndex::default();
index.collect_definitions(program.ast(), &mapper);
index.collect_occurrences(program.ast(), Some(program), &mapper);
Some(index.finish())
}
fn recovery_index(
uri: &Uri,
document: &OpenDocument,
documents: &BTreeMap<String, OpenDocument>,
) -> Option<NavigationIndex> {
let mut programs = completion_programs(uri, document, documents);
let current = parse(SourceFile::new(
uri.as_str().to_owned(),
document.text.clone(),
))
.program;
programs.retain(|program| {
source_uri(&program.source.name, uri, documents).is_none_or(|source_uri| source_uri != *uri)
});
if let Some(current) = current {
programs.push(current);
}
if programs.is_empty() {
return None;
}
let mut sources = BTreeMap::new();
for program in &programs {
let source_name = program.source.name.to_string();
let source_uri = source_uri(&source_name, uri, documents)?;
sources.insert(
source_name,
SourceDocument {
uri: source_uri,
text: program.source.text.to_string(),
},
);
}
let mut index = NavigationIndex::default();
for program in &programs {
let mapper = SpanMapper {
source: &program.source,
sources: &sources,
};
index.collect_definitions(program, &mapper);
}
for program in &programs {
let mapper = SpanMapper {
source: &program.source,
sources: &sources,
};
index.collect_occurrences(program, None, &mapper);
}
Some(index.finish())
}
fn open_overrides(documents: &BTreeMap<String, OpenDocument>) -> BTreeMap<PathBuf, String> {
documents
.iter()
.filter_map(|(uri, document)| {
let uri = Uri::from_str(uri).ok()?;
let path = file_uri_path(&uri)?.canonicalize().ok()?;
Some((path, document.text.clone()))
})
.collect()
}
fn project_sources(
files: &[PathBuf],
overrides: &BTreeMap<PathBuf, String>,
requested_uri: &Uri,
documents: &BTreeMap<String, OpenDocument>,
) -> BTreeMap<String, SourceDocument> {
files
.iter()
.filter_map(|path| {
let text = overrides
.get(path)
.cloned()
.or_else(|| fs::read_to_string(path).ok())?;
let uri = uri_for_path(path, requested_uri, documents)?;
Some((path.display().to_string(), SourceDocument { uri, text }))
})
.collect()
}
fn source_uri(
source_name: &str,
requested_uri: &Uri,
documents: &BTreeMap<String, OpenDocument>,
) -> Option<Uri> {
if source_name.starts_with("file://") {
return Uri::from_str(source_name).ok();
}
uri_for_path(Path::new(source_name), requested_uri, documents)
}
fn uri_for_path(
path: &Path,
requested_uri: &Uri,
documents: &BTreeMap<String, OpenDocument>,
) -> Option<Uri> {
let canonical = path
.canonicalize()
.ok()
.unwrap_or_else(|| path.to_path_buf());
if file_uri_path(requested_uri)
.and_then(|path| path.canonicalize().ok())
.is_some_and(|path| path == canonical)
{
return Some(requested_uri.clone());
}
for open_uri in documents.keys() {
let Ok(open_uri) = Uri::from_str(open_uri) else {
continue;
};
if file_uri_path(&open_uri)
.and_then(|path| path.canonicalize().ok())
.is_some_and(|path| path == canonical)
{
return Some(open_uri);
}
}
file_uri(&canonical)
}
fn file_uri(path: &Path) -> Option<Uri> {
let path = path.to_string_lossy();
let mut encoded = String::with_capacity(path.len());
for byte in path.bytes() {
if byte.is_ascii_alphanumeric() || matches!(byte, b'/' | b'-' | b'_' | b'.' | b'~') {
encoded.push(char::from(byte));
} else {
use std::fmt::Write as _;
let _ = write!(encoded, "%{byte:02X}");
}
}
Uri::from_str(&format!("file://{encoded}")).ok()
}
fn parameter_types(parameters: &[Parameter]) -> Vec<TypeRef> {
parameters
.iter()
.map(|parameter| type_ref_from_name(¶meter.ty.value))
.collect()
}
fn type_ref_from_name(name: &str) -> TypeRef {
match name {
"Bool" => TypeRef::Bool,
"Int" => TypeRef::Int,
"Decimal" => TypeRef::Decimal,
"String" => TypeRef::String,
"Date" => TypeRef::Date,
"Duration" => TypeRef::Duration,
_ => TypeRef::Named(name.to_owned()),
}
}
fn value_type_ref(value: &ValueType) -> Option<TypeRef> {
match value {
ValueType::Bool => Some(TypeRef::Bool),
ValueType::Int => Some(TypeRef::Int),
ValueType::Decimal => Some(TypeRef::Decimal),
ValueType::String => Some(TypeRef::String),
ValueType::Date => Some(TypeRef::Date),
ValueType::Duration => Some(TypeRef::Duration),
ValueType::Enum(name) | ValueType::Entity(name) => Some(TypeRef::Named(name.clone())),
ValueType::Unknown | ValueType::Error => None,
}
}
fn literal_type(literal: &tess::ast::Literal) -> Option<TypeRef> {
match literal {
tess::ast::Literal::Bool(_) => Some(TypeRef::Bool),
tess::ast::Literal::Number(tess::ast::NumericLiteral::Int(_)) => Some(TypeRef::Int),
tess::ast::Literal::Number(tess::ast::NumericLiteral::Decimal(_)) => Some(TypeRef::Decimal),
tess::ast::Literal::String(_) => Some(TypeRef::String),
tess::ast::Literal::Unknown => None,
}
}
fn builtin_signature(name: &str, expected: Option<&TypeRef>) -> Option<(Vec<TypeRef>, TypeRef)> {
match name {
"max" | "min" => {
let ty = expected
.filter(|ty| matches!(ty, TypeRef::Int | TypeRef::Decimal))
.cloned()
.unwrap_or(TypeRef::Int);
Some((vec![ty.clone(), ty.clone()], ty))
}
"abs" => {
let ty = expected
.filter(|ty| matches!(ty, TypeRef::Int | TypeRef::Decimal))
.cloned()
.unwrap_or(TypeRef::Int);
Some((vec![ty.clone()], ty))
}
"date" => Some((vec![TypeRef::String], TypeRef::Date)),
"days" | "hours" | "minutes" => Some((vec![TypeRef::Int], TypeRef::Duration)),
_ => None,
}
}
fn named_type(ty: &TypeRef) -> Option<&str> {
let TypeRef::Named(name) = ty else {
return None;
};
Some(name)
}
fn named_enum(ty: &TypeRef) -> Option<&str> {
named_type(ty)
}
fn is_builtin_type(name: &str) -> bool {
matches!(
name,
"Bool" | "Int" | "Decimal" | "String" | "Date" | "Duration"
)
}
fn cardinality_suffix(cardinality: Cardinality) -> &'static str {
match cardinality {
Cardinality::ExactlyOne => "",
Cardinality::ZeroOrOne => "?",
Cardinality::Many => "*",
}
}
fn contains_cursor(span: Span, cursor: usize) -> bool {
span.start <= cursor && cursor <= span.end && span.start < span.end
}
const fn span_len(span: Span) -> usize {
span.end.saturating_sub(span.start)
}
#[cfg(test)]
mod tests {
use super::*;
fn define(index: &mut NavigationIndex, key: SymbolKey) {
let name = match &key {
SymbolKey::Type(name)
| SymbolKey::Derive(name)
| SymbolKey::Decision(name)
| SymbolKey::Rule(name)
| SymbolKey::Fragment(name)
| SymbolKey::Fixture(name)
| SymbolKey::Case(name)
| SymbolKey::Invariant(name)
| SymbolKey::Local { name, .. } => name.clone(),
SymbolKey::Field(owner, name) | SymbolKey::Variant(owner, name) => {
format!("{owner}.{name}")
}
};
index.definitions.insert(
key,
SymbolDefinition {
name,
kind: "test",
detail: String::new(),
location: NavigationSpan {
uri: "file:///test.tes".to_owned(),
span: Span::new(0, 1),
},
},
);
}
#[test]
fn symbol_resolution_prefers_exact_then_unique_public_leaf() {
let mut index = NavigationIndex::default();
define(&mut index, SymbolKey::Derive("정책.판정".to_owned()));
assert_eq!(
index.resolve_symbol_key(&SymbolKey::Derive("판정".to_owned())),
Some(SymbolKey::Derive("정책.판정".to_owned()))
);
define(&mut index, SymbolKey::Derive("다른정책.판정".to_owned()));
assert_eq!(
index.resolve_symbol_key(&SymbolKey::Derive("판정".to_owned())),
None
);
define(&mut index, SymbolKey::Derive("판정".to_owned()));
assert_eq!(
index.resolve_symbol_key(&SymbolKey::Derive("판정".to_owned())),
Some(SymbolKey::Derive("판정".to_owned()))
);
index.derives.insert(
"정책.max".to_owned(),
CallableInfo {
parameters: vec![TypeRef::Int, TypeRef::Int],
return_type: Some(TypeRef::Int),
},
);
assert_eq!(index.resolve_derive_name("max"), None);
index.active_fragment = Some("정책".to_owned());
assert_eq!(
index.resolve_derive_name("max"),
Some("정책.max".to_owned())
);
index.active_fragment = None;
define(
&mut index,
SymbolKey::Decision("의존성.@pkg_내부.숨김판정".to_owned()),
);
define(&mut index, SymbolKey::Decision("공개.숨김판정".to_owned()));
assert_eq!(
index.resolve_symbol_key(&SymbolKey::Decision("숨김판정".to_owned())),
Some(SymbolKey::Decision("공개.숨김판정".to_owned()))
);
assert_eq!(
index.resolve_symbol_key(&SymbolKey::Decision("의존성.@pkg_내부.숨김판정".to_owned())),
Some(SymbolKey::Decision("의존성.@pkg_내부.숨김판정".to_owned()))
);
}
#[test]
fn member_resolution_uses_the_resolved_owner_type() {
let mut index = NavigationIndex::default();
define(&mut index, SymbolKey::Type("학교.학생".to_owned()));
define(
&mut index,
SymbolKey::Field("학교.학생".to_owned(), "점수".to_owned()),
);
assert_eq!(
index.resolve_symbol_key(&SymbolKey::Field("학생".to_owned(), "점수".to_owned())),
Some(SymbolKey::Field("학교.학생".to_owned(), "점수".to_owned()))
);
define(&mut index, SymbolKey::Type("학원.학생".to_owned()));
define(
&mut index,
SymbolKey::Field("학원.학생".to_owned(), "점수".to_owned()),
);
assert_eq!(
index.resolve_symbol_key(&SymbolKey::Field("학생".to_owned(), "점수".to_owned())),
None
);
}
}