diff options
author | Vincent Ambo <mail@tazj.in> | 2022-09-12T13·12+0300 |
---|---|---|
committer | tazjin <tazjin@tvl.su> | 2022-09-13T14·41+0000 |
commit | 0f59fe66015580612ee79a9e9e985eaa0fa495e6 (patch) | |
tree | 5b709005b5652d04bc20cd2dfb8a6784af6f4f8b | |
parent | 7fd7a4465b4357654af51bf7914e438932141f31 (diff) |
feat(tvix/eval): implement initial fancy formatting for errors r/4836
This very closely follows the way it's done for warnings, but errors have a lot more information available in some cases which we do not surface yet. Note also that due to requiring the `CodeMap`, this is not yet called from eval.rs as the way that is threaded through needs to be refactored, so only the method for reporting these errors as strings is implemented so far. Next steps for this will be to add a generic diagnostics module that reduces some of the boilerplate for this between warnings & errors, and which will also give us a good point in the future to switch to a fancier diagnostics crate. Change-Id: If6bb209f8e7a568d866e516a90335b9b2afbf66d Reviewed-on: https://cl.tvl.fyi/c/depot/+/6534 Reviewed-by: grfn <grfn@gws.fyi> Tested-by: BuildkiteCI
-rw-r--r-- | tvix/eval/src/compiler/mod.rs | 16 | ||||
-rw-r--r-- | tvix/eval/src/errors.rs | 150 | ||||
-rw-r--r-- | tvix/eval/src/value/attrs.rs | 6 |
3 files changed, 145 insertions, 27 deletions
diff --git a/tvix/eval/src/compiler/mod.rs b/tvix/eval/src/compiler/mod.rs index 7868bced4653..440ac284c1a6 100644 --- a/tvix/eval/src/compiler/mod.rs +++ b/tvix/eval/src/compiler/mod.rs @@ -1114,21 +1114,17 @@ impl Compiler<'_, '_> { self.scope_mut().poison(global_ident, depth); } - let mut shadowed = false; for other in self.scope().locals.iter().rev() { if other.has_name(&name) && other.depth == depth { - shadowed = true; + self.emit_error( + self.span_for(node), + ErrorKind::VariableAlreadyDefined(other.span), + ); + break; } } - if shadowed { - self.emit_error( - self.span_for(node), - ErrorKind::VariableAlreadyDefined(name.clone()), - ); - } - let span = self.span_for(node); self.scope_mut().declare_local(name, span) } @@ -1264,7 +1260,7 @@ impl Compiler<'_, '_> { N: AstNode<Language = rnix::NixLanguage>, { Error { - kind: ErrorKind::DynamicKeyInLet(node.syntax().clone()), + kind: ErrorKind::DynamicKeyInLet, span: self.span_for(node), } } diff --git a/tvix/eval/src/errors.rs b/tvix/eval/src/errors.rs index b87555d06d52..39b105de9812 100644 --- a/tvix/eval/src/errors.rs +++ b/tvix/eval/src/errors.rs @@ -1,16 +1,24 @@ use std::fmt::Display; +use codemap::{CodeMap, Span}; +use codemap_diagnostic::{Diagnostic, Emitter, Level, SpanLabel, SpanStyle}; + +use crate::Value; + #[derive(Clone, Debug)] pub enum ErrorKind { + /// These are user-generated errors through builtins. + Throw(String), + Abort(String), + AssertionFailed, + DuplicateAttrsKey { key: String, }, /// Attempted to specify an invalid key type (e.g. integer) in a /// dynamic attribute name. - InvalidAttributeName { - given: &'static str, - }, + InvalidAttributeName(Value), AttributeNotFound { name: String, @@ -30,7 +38,7 @@ pub enum ErrorKind { PathResolution(String), /// Dynamic keys are not allowed in let. - DynamicKeyInLet(rnix::SyntaxNode), + DynamicKeyInLet, /// Unknown variable in statically known scope. UnknownStaticVariable, @@ -39,7 +47,7 @@ pub enum ErrorKind { UnknownDynamicVariable(String), /// User is defining the same variable twice at the same depth. - VariableAlreadyDefined(String), + VariableAlreadyDefined(Span), /// Attempt to call something that is not callable. NotCallable, @@ -49,12 +57,6 @@ pub enum ErrorKind { ParseErrors(Vec<rnix::parser::ParseError>), - AssertionFailed, - - /// These are user-generated errors through builtins. - Throw(String), - Abort(String), - /// An error occured while forcing a thunk, and needs to be /// chained up. ThunkForce(Box<Error>), @@ -68,7 +70,7 @@ pub enum ErrorKind { #[derive(Clone, Debug)] pub struct Error { pub kind: ErrorKind, - pub span: codemap::Span, + pub span: Span, } impl Display for Error { @@ -78,3 +80,127 @@ impl Display for Error { } pub type EvalResult<T> = Result<T, Error>; + +impl Error { + pub fn fancy_format_str(&self, codemap: &CodeMap) -> String { + let mut out = vec![]; + Emitter::vec(&mut out, Some(codemap)).emit(&[self.diagnostic(codemap)]); + String::from_utf8_lossy(&out).to_string() + } + + /// Create the optional span label displayed as an annotation on + /// the underlined span of the error. + fn span_label(&self) -> Option<String> { + None + } + + /// Create the primary error message displayed to users. + fn message(&self, codemap: &CodeMap) -> String { + match &self.kind { + ErrorKind::Throw(msg) => format!("error thrown: {}", msg), + ErrorKind::Abort(msg) => format!("evaluation aborted: {}", msg), + ErrorKind::AssertionFailed => "assertion failed".to_string(), + + ErrorKind::DuplicateAttrsKey { key } => { + format!("attribute key '{}' already defined", key) + } + + ErrorKind::InvalidAttributeName(val) => format!( + "found attribute name '{}' of type '{}', but attribute names must be strings", + val, + val.type_of() + ), + + ErrorKind::AttributeNotFound { name } => format!( + "attribute with name '{}' could not be found in the set", + name + ), + + ErrorKind::TypeError { expected, actual } => format!( + "expected value of type '{}', but found a '{}'", + expected, actual + ), + + ErrorKind::Incomparable { lhs, rhs } => { + format!("can not compare a {} with a {}", lhs, rhs) + } + + ErrorKind::PathResolution(err) => format!("could not resolve path: {}", err), + + ErrorKind::DynamicKeyInLet => { + "dynamically evaluated keys are not allowed in let-bindings".to_string() + } + + ErrorKind::UnknownStaticVariable => "variable not found".to_string(), + + ErrorKind::UnknownDynamicVariable(name) => format!( + r#"variable '{}' could not be found + +Note that this occured within a `with`-expression. The problem may be related +to a missing value in the attribute set(s) included via `with`."#, + name + ), + + ErrorKind::VariableAlreadyDefined(_) => "variable has already been defined".to_string(), + + ErrorKind::NotCallable => { + "this value is not callable (i.e. not a function or builtin)".to_string() + } + + ErrorKind::InfiniteRecursion => "infinite recursion encountered".to_string(), + + // TODO(tazjin): these errors should actually end up with + // individual spans etc. + ErrorKind::ParseErrors(errors) => format!("failed to parse Nix code: {:?}", errors), + + // TODO(tazjin): trace through the whole chain of thunk + // forcing errors with secondary spans, instead of just + // delegating to the inner error + ErrorKind::ThunkForce(err) => err.message(codemap), + + ErrorKind::NotImplemented(feature) => { + format!("feature not yet implemented in Tvix: {}", feature) + } + } + } + + /// Return the unique error code for this variant which can be + /// used to refer users to documentation. + fn code(&self) -> &'static str { + match self.kind { + ErrorKind::Throw(_) => "E001", + ErrorKind::Abort(_) => "E002", + ErrorKind::AssertionFailed => "E003", + ErrorKind::InvalidAttributeName { .. } => "E004", + ErrorKind::AttributeNotFound { .. } => "E005", + ErrorKind::TypeError { .. } => "E006", + ErrorKind::Incomparable { .. } => "E007", + ErrorKind::PathResolution(_) => "E008", + ErrorKind::DynamicKeyInLet => "E009", + ErrorKind::UnknownStaticVariable => "E010", + ErrorKind::UnknownDynamicVariable(_) => "E011", + ErrorKind::VariableAlreadyDefined(_) => "E012", + ErrorKind::NotCallable => "E013", + ErrorKind::InfiniteRecursion => "E014", + ErrorKind::ParseErrors(_) => "E015", + ErrorKind::DuplicateAttrsKey { .. } => "E016", + ErrorKind::ThunkForce(_) => "E017", + ErrorKind::NotImplemented(_) => "E999", + } + } + + fn diagnostic(&self, codemap: &CodeMap) -> Diagnostic { + let span_label = SpanLabel { + label: self.span_label(), + span: self.span, + style: SpanStyle::Primary, + }; + + Diagnostic { + level: Level::Error, + message: self.message(codemap), + spans: vec![span_label], + code: Some(self.code().into()), + } + } +} diff --git a/tvix/eval/src/value/attrs.rs b/tvix/eval/src/value/attrs.rs index b8ae51bf48fc..4c18ad2f55b2 100644 --- a/tvix/eval/src/value/attrs.rs +++ b/tvix/eval/src/value/attrs.rs @@ -286,11 +286,7 @@ impl NixAttrs { continue; } - other => { - return Err(ErrorKind::InvalidAttributeName { - given: other.type_of(), - }) - } + other => return Err(ErrorKind::InvalidAttributeName(other)), } } |