1
1
mirror of https://github.com/oxalica/nil.git synced 2024-11-27 00:06:26 +03:00

Report name resolution failures

This commit is contained in:
oxalica 2022-08-18 00:52:46 +08:00
parent c91cf603e6
commit b2128038c2
4 changed files with 46 additions and 6 deletions

View File

@ -21,6 +21,7 @@ Super fast incremental analysis! Scans `all-packages.nix` in less than 0.1s and
- Syntax errors.
- Incomplete syntax errors are currently suppressed to avoid noisy outputs during typing.
- [x] Hard semantic errors reported as parse errors by Nix, like duplicated keys in attrsets.
- [x] Undefiend names.
- [x] Warnings of legacy syntax.
- [x] Warnings of unnecessary syntax.
- [x] Warnings of unused bindings, `with` and `rec`.

View File

@ -1,5 +1,5 @@
use super::{BindingKey, BindingValue, Bindings, DefDatabase, Expr, ExprId, Module, NameDefId};
use crate::{builtin, FileId};
use crate::{builtin, Diagnostic, DiagnosticKind, FileId};
use la_arena::{Arena, ArenaMap, Idx};
use smol_str::SmolStr;
use std::collections::HashMap;
@ -210,7 +210,8 @@ impl ScopeData {
/// Name resolution of all references.
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct NameResolution {
resolve_map: HashMap<ExprId, ResolveResult>,
// `None` value for unresolved names.
resolve_map: HashMap<ExprId, Option<ResolveResult>>,
}
impl NameResolution {
@ -222,7 +223,7 @@ impl NameResolution {
.filter_map(|(e, kind)| {
match kind {
// Inherited attrs are also translated into Expr::References.
Expr::Reference(name) => Some((e, scopes.resolve_name(e, name)?)),
Expr::Reference(name) => Some((e, scopes.resolve_name(e, name))),
_ => None,
}
})
@ -231,11 +232,29 @@ impl NameResolution {
}
pub fn get(&self, expr: ExprId) -> Option<&ResolveResult> {
self.resolve_map.get(&expr)
self.resolve_map.get(&expr)?.as_ref()
}
pub fn iter(&self) -> impl Iterator<Item = (ExprId, &'_ ResolveResult)> + '_ {
self.resolve_map.iter().map(|(e, res)| (*e, res))
self.resolve_map
.iter()
.filter_map(|(e, res)| Some((*e, res.as_ref()?)))
}
pub fn to_diagnostics(
&self,
db: &dyn DefDatabase,
file_id: FileId,
) -> impl Iterator<Item = Diagnostic> + '_ {
let source_map = db.source_map(file_id);
self.resolve_map
.iter()
.filter(|(_, res)| res.is_none())
.filter_map(move |(&e, _)| {
let ptr = source_map.expr_node(e)?;
let range = ptr.text_range();
Some(Diagnostic::new(range, DiagnosticKind::UndefinedName))
})
}
}

View File

@ -23,6 +23,9 @@ pub enum DiagnosticKind {
MergePlainRecAttrset,
MergeRecAttrset,
// Name resolution.
UndefinedName,
// Liveness.
UnusedBinding,
UnusedWith,
@ -52,7 +55,9 @@ impl Diagnostic {
pub fn severity(&self) -> Severity {
match self.kind {
DiagnosticKind::InvalidDynamic | DiagnosticKind::DuplicatedKey => Severity::Error,
DiagnosticKind::InvalidDynamic
| DiagnosticKind::DuplicatedKey
| DiagnosticKind::UndefinedName => Severity::Error,
DiagnosticKind::EmptyInherit
| DiagnosticKind::EmptyLetIn
| DiagnosticKind::LetAttrset
@ -97,6 +102,8 @@ impl Diagnostic {
"Merging rec-attrset with other attrsets or attrpath. Merged values can unexpectedly reference each other remotely as in a single `rec { ... }`"
}
DiagnosticKind::UndefinedName => "Undefined name",
DiagnosticKind::UnusedBinding => "Unused binding",
DiagnosticKind::UnusedWith => "Unused `with`",
DiagnosticKind::UnusedRec => "Unused `rec`",

View File

@ -11,6 +11,9 @@ pub(crate) fn diagnostics(db: &dyn DefDatabase, file: FileId) -> Vec<Diagnostic>
let module = db.module(file);
diags.extend(module.diagnostics().iter().cloned());
// Name resolution.
diags.extend(db.name_resolution(file).to_diagnostics(db, file));
// Liveness check.
let liveness = db.liveness_check(file);
diags.extend(liveness.to_diagnostics(db, file));
@ -56,6 +59,16 @@ mod tests {
);
}
#[test]
fn name_resolution() {
check(
"a",
expect![[r#"
0..1: Undefined name
"#]],
);
}
#[test]
fn liveness() {
check(