소스 검색

More spans for type check errors

David Peter 2 년 전
부모
커밋
24b936f5b0

+ 1 - 0
examples/typecheck_error/division_by_zero_in_const_eval.nbt

@@ -0,0 +1 @@
+let x = meter^(1/0)

+ 4 - 0
examples/typecheck_error/non_scalar_exponent.nbt

@@ -0,0 +1,4 @@
+let x = 2 meter
+let sigma = 0.5 meter
+
+e^(-x^2/sigma)

+ 2 - 0
examples/typecheck_error/unknown_callable.nbt

@@ -0,0 +1,2 @@
+let area = 5m²
+sqtr(area)

+ 1 - 0
examples/typecheck_error/unknown_foreign_function.nbt

@@ -0,0 +1 @@
+fn my_sqrt(x: Length^2) -> Length

+ 1 - 0
examples/typecheck_error/unknown_identifier.nbt

@@ -0,0 +1 @@
+2 meter + 3 sceond

+ 5 - 2
numbat/src/ast.rs

@@ -64,7 +64,7 @@ impl Expression {
                 }
                 span
             }
-            Expression::FunctionCall(_, _, _) => todo!(),
+            Expression::FunctionCall(identifier_span, _, _) => *identifier_span, // TODO: this is not the full function call span
         }
     }
 }
@@ -651,7 +651,10 @@ impl ReplaceSpans for Statement {
                 function_name_span: Span::dummy(),
                 function_name: function_name.clone(),
                 type_parameters: type_parameters.clone(),
-                parameters: parameters.clone(),
+                parameters: parameters
+                    .iter()
+                    .map(|(_, a, b, c)| (Span::dummy(), a.clone(), b.clone(), c.clone()))
+                    .collect(),
                 body: body.clone().map(|b| b.replace_spans()),
                 return_type_span: return_type_span.map(|_| Span::dummy()),
                 return_type_annotation: return_type_annotation.clone(),

+ 3 - 3
numbat/src/bytecode_interpreter.rs

@@ -59,7 +59,7 @@ impl BytecodeInterpreter {
                 self.compile_expression(rhs)?;
                 self.vm.add_op(Op::Negate);
             }
-            Expression::BinaryOperator(operator, lhs, rhs, _type) => {
+            Expression::BinaryOperator(_span, operator, lhs, rhs, _type) => {
                 self.compile_expression(lhs)?;
                 self.compile_expression(rhs)?;
 
@@ -102,8 +102,8 @@ impl BytecodeInterpreter {
             | Expression::UnitIdentifier(_, _, _, _)
             | Expression::FunctionCall(_, _, _)
             | Expression::Negate(_, _)
-            | Expression::BinaryOperator(BinaryOperator::ConvertTo, _, _, _) => {}
-            Expression::BinaryOperator(_, _, _, _) => {
+            | Expression::BinaryOperator(_, BinaryOperator::ConvertTo, _, _, _) => {}
+            Expression::BinaryOperator(_, _, _, _, _) => {
                 self.vm.add_op(Op::FullSimplify);
             }
         }

+ 28 - 27
numbat/src/diagnostic.rs

@@ -53,12 +53,15 @@ impl ErrorDiagnostic for NameResolutionError {
 impl ErrorDiagnostic for TypeCheckError {
     fn diagnostic(&self) -> Diagnostic {
         let d = Diagnostic::error().with_message("while type checking");
+        let inner_error = format!("{}", self);
 
         match self {
-            TypeCheckError::UnknownIdentifier(span, _) => {
-                d.with_labels(vec![span.diagnostic_label(LabelStyle::Primary)])
-            }
-            TypeCheckError::UnknownFunction(_) => d.with_notes(vec![format!("{self:#}")]),
+            TypeCheckError::UnknownIdentifier(span, _) => d.with_labels(vec![span
+                .diagnostic_label(LabelStyle::Primary)
+                .with_message("unknown identifier")]),
+            TypeCheckError::UnknownFunction(span, _) => d.with_labels(vec![span
+                .diagnostic_label(LabelStyle::Primary)
+                .with_message("unknown callable")]),
             TypeCheckError::IncompatibleDimensions {
                 operation,
                 span_operation,
@@ -79,22 +82,22 @@ impl ErrorDiagnostic for TypeCheckError {
                         .diagnostic_label(LabelStyle::Primary)
                         .with_message(format!("{actual_type}")),
                 ];
-                d.with_labels(labels).with_notes(vec![format!("{self:#}")])
+                d.with_labels(labels).with_notes(vec![inner_error])
             }
             TypeCheckError::NonScalarExponent(span, type_) => d
                 .with_labels(vec![span
                     .diagnostic_label(LabelStyle::Primary)
                     .with_message(format!("{type_}"))])
-                .with_notes(vec![format!("{self:#}")]),
-            TypeCheckError::UnsupportedConstEvalExpression(_) => {
-                d.with_notes(vec![format!("{self:#}")])
-            }
-            TypeCheckError::DivisionByZeroInConstEvalExpression => {
-                d.with_notes(vec![format!("{self:#}")])
-            }
-            TypeCheckError::RegistryError(_) => d.with_notes(vec![format!("{self:#}")]),
+                .with_notes(vec![inner_error]),
+            TypeCheckError::UnsupportedConstEvalExpression(span, _) => d.with_labels(vec![span
+                .diagnostic_label(LabelStyle::Primary)
+                .with_message(inner_error)]),
+            TypeCheckError::DivisionByZeroInConstEvalExpression(span) => d.with_labels(vec![span
+                .diagnostic_label(LabelStyle::Primary)
+                .with_message(inner_error)]),
+            TypeCheckError::RegistryError(_) => d.with_notes(vec![inner_error]),
             TypeCheckError::IncompatibleAlternativeDimensionExpression(_) => {
-                d.with_notes(vec![format!("{self:#}")])
+                d.with_notes(vec![inner_error])
             }
             TypeCheckError::WrongArity {
                 callable_span,
@@ -116,20 +119,18 @@ impl ErrorDiagnostic for TypeCheckError {
                         format!("{} to {}", arity.start(), arity.end())
                     }
                 ))]),
-            TypeCheckError::TypeParameterNameClash(_) => d.with_notes(vec![format!("{self:#}")]),
-            TypeCheckError::CanNotInferTypeParameters(_, _) => {
-                d.with_notes(vec![format!("{self:#}")])
-            }
-            TypeCheckError::MultipleUnresolvedTypeParameters => {
-                d.with_notes(vec![format!("{self:#}")])
-            }
-            TypeCheckError::ForeignFunctionNeedsReturnTypeAnnotation(_) => {
-                d.with_notes(vec![format!("{self:#}")])
-            }
-            TypeCheckError::UnknownForeignFunction(_) => d.with_notes(vec![format!("{self:#}")]),
-            TypeCheckError::ParameterTypesCanNotBeDeduced => {
-                d.with_notes(vec![format!("{self:#}")])
+            TypeCheckError::TypeParameterNameClash(_) => d.with_notes(vec![inner_error]),
+            TypeCheckError::CanNotInferTypeParameters(_, _) => d.with_notes(vec![inner_error]),
+            TypeCheckError::MultipleUnresolvedTypeParameters => d.with_notes(vec![inner_error]),
+            TypeCheckError::ForeignFunctionNeedsReturnTypeAnnotation(span, _) => {
+                d.with_labels(vec![span
+                    .diagnostic_label(LabelStyle::Primary)
+                    .with_message(inner_error)])
             }
+            TypeCheckError::UnknownForeignFunction(span, _) => d.with_labels(vec![span
+                .diagnostic_label(LabelStyle::Primary)
+                .with_message(inner_error)]),
+            TypeCheckError::ParameterTypesCanNotBeDeduced => d.with_notes(vec![inner_error]),
         }
     }
 }

+ 49 - 43
numbat/src/typechecker.rs

@@ -17,8 +17,8 @@ pub enum TypeCheckError {
     #[error("Unknown identifier '{1}'.")]
     UnknownIdentifier(Span, String),
 
-    #[error("Unknown function '{0}'.")]
-    UnknownFunction(String),
+    #[error("Unknown function '{1}'.")]
+    UnknownFunction(Span, String),
 
     #[error("{expected_name}: {expected_type}\n{actual_name}: {actual_type}")]
     IncompatibleDimensions {
@@ -35,17 +35,17 @@ pub enum TypeCheckError {
     #[error("Exponents need to be dimensionless (got {1}).")]
     NonScalarExponent(Span, BaseRepresentation),
 
-    #[error("Unsupported expression in const-evaluation of exponent: {0}.")]
-    UnsupportedConstEvalExpression(&'static str),
+    #[error("Unsupported expression in const-evaluation of exponent: {1}.")]
+    UnsupportedConstEvalExpression(Span, &'static str),
 
-    #[error("Division by zero in dimension exponent")]
-    DivisionByZeroInConstEvalExpression,
+    #[error("Division by zero in const. eval. expression")]
+    DivisionByZeroInConstEvalExpression(Span),
 
     #[error("{0}")]
     RegistryError(RegistryError),
 
     #[error("Incompatible alternative expressions have been provided for dimension '{0}'")]
-    IncompatibleAlternativeDimensionExpression(String),
+    IncompatibleAlternativeDimensionExpression(String), // TODO: add span information
 
     #[error("Function or procedure '{callable_name}' called with {num_args} arguments(s), but needs {}..{}", arity.start(), arity.end())]
     WrongArity {
@@ -56,22 +56,22 @@ pub enum TypeCheckError {
     },
 
     #[error("'{0}' can not be used as a type parameter because it is also an existing dimension identifier.")]
-    TypeParameterNameClash(String),
+    TypeParameterNameClash(String), // TODO: add span information
 
     #[error("Could not infer the type parameters {0} in the function call '{1}'.")]
-    CanNotInferTypeParameters(String, String),
+    CanNotInferTypeParameters(String, String), // TODO: add span information
 
     #[error("Multiple unresolved generic parameters in a single function parameter type are not (yet) supported. Consider reordering the function parameters")]
-    MultipleUnresolvedTypeParameters,
+    MultipleUnresolvedTypeParameters, // TODO: add span information
 
-    #[error("Foreign function definition '{0}' needs a return type annotation.")]
-    ForeignFunctionNeedsReturnTypeAnnotation(String),
+    #[error("Foreign function definition (without body) '{1}' needs a return type annotation.")]
+    ForeignFunctionNeedsReturnTypeAnnotation(Span, String),
 
-    #[error("Unknown foreign function '{0}'")]
-    UnknownForeignFunction(String),
+    #[error("Unknown foreign function (without body) '{1}'")]
+    UnknownForeignFunction(Span, String),
 
     #[error("Parameter types can not (yet) be deduced, they have to be specified manually: f(x: Length, y: Time) -> …")]
-    ParameterTypesCanNotBeDeduced,
+    ParameterTypesCanNotBeDeduced, // TODO: add span information
 }
 
 type Result<T> = std::result::Result<T, TypeCheckError>;
@@ -87,7 +87,7 @@ fn evaluate_const_expr(expr: &typed_ast::Expression) -> Result<Exponent> {
     match expr {
         typed_ast::Expression::Scalar(n) => Ok(to_rational_exponent(n.to_f64())),
         typed_ast::Expression::Negate(ref expr, _) => Ok(-evaluate_const_expr(expr)?),
-        typed_ast::Expression::BinaryOperator(op, lhs_expr, rhs_expr, _) => {
+        typed_ast::Expression::BinaryOperator(span, op, lhs_expr, rhs_expr, _) => {
             let lhs = evaluate_const_expr(lhs_expr)?;
             let rhs = evaluate_const_expr(rhs_expr)?;
             match op {
@@ -96,7 +96,7 @@ fn evaluate_const_expr(expr: &typed_ast::Expression) -> Result<Exponent> {
                 typed_ast::BinaryOperator::Mul => Ok(lhs * rhs),
                 typed_ast::BinaryOperator::Div => {
                     if rhs == Rational::zero() {
-                        Err(TypeCheckError::DivisionByZeroInConstEvalExpression)
+                        Err(TypeCheckError::DivisionByZeroInConstEvalExpression(*span))
                     } else {
                         Ok(lhs / rhs)
                     }
@@ -106,23 +106,24 @@ fn evaluate_const_expr(expr: &typed_ast::Expression) -> Result<Exponent> {
                         Ok(lhs.pow(rhs.to_integer() as i32)) // TODO: dangerous cast
                     } else {
                         Err(TypeCheckError::UnsupportedConstEvalExpression(
+                            *span,
                             "exponentiation with non-integer exponent",
                         ))
                     }
                 }
-                typed_ast::BinaryOperator::ConvertTo => {
-                    Err(TypeCheckError::UnsupportedConstEvalExpression("conversion"))
-                }
+                typed_ast::BinaryOperator::ConvertTo => Err(
+                    TypeCheckError::UnsupportedConstEvalExpression(Span::dummy(), "conversion"), // TODO
+                ),
             }
         }
-        typed_ast::Expression::Identifier(_, _) => {
-            Err(TypeCheckError::UnsupportedConstEvalExpression("identifier"))
-        }
-        typed_ast::Expression::UnitIdentifier(_, _, _, _) => {
-            Err(TypeCheckError::UnsupportedConstEvalExpression("identifier"))
-        }
+        typed_ast::Expression::Identifier(_, _) => Err(
+            TypeCheckError::UnsupportedConstEvalExpression(Span::dummy(), "identifier"), // TODO
+        ),
+        typed_ast::Expression::UnitIdentifier(_, _, _, _) => Err(
+            TypeCheckError::UnsupportedConstEvalExpression(Span::dummy(), "identifier"), // TODO
+        ),
         typed_ast::Expression::FunctionCall(_, _, _) => Err(
-            TypeCheckError::UnsupportedConstEvalExpression("function call"),
+            TypeCheckError::UnsupportedConstEvalExpression(Span::dummy(), "function call"), // TODO
         ),
     }
 }
@@ -165,6 +166,14 @@ impl TypeChecker {
                 rhs,
                 span_op,
             } => {
+                let full_span = ast::Expression::BinaryOperator {
+                    op,
+                    lhs: lhs.clone(),
+                    rhs: rhs.clone(),
+                    span_op,
+                }
+                .full_span();
+
                 let lhs_checked = self.check_expression((*lhs).clone())?;
                 let rhs_checked = self.check_expression((*rhs).clone())?;
 
@@ -173,15 +182,7 @@ impl TypeChecker {
                     let rhs_type = rhs_checked.get_type();
                     if lhs_type != rhs_type {
                         Err(TypeCheckError::IncompatibleDimensions {
-                            span_operation: span_op.unwrap_or(
-                                ast::Expression::BinaryOperator {
-                                    op,
-                                    lhs: lhs.clone(),
-                                    rhs: rhs.clone(),
-                                    span_op,
-                                }
-                                .full_span(),
-                            ),
+                            span_operation: span_op.unwrap_or(full_span),
                             operation: match op {
                                 typed_ast::BinaryOperator::Add => "addition".into(),
                                 typed_ast::BinaryOperator::Sub => "subtraction".into(),
@@ -235,6 +236,7 @@ impl TypeChecker {
                 };
 
                 typed_ast::Expression::BinaryOperator(
+                    full_span,
                     op,
                     Box::new(lhs_checked),
                     Box::new(rhs_checked),
@@ -245,7 +247,7 @@ impl TypeChecker {
                 let (type_parameters, parameter_types, is_variadic, return_type) = self
                     .function_signatures
                     .get(&function_name)
-                    .ok_or_else(|| TypeCheckError::UnknownFunction(function_name.clone()))?;
+                    .ok_or_else(|| TypeCheckError::UnknownFunction(span, function_name.clone()))?;
 
                 let arity_range = if *is_variadic {
                     1..=usize::MAX
@@ -543,11 +545,15 @@ impl TypeChecker {
                     return_type_deduced
                 } else {
                     if !ffi::functions().contains_key(function_name.as_str()) {
-                        return Err(TypeCheckError::UnknownForeignFunction(function_name));
+                        return Err(TypeCheckError::UnknownForeignFunction(
+                            function_name_span,
+                            function_name,
+                        ));
                     }
 
                     return_type_specified.ok_or_else(|| {
                         TypeCheckError::ForeignFunctionNeedsReturnTypeAnnotation(
+                            function_name_span,
                             function_name.clone(),
                         )
                     })?
@@ -751,12 +757,12 @@ mod tests {
         assert!(matches!(
             get_typecheck_error("let x=2
                                  a^x"),
-            TypeCheckError::UnsupportedConstEvalExpression(desc) if desc == "identifier"
+            TypeCheckError::UnsupportedConstEvalExpression(_, desc) if desc == "identifier"
         ));
 
         assert!(matches!(
             get_typecheck_error("a^(3/(1-1))"),
-            TypeCheckError::DivisionByZeroInConstEvalExpression
+            TypeCheckError::DivisionByZeroInConstEvalExpression(_)
         ));
     }
 
@@ -904,7 +910,7 @@ mod tests {
     fn unknown_function() {
         assert!(matches!(
             get_typecheck_error("foo(2)"),
-            TypeCheckError::UnknownFunction(name) if name == "foo"
+            TypeCheckError::UnknownFunction(_, name) if name == "foo"
         ));
     }
 
@@ -971,7 +977,7 @@ mod tests {
     fn foreign_function_with_missing_return_type() {
         assert!(matches!(
             get_typecheck_error("fn sin(x: Scalar)"),
-            TypeCheckError::ForeignFunctionNeedsReturnTypeAnnotation(name) if name == "sin"
+            TypeCheckError::ForeignFunctionNeedsReturnTypeAnnotation(_, name) if name == "sin"
         ));
     }
 
@@ -979,7 +985,7 @@ mod tests {
     fn unknown_foreign_function() {
         assert!(matches!(
             get_typecheck_error("fn foo(x: Scalar) -> Scalar"),
-            TypeCheckError::UnknownForeignFunction(name) if name == "foo"
+            TypeCheckError::UnknownForeignFunction(_, name) if name == "foo"
         ));
     }
 

+ 2 - 2
numbat/src/typed_ast.rs

@@ -11,7 +11,7 @@ pub enum Expression {
     Identifier(String, Type),
     UnitIdentifier(Prefix, String, String, Type),
     Negate(Box<Expression>, Type),
-    BinaryOperator(BinaryOperator, Box<Expression>, Box<Expression>, Type),
+    BinaryOperator(Span, BinaryOperator, Box<Expression>, Box<Expression>, Type),
     FunctionCall(String, Vec<Expression>, Type),
 }
 
@@ -38,7 +38,7 @@ impl Expression {
             Expression::Identifier(_, type_) => type_.clone(),
             Expression::UnitIdentifier(_, _, _, _type) => _type.clone(),
             Expression::Negate(_, type_) => type_.clone(),
-            Expression::BinaryOperator(_, _, _, type_) => type_.clone(),
+            Expression::BinaryOperator(_, _, _, _, type_) => type_.clone(),
             Expression::FunctionCall(_, _, type_) => type_.clone(),
         }
     }

+ 3 - 5
numbat/tests/interpreter.rs

@@ -26,6 +26,7 @@ fn expect_failure(code: &str, msg_part: &str) {
     let mut ctx = get_test_context();
     if let Err(e) = ctx.interpret(code, CodeSource::Text) {
         let error_message = e.to_string();
+        println!("{}", error_message);
         assert!(error_message.contains(msg_part));
     } else {
         panic!();
@@ -109,7 +110,7 @@ fn test_math() {
     expect_output("atan2(100 cm, 1 m) / (pi / 4)", "1");
     expect_failure(
         "atan2(100 cm, 1 m²)",
-        "Incompatible dimensions in argument 2 of function call to 'atan2'",
+        "parameter type: Length\n argument type: Length²",
     );
 
     expect_output("5 % 3", "2");
@@ -117,10 +118,7 @@ fn test_math() {
     expect_output("8 cm % 5 cm", "3 cm");
     expect_output("235 cm % 1 m", "35 cm");
     expect_output("2 m % 7 cm", "0.04 m");
-    expect_failure(
-        "8 m % 5 s",
-        "Incompatible dimensions in argument 2 of function call to 'mod'",
-    )
+    expect_failure("8 m % 5 s", "parameter type: Length\n argument type: Time")
 }
 
 #[test]