Преглед изворни кода

Add spans to remaining type check errors

David Peter пре 2 година
родитељ
комит
5533d88fbf

+ 2 - 0
examples/typecheck_error/can_not_infer_type_parameters.nbt

@@ -0,0 +1,2 @@
+fn foo<D0, D1>(x: D0, y: D0) -> Scalar = 1
+foo(2, 3)

+ 3 - 0
examples/typecheck_error/multiple_unresolved_type_parameters.nbt

@@ -0,0 +1,3 @@
+fn foo<T1, T2>(x: T1*T2, y: T2) -> T1 = x / y
+
+foo(2 meter * second, 3 second)

+ 1 - 0
examples/typecheck_error/parameter_types_can_not_be_deduced.nbt

@@ -0,0 +1 @@
+fn f(x, y) -> Scalar = x * y

+ 1 - 0
examples/typecheck_error/type_parameter_name_clash.nbt

@@ -0,0 +1 @@
+fn foo<Length>(x: Length) -> Scalar = 1

+ 8 - 3
numbat/src/ast.rs

@@ -368,7 +368,7 @@ pub enum Statement {
     DeclareFunction {
         function_name_span: Span,
         function_name: String,
-        type_parameters: Vec<String>,
+        type_parameters: Vec<(Span, String)>,
         /// Parameters, optionally with type annotations. The boolean argument specifies whether or not the parameter is variadic
         parameters: Vec<(Span, String, Option<DimensionExpression>, bool)>,
         /// Function body. If it is absent, the function is implemented via FFI
@@ -479,7 +479,9 @@ impl PrettyPrint for Statement {
                 } else {
                     m::operator("<")
                         + Itertools::intersperse(
-                            type_parameters.iter().map(m::type_identifier),
+                            type_parameters
+                                .iter()
+                                .map(|(_, name)| m::type_identifier(name)),
                             m::operator(", "),
                         )
                         .sum()
@@ -696,7 +698,10 @@ impl ReplaceSpans for Statement {
             } => Statement::DeclareFunction {
                 function_name_span: Span::dummy(),
                 function_name: function_name.clone(),
-                type_parameters: type_parameters.clone(),
+                type_parameters: type_parameters
+                    .iter()
+                    .map(|(_, name)| (Span::dummy(), name.clone()))
+                    .collect(),
                 parameters: parameters
                     .iter()
                     .map(|(_, name, type_, is_variadic)| {

+ 29 - 5
numbat/src/diagnostic.rs

@@ -143,15 +143,37 @@ impl ErrorDiagnostic for TypeCheckError {
                     labels.insert(
                         0,
                         span.diagnostic_label(LabelStyle::Secondary)
-                            .with_message("The function defined here"),
+                            .with_message("The function defined here"),
                     );
                 }
 
                 d.with_labels(labels)
             }
-            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::TypeParameterNameClash(span, _) => d.with_labels(vec![span
+                .diagnostic_label(LabelStyle::Primary)
+                .with_message(inner_error)]),
+            TypeCheckError::CanNotInferTypeParameters(
+                span,
+                callable_definition_span,
+                _,
+                params,
+            ) => d.with_labels(vec![
+                span.diagnostic_label(LabelStyle::Primary)
+                    .with_message(format!("… could not be infered for this function call")),
+                callable_definition_span
+                    .diagnostic_label(LabelStyle::Secondary)
+                    .with_message(format!(
+                        "The type parameter(s) {params} in this generic function"
+                    )),
+            ]),
+            TypeCheckError::MultipleUnresolvedTypeParameters(span, parameter_span) => d
+                .with_labels(vec![
+                    span.diagnostic_label(LabelStyle::Secondary)
+                        .with_message("In this function call"),
+                    parameter_span
+                        .diagnostic_label(LabelStyle::Primary)
+                        .with_message(inner_error),
+                ]),
             TypeCheckError::ForeignFunctionNeedsReturnTypeAnnotation(span, _) => {
                 d.with_labels(vec![span
                     .diagnostic_label(LabelStyle::Primary)
@@ -160,7 +182,9 @@ impl ErrorDiagnostic for TypeCheckError {
             TypeCheckError::UnknownForeignFunction(span, _) => d.with_labels(vec![span
                 .diagnostic_label(LabelStyle::Primary)
                 .with_message(inner_error)]),
-            TypeCheckError::ParameterTypesCanNotBeDeduced => d.with_notes(vec![inner_error]),
+            TypeCheckError::ParameterTypesCanNotBeDeduced(span) => d.with_labels(vec![span
+                .diagnostic_label(LabelStyle::Primary)
+                .with_message(inner_error)]),
         }
     }
 }

+ 5 - 4
numbat/src/parser.rs

@@ -274,7 +274,8 @@ impl<'a> Parser<'a> {
                 if self.match_exact(TokenKind::LeftAngleBracket).is_some() {
                     while self.match_exact(TokenKind::RightAngleBracket).is_none() {
                         if let Some(type_parameter_name) = self.match_exact(TokenKind::Identifier) {
-                            type_parameters.push(type_parameter_name.lexeme.to_string());
+                            let span = self.last().unwrap().span;
+                            type_parameters.push((span, type_parameter_name.lexeme.to_string()));
 
                             if self.match_exact(TokenKind::Comma).is_none()
                                 && self.peek().kind != TokenKind::RightAngleBracket
@@ -375,7 +376,7 @@ impl<'a> Parser<'a> {
                 Ok(Statement::DeclareFunction {
                     function_name_span,
                     function_name: fn_name.lexeme.clone(),
-                    type_parameters: type_parameters,
+                    type_parameters,
                     parameters,
                     body,
                     return_type_span,
@@ -1539,7 +1540,7 @@ mod tests {
             Statement::DeclareFunction {
                 function_name_span: Span::dummy(),
                 function_name: "foo".into(),
-                type_parameters: vec!["X".into()],
+                type_parameters: vec![(Span::dummy(), "X".into())],
                 parameters: vec![(
                     Span::dummy(),
                     "x".into(),
@@ -1557,7 +1558,7 @@ mod tests {
             Statement::DeclareFunction {
                 function_name_span: Span::dummy(),
                 function_name: "foo".into(),
-                type_parameters: vec!["D".into()],
+                type_parameters: vec![(Span::dummy(), "D".into())],
                 parameters: vec![(
                     Span::dummy(),
                     "x".into(),

+ 35 - 23
numbat/src/typechecker.rs

@@ -56,23 +56,23 @@ pub enum TypeCheckError {
         num_args: usize,
     },
 
-    #[error("'{0}' can not be used as a type parameter because it is also an existing dimension identifier.")]
-    TypeParameterNameClash(String), // TODO: add span information
+    #[error("'{1}' can not be used as a type parameter because it is also an existing dimension identifier.")]
+    TypeParameterNameClash(Span, String),
 
-    #[error("Could not infer the type parameters {0} in the function call '{1}'.")]
-    CanNotInferTypeParameters(String, String), // TODO: add span information
+    #[error("Could not infer the type parameters {3} in the function call '{2}'.")]
+    CanNotInferTypeParameters(Span, Span, String, String),
 
     #[error("Multiple unresolved generic parameters in a single function parameter type are not (yet) supported. Consider reordering the function parameters")]
-    MultipleUnresolvedTypeParameters, // TODO: add span information
+    MultipleUnresolvedTypeParameters(Span, Span),
+
+    #[error("Parameter types can not (yet) be deduced, they have to be specified manually: f(x: Length, y: Time) -> …")]
+    ParameterTypesCanNotBeDeduced(Span),
 
     #[error("Foreign function definition (without body) '{1}' needs a return type annotation.")]
     ForeignFunctionNeedsReturnTypeAnnotation(Span, 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, // TODO: add span information
 }
 
 type Result<T> = std::result::Result<T, TypeCheckError>;
@@ -132,7 +132,8 @@ fn evaluate_const_expr(expr: &typed_ast::Expression) -> Result<Exponent> {
 #[derive(Clone, Default)]
 pub struct TypeChecker {
     identifiers: HashMap<String, Type>,
-    function_signatures: HashMap<String, (Span, Vec<String>, Vec<(Span, Type)>, bool, Type)>,
+    function_signatures:
+        HashMap<String, (Span, Vec<(Span, String)>, Vec<(Span, Type)>, bool, Type)>,
     registry: DimensionRegistry,
 }
 
@@ -306,18 +307,23 @@ impl TypeChecker {
                     }
                 }
 
-                for (idx, ((_, parameter_type), argument_type)) in
+                for (idx, ((parameter_span, parameter_type), argument_type)) in
                     parameter_types.iter().zip(argument_types).enumerate()
                 {
                     let mut parameter_type = substitute(&substitutions, parameter_type);
 
                     let remaining_generic_subtypes: Vec<_> = parameter_type
                         .iter()
-                        .filter(|BaseRepresentationFactor(name, _)| type_parameters.contains(name))
+                        .filter(|BaseRepresentationFactor(name, _)| {
+                            type_parameters.iter().any(|(_, n)| name == n)
+                        })
                         .collect();
 
                     if remaining_generic_subtypes.len() > 1 {
-                        return Err(TypeCheckError::MultipleUnresolvedTypeParameters);
+                        return Err(TypeCheckError::MultipleUnresolvedTypeParameters(
+                            span,
+                            *parameter_span,
+                        ));
                     }
 
                     if let Some(&generic_subtype_factor) = remaining_generic_subtypes.first() {
@@ -364,7 +370,11 @@ impl TypeChecker {
                 }
 
                 if substitutions.len() != type_parameters.len() {
-                    let parameters: HashSet<String> = type_parameters.iter().cloned().collect();
+                    let parameters: HashSet<String> = type_parameters
+                        .iter()
+                        .map(|(_, name)| name)
+                        .cloned()
+                        .collect();
                     let inferred_parameters: HashSet<String> =
                         substitutions.iter().map(|t| t.0.clone()).collect();
 
@@ -374,6 +384,8 @@ impl TypeChecker {
                         .collect();
 
                     return Err(TypeCheckError::CanNotInferTypeParameters(
+                        span,
+                        *callable_definition_span,
                         function_name.clone(),
                         remaining.join(", "),
                     ));
@@ -491,10 +503,10 @@ impl TypeChecker {
             } => {
                 let mut typechecker_fn = self.clone();
 
-                for type_parameter in &type_parameters {
+                for (span, type_parameter) in &type_parameters {
                     match typechecker_fn.registry.add_base_dimension(type_parameter) {
                         Err(RegistryError::EntryExists(name)) => {
-                            return Err(TypeCheckError::TypeParameterNameClash(name))
+                            return Err(TypeCheckError::TypeParameterNameClash(*span, name))
                         }
                         Err(err) => return Err(TypeCheckError::RegistryError(err)),
                         _ => {}
@@ -506,9 +518,9 @@ impl TypeChecker {
                 for (parameter_span, parameter, optional_dexpr, p_is_variadic) in parameters {
                     let parameter_type = typechecker_fn
                         .registry
-                        .get_base_representation(
-                            &optional_dexpr.ok_or(TypeCheckError::ParameterTypesCanNotBeDeduced)?,
-                        )
+                        .get_base_representation(&optional_dexpr.ok_or(
+                            TypeCheckError::ParameterTypesCanNotBeDeduced(parameter_span),
+                        )?)
                         // TODO: add type inference, see https://github.com/sharkdp/numbat/issues/29
                         // TODO: once we add type inference, make sure that annotations are required for foreign functions
                         .map_err(TypeCheckError::RegistryError)?;
@@ -875,7 +887,7 @@ mod tests {
                 foo(2)
             "
             ),
-            TypeCheckError::MultipleUnresolvedTypeParameters
+            TypeCheckError::MultipleUnresolvedTypeParameters(..)
         ));
     }
 
@@ -886,7 +898,7 @@ mod tests {
                 fn foo<D0>(x: Scalar) -> Scalar = 1
                 foo(2)
             "),
-            TypeCheckError::CanNotInferTypeParameters(function_name, parameters) if function_name == "foo" && parameters == "D0"
+            TypeCheckError::CanNotInferTypeParameters(_, _, function_name, parameters) if function_name == "foo" && parameters == "D0"
         ));
 
         assert!(matches!(
@@ -894,7 +906,7 @@ mod tests {
                 fn foo<D0, D1>(x: D0, y: D0) -> Scalar = 1
                 foo(2, 3)
             "),
-            TypeCheckError::CanNotInferTypeParameters(function_name, parameters) if function_name == "foo" && parameters == "D1"
+            TypeCheckError::CanNotInferTypeParameters(_, _, function_name, parameters) if function_name == "foo" && parameters == "D1"
         ));
 
         assert!(matches!(
@@ -902,7 +914,7 @@ mod tests {
                 fn foo<D0, D1>(x: Scalar, y: Scalar) -> Scalar = 1
                 foo(2, 3)
             "),
-            TypeCheckError::CanNotInferTypeParameters(function_name, parameters) if function_name == "foo" && (parameters == "D1, D0" || parameters == "D0, D1")
+            TypeCheckError::CanNotInferTypeParameters(_, _, function_name, parameters) if function_name == "foo" && (parameters == "D1, D0" || parameters == "D0, D1")
         ));
     }
 
@@ -913,7 +925,7 @@ mod tests {
                 dimension Existing
                 fn f<Existing>(x: Existing) = 1
             "),
-            TypeCheckError::TypeParameterNameClash(name) if name == "Existing"
+            TypeCheckError::TypeParameterNameClash(_, name) if name == "Existing"
         ));
     }