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
- **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**: 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-project**: recursive issue with blueprints fixed
- **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

View File

@ -1039,8 +1039,6 @@ impl<'a> Environment<'a> {
// Construct type from annotations
let mut hydrator = Hydrator::new();
hydrator.permit_holes(true);
let mut arg_types = Vec::new();
for arg in args {
@ -1098,8 +1096,6 @@ impl<'a> Environment<'a> {
// Construct type from annotations
let mut hydrator = Hydrator::new();
hydrator.permit_holes(false);
let mut arg_types = Vec::new();
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())
))]
RecordAccessUnknownType {
#[label]
#[label("annotation needed")]
location: Span,
},
@ -642,15 +642,6 @@ Perhaps, try the following:
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")]
#[diagnostic(code("unknown::labels"))]
UnknownLabels(#[related] Vec<UnknownLabels>),
@ -745,7 +736,7 @@ Perhaps, try the following:
#[diagnostic(code("unknown::record_field"))]
#[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 {
#[label]
@ -1271,7 +1262,7 @@ pub enum Warning {
help(
"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()),
"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()),
format_suggestion(sample)
)
@ -1287,15 +1278,18 @@ pub enum Warning {
},
#[error("I found a todo left in the code.\n")]
#[diagnostic(
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(help("You probably want to replace that with actual code... eventually."))]
#[diagnostic(code("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,
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 {
let mut hydrator = Hydrator::new();
hydrator.permit_holes(true);
Self {
hydrator,
hydrator: Hydrator::new(),
environment,
tracing,
ungeneralised_function_used: false,

View File

@ -3,9 +3,14 @@ use std::{collections::HashMap, sync::Arc};
use crate::{
ast::Annotation,
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
/// for a function argument) and returns a Type for that annotation.
@ -28,7 +33,6 @@ pub struct Hydrator {
/// annotated name on error.
rigid_type_names: HashMap<u64, String>,
permit_new_type_variables: bool,
permit_holes: bool,
}
#[derive(Debug)]
@ -49,7 +53,6 @@ impl Hydrator {
created_type_variables: HashMap::new(),
rigid_type_names: HashMap::new(),
permit_new_type_variables: true,
permit_holes: false,
}
}
@ -72,10 +75,6 @@ impl Hydrator {
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
/// an annotation. As such it should never be instantiated into an unbound
/// variable.
@ -98,12 +97,31 @@ impl Hydrator {
}
}
/// Construct a Type from an AST Type annotation.
///
pub fn type_from_annotation(
&mut self,
annotation: &Annotation,
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> {
match annotation {
Annotation::Constructor {
@ -115,7 +133,7 @@ impl Hydrator {
// Hydrate the type argument AST into types
let mut argument_types = Vec::with_capacity(args.len());
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));
}
@ -170,12 +188,12 @@ impl Hydrator {
let mut args = Vec::with_capacity(arguments.len());
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);
}
let ret = self.type_from_annotation(ret, environment)?;
let ret = self.do_type_from_annotation(ret, environment, unbounds)?;
Ok(function(args, ret))
}
@ -206,17 +224,16 @@ impl Hydrator {
}),
},
Annotation::Hole { .. } if self.permit_holes => Ok(environment.new_unbound_var()),
Annotation::Hole { location, .. } => Err(Error::UnexpectedTypeHole {
location: *location,
}),
Annotation::Hole { location, .. } => {
unbounds.push(location);
Ok(environment.new_unbound_var())
}
Annotation::Tuple { elems, .. } => {
let mut typed_elems = vec![];
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)
}