Provide better compiler feedback for type holes in annotations.

It is now possible to leave a hole in a type annotation and have the compiler fill-in the expected type of us.
  This is a pretty useful debugging tool when playing with complex functions.
This commit is contained in:
KtorZ 2023-03-16 13:58:46 +01:00
parent 0e7494541d
commit d59305a1b0
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
5 changed files with 54 additions and 48 deletions

View File

@ -8,12 +8,15 @@
### Changed ### Changed
- **aiken-project**: tests filtering with `-m` during check now happens in `Project::collect_tests`
- **aiken-project**: fixed generation of blueprints for recursive and mutually recursive data-types
- **aiken-lang**: block `Data` and `String` from unifying when casting - **aiken-lang**: block `Data` and `String` from unifying when casting
- **aiken-lang**: remove ability for a type with many variants with matching field labels and types to support field access - **aiken-lang**: remove ability for a type with many variants with matching field labels and types to support field access
- **aiken-project**: tests filtering with `-m` during check now happens in `Project::collect_tests`
- **aiken-lang**: various uplc code gen fixes - **aiken-lang**: various uplc code gen fixes
- **aiken-project**: recursive issue with blueprints fixed
- **aiken-lang**: update todo warning to include type - **aiken-lang**: update todo warning to include type
- **aiken-lang**: `|>` operator can now be formatted as a single (short) line or forced over multiline in a flexible manner
- **aiken-lang**: the compiler now provides better feedback for type holes (i.e. `_`) in type annotations
## [v0.0.29] - 2023-MM-DD ## [v0.0.29] - 2023-MM-DD

View File

@ -1039,8 +1039,6 @@ impl<'a> Environment<'a> {
// Construct type from annotations // Construct type from annotations
let mut hydrator = Hydrator::new(); let mut hydrator = Hydrator::new();
hydrator.permit_holes(true);
let mut arg_types = Vec::new(); let mut arg_types = Vec::new();
for arg in args { for arg in args {
@ -1098,8 +1096,6 @@ impl<'a> Environment<'a> {
// Construct type from annotations // Construct type from annotations
let mut hydrator = Hydrator::new(); let mut hydrator = Hydrator::new();
hydrator.permit_holes(false);
let mut arg_types = Vec::new(); let mut arg_types = Vec::new();
for arg in params.iter().chain(fun.arguments.iter()) { for arg in params.iter().chain(fun.arguments.iter()) {

View File

@ -568,7 +568,7 @@ You can help me by providing a type-annotation for 'x', as such:
, type_ScriptContext = "ScriptContext".if_supports_color(Stdout, |s| s.green()) , type_ScriptContext = "ScriptContext".if_supports_color(Stdout, |s| s.green())
))] ))]
RecordAccessUnknownType { RecordAccessUnknownType {
#[label] #[label("annotation needed")]
location: Span, location: Span,
}, },
@ -642,15 +642,6 @@ Perhaps, try the following:
with_spread: bool, with_spread: bool,
}, },
// TODO: Seems like we can't really trigger this error because we allow type holes everywhere
// anyway. We need to revise that perhaps.
#[error("I stumbled upon an unexpected type hole.\n")]
#[diagnostic(code("unexpected::type_hole"))]
UnexpectedTypeHole {
#[label]
location: Span,
},
#[error("I tripped over some unknown labels in a pattern or function.\n")] #[error("I tripped over some unknown labels in a pattern or function.\n")]
#[diagnostic(code("unknown::labels"))] #[diagnostic(code("unknown::labels"))]
UnknownLabels(#[related] Vec<UnknownLabels>), UnknownLabels(#[related] Vec<UnknownLabels>),
@ -745,7 +736,7 @@ Perhaps, try the following:
#[diagnostic(code("unknown::record_field"))] #[diagnostic(code("unknown::record_field"))]
#[diagnostic(help( #[diagnostic(help(
"{}", "{}",
suggest_neighbor(label, fields.iter(), "Did you forget to make it public?\n\nAlso record access is only supported on types with one constructor.") suggest_neighbor(label, fields.iter(), "Did you forget to make it public?\nNote also that record access is only supported on types with a single constructor.")
))] ))]
UnknownRecordField { UnknownRecordField {
#[label] #[label]
@ -1271,7 +1262,7 @@ pub enum Warning {
help( help(
"If your type has one constructor, unless you are casting {} {}, you can\nprefer using a {} binding like so...\n\n{}", "If your type has one constructor, unless you are casting {} {}, you can\nprefer using a {} binding like so...\n\n{}",
"from".if_supports_color(Stderr, |s| s.bold()), "from".if_supports_color(Stderr, |s| s.bold()),
"Data".if_supports_color(Stderr, |s| s.purple()), "Data".if_supports_color(Stderr, |s| s.bright_blue()),
"let".if_supports_color(Stderr, |s| s.purple()), "let".if_supports_color(Stderr, |s| s.purple()),
format_suggestion(sample) format_suggestion(sample)
) )
@ -1287,15 +1278,18 @@ pub enum Warning {
}, },
#[error("I found a todo left in the code.\n")] #[error("I found a todo left in the code.\n")]
#[diagnostic( #[diagnostic(help("You probably want to replace that with actual code... eventually."))]
help(
"You probably want to replace that one with real code... eventually. The expected type is {}",
tipo.to_pretty_with_names(HashMap::new(), 0).if_supports_color(Stderr, |s| s.purple())
)
)]
#[diagnostic(code("todo"))] #[diagnostic(code("todo"))]
Todo { Todo {
#[label] #[label("An expression of type {} is expected here.", tipo.to_pretty(0))]
location: Span,
tipo: Arc<Type>,
},
#[error("I found a type hole in an annotation.\n")]
#[diagnostic(code("unexpected::type_hole"))]
UnexpectedTypeHole {
#[label("{}", tipo.to_pretty(0))]
location: Span, location: Span,
tipo: Arc<Type>, tipo: Arc<Type>,
}, },

View File

@ -1917,12 +1917,8 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
} }
pub fn new(environment: &'a mut Environment<'b>, tracing: Tracing) -> Self { pub fn new(environment: &'a mut Environment<'b>, tracing: Tracing) -> Self {
let mut hydrator = Hydrator::new();
hydrator.permit_holes(true);
Self { Self {
hydrator, hydrator: Hydrator::new(),
environment, environment,
tracing, tracing,
ungeneralised_function_used: false, ungeneralised_function_used: false,

View File

@ -3,9 +3,14 @@ use std::{collections::HashMap, sync::Arc};
use crate::{ use crate::{
ast::Annotation, ast::Annotation,
builtins::{function, tuple}, builtins::{function, tuple},
tipo::Span,
}; };
use super::{environment::Environment, error::Error, Type, TypeConstructor}; use super::{
environment::Environment,
error::{Error, Warning},
Type, TypeConstructor,
};
/// The Hydrator takes an AST representing a type (i.e. a type annotation /// The Hydrator takes an AST representing a type (i.e. a type annotation
/// for a function argument) and returns a Type for that annotation. /// for a function argument) and returns a Type for that annotation.
@ -28,7 +33,6 @@ pub struct Hydrator {
/// annotated name on error. /// annotated name on error.
rigid_type_names: HashMap<u64, String>, rigid_type_names: HashMap<u64, String>,
permit_new_type_variables: bool, permit_new_type_variables: bool,
permit_holes: bool,
} }
#[derive(Debug)] #[derive(Debug)]
@ -49,7 +53,6 @@ impl Hydrator {
created_type_variables: HashMap::new(), created_type_variables: HashMap::new(),
rigid_type_names: HashMap::new(), rigid_type_names: HashMap::new(),
permit_new_type_variables: true, permit_new_type_variables: true,
permit_holes: false,
} }
} }
@ -72,10 +75,6 @@ impl Hydrator {
self.permit_new_type_variables = false self.permit_new_type_variables = false
} }
pub fn permit_holes(&mut self, flag: bool) {
self.permit_holes = flag
}
/// A rigid type is a generic type that was specified as being generic in /// A rigid type is a generic type that was specified as being generic in
/// an annotation. As such it should never be instantiated into an unbound /// an annotation. As such it should never be instantiated into an unbound
/// variable. /// variable.
@ -98,12 +97,31 @@ impl Hydrator {
} }
} }
/// Construct a Type from an AST Type annotation.
///
pub fn type_from_annotation( pub fn type_from_annotation(
&mut self, &mut self,
annotation: &Annotation, annotation: &Annotation,
environment: &mut Environment, environment: &mut Environment,
) -> Result<Arc<Type>, Error> {
let mut unbounds = vec![];
let tipo = self.do_type_from_annotation(annotation, environment, &mut unbounds)?;
if let Some(location) = unbounds.last() {
environment.warnings.push(Warning::UnexpectedTypeHole {
location: **location,
tipo: tipo.clone(),
});
}
Ok(tipo)
}
/// Construct a Type from an AST Type annotation.
///
fn do_type_from_annotation<'a>(
&mut self,
annotation: &'a Annotation,
environment: &mut Environment,
unbounds: &mut Vec<&'a Span>,
) -> Result<Arc<Type>, Error> { ) -> Result<Arc<Type>, Error> {
match annotation { match annotation {
Annotation::Constructor { Annotation::Constructor {
@ -115,7 +133,7 @@ impl Hydrator {
// Hydrate the type argument AST into types // Hydrate the type argument AST into types
let mut argument_types = Vec::with_capacity(args.len()); let mut argument_types = Vec::with_capacity(args.len());
for t in args { for t in args {
let typ = self.type_from_annotation(t, environment)?; let typ = self.do_type_from_annotation(t, environment, unbounds)?;
argument_types.push((t.location(), typ)); argument_types.push((t.location(), typ));
} }
@ -170,12 +188,12 @@ impl Hydrator {
let mut args = Vec::with_capacity(arguments.len()); let mut args = Vec::with_capacity(arguments.len());
for arg in arguments { for arg in arguments {
let arg = self.type_from_annotation(arg, environment)?; let arg = self.do_type_from_annotation(arg, environment, unbounds)?;
args.push(arg); args.push(arg);
} }
let ret = self.type_from_annotation(ret, environment)?; let ret = self.do_type_from_annotation(ret, environment, unbounds)?;
Ok(function(args, ret)) Ok(function(args, ret))
} }
@ -206,17 +224,16 @@ impl Hydrator {
}), }),
}, },
Annotation::Hole { .. } if self.permit_holes => Ok(environment.new_unbound_var()), Annotation::Hole { location, .. } => {
unbounds.push(location);
Annotation::Hole { location, .. } => Err(Error::UnexpectedTypeHole { Ok(environment.new_unbound_var())
location: *location, }
}),
Annotation::Tuple { elems, .. } => { Annotation::Tuple { elems, .. } => {
let mut typed_elems = vec![]; let mut typed_elems = vec![];
for elem in elems { for elem in elems {
let typed_elem = self.type_from_annotation(elem, environment)?; let typed_elem = self.do_type_from_annotation(elem, environment, unbounds)?;
typed_elems.push(typed_elem) typed_elems.push(typed_elem)
} }