Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Improve "type annotations needed" errors #5830

Merged
merged 3 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/noirc_frontend/src/elaborator/statements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ impl<'context> Elaborator<'context> {
// If we get here the type has no field named 'access.rhs'.
// Now we specialize the error message based on whether we know the object type in question yet.
if let Type::TypeVariable(..) = &lhs_type {
self.push_err(TypeCheckError::TypeAnnotationsNeeded { span });
self.push_err(TypeCheckError::TypeAnnotationsNeededForFieldAccess { span });
} else if lhs_type != Type::Error {
self.push_err(TypeCheckError::AccessUnknownMember {
lhs_type,
Expand Down
42 changes: 30 additions & 12 deletions compiler/noirc_frontend/src/elaborator/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ use crate::{
SecondaryAttribute, Signedness, UnaryOp, UnresolvedType, UnresolvedTypeData,
},
node_interner::{
DefinitionKind, DependencyId, ExprId, FuncId, GlobalId, TraitId, TraitImplKind,
TraitMethodId,
DefinitionKind, DependencyId, ExprId, FuncId, GlobalId, ImplSearchErrorKind, TraitId,
TraitImplKind, TraitMethodId,
},
Generics, Kind, ResolvedGeneric, Type, TypeBinding, TypeBindings, TypeVariable,
TypeVariableKind,
Expand Down Expand Up @@ -122,7 +122,7 @@ impl<'context> Elaborator<'context> {
Unit => Type::Unit,
Unspecified => {
let span = typ.span;
self.push_err(TypeCheckError::TypeAnnotationsNeeded { span });
self.push_err(TypeCheckError::UnspecifiedType { span });
Type::Error
}
Error => Type::Error,
Expand Down Expand Up @@ -482,7 +482,7 @@ impl<'context> Elaborator<'context> {
match self.interner.lookup_trait_implementation(&object_type, trait_id, &ordered, &named) {
Ok(impl_kind) => self.get_associated_type_from_trait_impl(path, impl_kind),
Err(constraints) => {
self.push_trait_constraint_error(constraints, span);
self.push_trait_constraint_error(&object_type, constraints, span);
Type::Error
}
}
Expand Down Expand Up @@ -1332,7 +1332,7 @@ impl<'context> Elaborator<'context> {

// The type variable must be unbound at this point since follow_bindings was called
Type::TypeVariable(_, TypeVariableKind::Normal) => {
self.push_err(TypeCheckError::TypeAnnotationsNeeded { span });
self.push_err(TypeCheckError::TypeAnnotationsNeededForMethodCall { span });
None
}

Expand Down Expand Up @@ -1596,16 +1596,34 @@ impl<'context> Elaborator<'context> {
Ok(impl_kind) => {
self.interner.select_impl_for_expression(function_ident_id, impl_kind);
}
Err(constraints) => self.push_trait_constraint_error(constraints, span),
Err(error) => self.push_trait_constraint_error(object_type, error, span),
}
}

fn push_trait_constraint_error(&mut self, constraints: Vec<TraitConstraint>, span: Span) {
if constraints.is_empty() {
self.push_err(TypeCheckError::TypeAnnotationsNeeded { span });
} else if let Some(error) = NoMatchingImplFoundError::new(self.interner, constraints, span)
{
self.push_err(TypeCheckError::NoMatchingImplFound(error));
fn push_trait_constraint_error(
&mut self,
object_type: &Type,
error: ImplSearchErrorKind,
span: Span,
) {
match error {
ImplSearchErrorKind::TypeAnnotationsNeededOnObjectType => {
self.push_err(TypeCheckError::TypeAnnotationsNeededForMethodCall { span });
}
ImplSearchErrorKind::Nested(constraints) => {
if let Some(error) = NoMatchingImplFoundError::new(self.interner, constraints, span)
{
self.push_err(TypeCheckError::NoMatchingImplFound(error));
}
}
ImplSearchErrorKind::MultipleMatching(candidates) => {
let object_type = object_type.clone();
self.push_err(TypeCheckError::MultipleMatchingImpls {
object_type,
span,
candidates,
});
}
}
}

Expand Down
34 changes: 32 additions & 2 deletions compiler/noirc_frontend/src/hir/comptime/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,19 @@
FunctionAlreadyResolved {
location: Location,
},
MultipleMatchingImpls {
object_type: Type,
candidates: Vec<String>,
location: Location,
},

Unimplemented {
item: String,
location: Location,
},
TypeAnnotationsNeededForMethodCall {
location: Location,
},

// These cases are not errors, they are just used to prevent us from running more code
// until the loop can be resumed properly. These cases will never be displayed to users.
Expand Down Expand Up @@ -257,8 +265,10 @@
| InterpreterError::ContinueNotInLoop { location, .. }
| InterpreterError::TraitDefinitionMustBeAPath { location }
| InterpreterError::FailedToResolveTraitDefinition { location }
| InterpreterError::FailedToResolveTraitBound { location, .. } => *location,
InterpreterError::FunctionAlreadyResolved { location, .. } => *location,
| InterpreterError::FailedToResolveTraitBound { location, .. }
| InterpreterError::FunctionAlreadyResolved { location, .. }
| InterpreterError::MultipleMatchingImpls { location, .. }
| InterpreterError::TypeAnnotationsNeededForMethodCall { location } => *location,

InterpreterError::FailedToParseMacro { error, file, .. } => {
Location::new(error.span(), *file)
Expand Down Expand Up @@ -445,7 +455,7 @@
let message = format!("Failed to parse macro's token stream into {rule}");
let tokens = vecmap(tokens.iter(), ToString::to_string).join(" ");

// 10 is an aribtrary number of tokens here chosen to fit roughly onto one line

Check warning on line 458 in compiler/noirc_frontend/src/hir/comptime/errors.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (aribtrary)
let token_stream = if tokens.len() > 10 {
format!("The resulting token stream was: {tokens}")
} else {
Expand Down Expand Up @@ -527,6 +537,26 @@
.to_string();
CustomDiagnostic::simple_error(msg, secondary, location.span)
}
InterpreterError::MultipleMatchingImpls { object_type, candidates, location } => {
let message = format!("Multiple trait impls match the object type `{object_type}`");
let secondary = "Ambiguous impl".to_string();
let mut error = CustomDiagnostic::simple_error(message, secondary, location.span);
for (i, candidate) in candidates.iter().enumerate() {
error.add_note(format!("Candidate {}: `{candidate}`", i + 1));
}
error
}
InterpreterError::TypeAnnotationsNeededForMethodCall { location } => {
let mut error = CustomDiagnostic::simple_error(
"Object type is unknown in method call".to_string(),
"Type must be known by this point to know which method to call".to_string(),
location.span,
);
let message =
"Try adding a type annotation for the object type before this method call";
error.add_note(message.to_string());
error
}
}
}
}
50 changes: 43 additions & 7 deletions compiler/noirc_frontend/src/hir/type_check/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,12 @@ pub enum TypeCheckError {
second_type: String,
second_index: usize,
},
#[error("Cannot infer type of expression, type annotations needed before this point")]
TypeAnnotationsNeeded { span: Span },
#[error("Object type is unknown in method call")]
TypeAnnotationsNeededForMethodCall { span: Span },
#[error("Object type is unknown in field access")]
TypeAnnotationsNeededForFieldAccess { span: Span },
#[error("Multiple trait impls may apply to this object type")]
MultipleMatchingImpls { object_type: Type, candidates: Vec<String>, span: Span },
#[error("use of deprecated function {name}")]
CallDeprecated { name: String, note: Option<String>, span: Span },
#[error("{0}")]
Expand Down Expand Up @@ -166,6 +170,10 @@ pub enum TypeCheckError {
NoSuchNamedTypeArg { name: Ident, item: String },
#[error("`{item}` is missing the associated type `{name}`")]
MissingNamedTypeArg { name: Rc<String>, item: String, span: Span },
#[error("Internal compiler error: type unspecified for value")]
UnspecifiedType { span: Span },
#[error("Binding `{typ}` here to the `_` inside would create a cyclic type")]
CyclicType { typ: Type, span: Span },
}

#[derive(Debug, Clone, PartialEq, Eq)]
Expand Down Expand Up @@ -286,11 +294,33 @@ impl<'a> From<&'a TypeCheckError> for Diagnostic {
format!("return type is {typ}"),
*span,
),
TypeCheckError::TypeAnnotationsNeeded { span } => Diagnostic::simple_error(
"Expression type is ambiguous".to_string(),
"Type must be known at this point".to_string(),
*span,
),
TypeCheckError::TypeAnnotationsNeededForMethodCall { span } => {
let mut error = Diagnostic::simple_error(
"Object type is unknown in method call".to_string(),
"Type must be known by this point to know which method to call".to_string(),
*span,
);
error.add_note("Try adding a type annotation for the object type before this method call".to_string());
error
},
TypeCheckError::TypeAnnotationsNeededForFieldAccess { span } => {
let mut error = Diagnostic::simple_error(
"Object type is unknown in field access".to_string(),
"Type must be known by this point".to_string(),
*span,
);
error.add_note("Try adding a type annotation for the object type before this expression".to_string());
error
},
TypeCheckError::MultipleMatchingImpls { object_type, candidates, span } => {
let message = format!("Multiple trait impls match the object type `{object_type}`");
let secondary = "Ambiguous impl".to_string();
let mut error = Diagnostic::simple_error(message, secondary, *span);
for (i, candidate) in candidates.iter().enumerate() {
error.add_note(format!("Candidate {}: `{candidate}`", i + 1));
}
error
},
TypeCheckError::ResolverError(error) => error.into(),
TypeCheckError::TypeMismatchWithSource { expected, actual, span, source } => {
let message = match source {
Expand Down Expand Up @@ -388,6 +418,12 @@ impl<'a> From<&'a TypeCheckError> for Diagnostic {
TypeCheckError::UnsafeFn { span } => {
Diagnostic::simple_warning(error.to_string(), String::new(), *span)
}
TypeCheckError::UnspecifiedType { span } => {
Diagnostic::simple_error(error.to_string(), String::new(), *span)
}
TypeCheckError::CyclicType { typ: _, span } => {
Diagnostic::simple_error(error.to_string(), "Cyclic types have unlimited size and are prohibited in Noir".into(), *span)
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_frontend/src/hir_def/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@
};

if binding.occurs(id) {
Err(TypeCheckError::TypeAnnotationsNeeded { span })
Err(TypeCheckError::CyclicType { span, typ: binding })
} else {
*self.1.borrow_mut() = TypeBinding::Bound(binding);
Ok(())
Expand Down Expand Up @@ -2094,7 +2094,7 @@
}

let recur_on_binding = |id, replacement: &Type| {
// Prevent recuring forever if there's a `T := T` binding

Check warning on line 2097 in compiler/noirc_frontend/src/hir_def/types.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (recuring)
if replacement.type_variable_id() == Some(id) {
replacement.clone()
} else {
Expand Down Expand Up @@ -2165,7 +2165,7 @@
Type::Tuple(fields)
}
Type::Forall(typevars, typ) => {
// Trying to substitute_helper a variable de, substitute_bound_typevarsfined within a nested Forall

Check warning on line 2168 in compiler/noirc_frontend/src/hir_def/types.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (typevarsfined)
// is usually impossible and indicative of an error in the type checker somewhere.
for var in typevars {
assert!(!type_bindings.contains_key(&var.id()));
Expand Down Expand Up @@ -2332,7 +2332,7 @@

/// Replace any `Type::NamedGeneric` in this type with a `Type::TypeVariable`
/// using to the same inner `TypeVariable`. This is used during monomorphization
/// to bind to named generics since they are unbindable during type checking.

Check warning on line 2335 in compiler/noirc_frontend/src/hir_def/types.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (unbindable)
pub fn replace_named_generics_with_type_variables(&mut self) {
match self {
Type::FieldElement
Expand Down
28 changes: 16 additions & 12 deletions compiler/noirc_frontend/src/monomorphization/errors.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
use noirc_errors::{CustomDiagnostic, FileDiagnostic, Location};

use crate::hir::comptime::InterpreterError;
use crate::{hir::comptime::InterpreterError, Type};

#[derive(Debug)]
pub enum MonomorphizationError {
UnknownArrayLength { location: Location },
TypeAnnotationsNeeded { location: Location },
UnknownArrayLength { length: Type, location: Location },
NoDefaultType { location: Location },
InternalError { message: &'static str, location: Location },
InterpreterError(InterpreterError),
}

impl MonomorphizationError {
fn location(&self) -> Location {
match self {
MonomorphizationError::UnknownArrayLength { location }
MonomorphizationError::UnknownArrayLength { location, .. }
| MonomorphizationError::InternalError { location, .. }
| MonomorphizationError::TypeAnnotationsNeeded { location } => *location,
| MonomorphizationError::NoDefaultType { location, .. } => *location,
MonomorphizationError::InterpreterError(error) => error.get_location(),
}
}
Expand All @@ -32,16 +32,20 @@ impl From<MonomorphizationError> for FileDiagnostic {

impl MonomorphizationError {
fn into_diagnostic(self) -> CustomDiagnostic {
let message = match self {
MonomorphizationError::UnknownArrayLength { .. } => {
"Length of generic array could not be determined."
let message = match &self {
MonomorphizationError::UnknownArrayLength { length, .. } => {
format!("ICE: Could not determine array length `{length}`")
}
MonomorphizationError::TypeAnnotationsNeeded { .. } => "Type annotations needed",
MonomorphizationError::InterpreterError(error) => return (&error).into(),
MonomorphizationError::InternalError { message, .. } => message,
MonomorphizationError::NoDefaultType { location } => {
let message = "Type annotation needed".into();
let secondary = "Could not determine type of generic argument".into();
return CustomDiagnostic::simple_error(message, secondary, location.span);
}
MonomorphizationError::InterpreterError(error) => return error.into(),
MonomorphizationError::InternalError { message, .. } => message.to_string(),
};

let location = self.location();
CustomDiagnostic::simple_error(message.into(), String::new(), location.span)
CustomDiagnostic::simple_error(message, String::new(), location.span)
}
}
29 changes: 20 additions & 9 deletions compiler/noirc_frontend/src/monomorphization/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
use crate::ast::{FunctionKind, IntegerBitSize, Signedness, UnaryOp, Visibility};
use crate::hir::comptime::InterpreterError;
use crate::hir::type_check::NoMatchingImplFoundError;
use crate::node_interner::ExprId;
use crate::node_interner::{ExprId, ImplSearchErrorKind};
use crate::{
debug::DebugInstrumenter,
hir_def::{
Expand Down Expand Up @@ -569,7 +569,7 @@ impl<'interner> Monomorphizer<'interner> {

let length = length.evaluate_to_u32().ok_or_else(|| {
let location = self.interner.expr_location(&array);
MonomorphizationError::UnknownArrayLength { location }
MonomorphizationError::UnknownArrayLength { location, length }
})?;

let contents = try_vecmap(0..length, |_| self.expr(repeated_element))?;
Expand Down Expand Up @@ -936,7 +936,10 @@ impl<'interner> Monomorphizer<'interner> {
let element = Box::new(Self::convert_type(element.as_ref(), location)?);
let length = match length.evaluate_to_u32() {
Some(length) => length,
None => return Err(MonomorphizationError::TypeAnnotationsNeeded { location }),
None => {
let length = length.as_ref().clone();
return Err(MonomorphizationError::UnknownArrayLength { location, length });
}
};
ast::Type::Array(length, element)
}
Expand Down Expand Up @@ -969,7 +972,7 @@ impl<'interner> Monomorphizer<'interner> {
// and within a larger generic type.
let default = match kind.default_type() {
Some(typ) => typ,
None => return Err(MonomorphizationError::TypeAnnotationsNeeded { location }),
None => return Err(MonomorphizationError::NoDefaultType { location }),
};

let monomorphized_default = Self::convert_type(&default, location)?;
Expand Down Expand Up @@ -1074,7 +1077,7 @@ impl<'interner> Monomorphizer<'interner> {
// and within a larger generic type.
let default = match kind.default_type() {
Some(typ) => typ,
None => return Err(MonomorphizationError::TypeAnnotationsNeeded { location }),
None => return Err(MonomorphizationError::NoDefaultType { location }),
};

Self::check_type(&default, location)
Expand Down Expand Up @@ -1946,6 +1949,7 @@ pub fn resolve_trait_method(
let impl_id = match trait_impl {
TraitImplKind::Normal(impl_id) => impl_id,
TraitImplKind::Assumed { object_type, trait_generics } => {
let location = interner.expr_location(&expr_id);
match interner.lookup_trait_implementation(
&object_type,
method.trait_id,
Expand All @@ -1954,21 +1958,28 @@ pub fn resolve_trait_method(
) {
Ok(TraitImplKind::Normal(impl_id)) => impl_id,
Ok(TraitImplKind::Assumed { .. }) => {
let location = interner.expr_location(&expr_id);
return Err(InterpreterError::NoImpl { location });
}
Err(constraints) => {
let location = interner.expr_location(&expr_id);
Err(ImplSearchErrorKind::TypeAnnotationsNeededOnObjectType) => {
return Err(InterpreterError::TypeAnnotationsNeededForMethodCall { location });
}
Err(ImplSearchErrorKind::Nested(constraints)) => {
if let Some(error) =
NoMatchingImplFoundError::new(interner, constraints, location.span)
{
let file = location.file;
return Err(InterpreterError::NoMatchingImplFound { error, file });
} else {
let location = interner.expr_location(&expr_id);
return Err(InterpreterError::NoImpl { location });
}
}
Err(ImplSearchErrorKind::MultipleMatching(candidates)) => {
return Err(InterpreterError::MultipleMatchingImpls {
object_type,
location,
candidates,
});
}
}
}
};
Expand Down
Loading
Loading