Resolve type aliases based on inferred types.

Before this commit, we would always show the 'declared form' of type aliases, with their generic, non-instantiated parameters. This now tries to unify the annotation with the underlying inferred type to provide even better alias pretty printing.
This commit is contained in:
KtorZ 2024-03-08 15:53:41 +01:00
parent ed9f5c6ef7
commit a578728a94
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
9 changed files with 383 additions and 134 deletions

View File

@ -1,12 +1,12 @@
use crate::{
ast::{
Arg, ArgName, CallArg, DataTypeKey, Function, FunctionAccessKey, ModuleKind, Span,
TypedDataType, TypedFunction, UnOp,
Annotation, Arg, ArgName, CallArg, DataTypeKey, Function, FunctionAccessKey, ModuleKind,
Span, TypedDataType, TypedFunction, UnOp,
},
expr::TypedExpr,
tipo::{
fields::FieldMap, Type, TypeConstructor, TypeInfo, TypeVar, ValueConstructor,
ValueConstructorVariant,
fields::FieldMap, Type, TypeAliasAnnotation, TypeConstructor, TypeInfo, TypeVar,
ValueConstructor, ValueConstructorVariant,
},
IdGenerator,
};
@ -1349,10 +1349,42 @@ pub fn prng() -> Rc<Type> {
}
pub fn fuzzer(a: Rc<Type>) -> Rc<Type> {
let prng_annotation = Annotation::Constructor {
location: Span::empty(),
module: None,
name: "PRNG".to_string(),
arguments: vec![],
};
Rc::new(Type::Fn {
args: vec![prng()],
ret: option(tuple(vec![prng(), a])),
alias: Some(("Fuzzer".to_string(), vec!["a".to_string()])),
alias: Some(
TypeAliasAnnotation {
alias: "Fuzzer".to_string(),
parameters: vec!["a".to_string()],
annotation: Annotation::Fn {
location: Span::empty(),
arguments: vec![prng_annotation.clone()],
ret: Annotation::Constructor {
location: Span::empty(),
module: None,
name: "Option".to_string(),
arguments: vec![Annotation::Tuple {
location: Span::empty(),
elems: vec![
prng_annotation,
Annotation::Var {
location: Span::empty(),
name: "a".to_string(),
},
],
}],
}
.into(),
},
}
.into(),
),
})
}

View File

@ -23,6 +23,13 @@ mod pattern;
mod pipe;
pub mod pretty;
#[derive(Debug, Clone)]
pub struct TypeAliasAnnotation {
pub alias: String,
pub parameters: Vec<String>,
pub annotation: Annotation,
}
#[derive(Debug, Clone)]
pub enum Type {
/// A nominal (named) type such as `Int`, `Float`, or a programmer defined
@ -38,7 +45,7 @@ pub enum Type {
module: String,
name: String,
args: Vec<Rc<Type>>,
alias: Option<(String, Vec<String>)>,
alias: Option<Rc<TypeAliasAnnotation>>,
},
/// The type of a function. It takes arguments and returns a value.
@ -46,14 +53,14 @@ pub enum Type {
Fn {
args: Vec<Rc<Type>>,
ret: Rc<Type>,
alias: Option<(String, Vec<String>)>,
alias: Option<Rc<TypeAliasAnnotation>>,
},
/// A type variable. See the contained `TypeVar` enum for more information.
///
Var {
tipo: Rc<RefCell<TypeVar>>,
alias: Option<(String, Vec<String>)>,
alias: Option<Rc<TypeAliasAnnotation>>,
},
// /// A tuple is an ordered collection of 0 or more values, each of which
// /// can have a different type, so the `tuple` type is the sum of all the
@ -61,7 +68,7 @@ pub enum Type {
// ///
Tuple {
elems: Vec<Rc<Type>>,
alias: Option<(String, Vec<String>)>,
alias: Option<Rc<TypeAliasAnnotation>>,
},
}
@ -125,15 +132,23 @@ impl PartialEq for Type {
}
impl Type {
pub fn with_alias(tipo: Rc<Type>, alias: &Option<(String, Vec<String>)>) -> Rc<Type> {
match alias {
None => tipo,
Some((name, args)) => tipo.as_ref().to_owned().set_alias(name, args),
pub fn alias(&self) -> Option<Rc<TypeAliasAnnotation>> {
match self {
Type::App { alias, .. }
| Type::Fn { alias, .. }
| Type::Var { alias, .. }
| Type::Tuple { alias, .. } => alias.clone(),
}
}
pub fn set_alias(self, name: &str, args: &[String]) -> Rc<Type> {
let alias = Some((name.to_string(), args.to_vec()));
pub fn with_alias(tipo: Rc<Type>, alias: Option<Rc<TypeAliasAnnotation>>) -> Rc<Type> {
match alias {
None => tipo,
Some(alias) => tipo.deref().to_owned().set_alias(Some(alias)),
}
}
pub fn set_alias(self, alias: Option<Rc<TypeAliasAnnotation>>) -> Rc<Type> {
Rc::new(match self {
Type::App {
public,
@ -939,13 +954,11 @@ impl TypeVar {
Self::Link { tipo } => tipo.get_inner_types(),
Self::Unbound { .. } => vec![],
var => {
vec![
Type::Var {
vec![Type::Var {
tipo: RefCell::new(var.clone()).into(),
alias: None,
}
.into(),
]
.into()]
}
}
}

View File

@ -12,7 +12,7 @@ use crate::{
UnqualifiedImport, UntypedArg, UntypedDefinition, Use, Validator, PIPE_VARIABLE,
},
builtins::{function, generic_var, tuple, unbound_var},
tipo::fields::FieldMap,
tipo::{fields::FieldMap, TypeAliasAnnotation},
IdGenerator,
};
use std::{
@ -567,7 +567,7 @@ impl<'a> Environment<'a> {
TypeVar::Link { tipo } => {
return Type::with_alias(
self.instantiate(tipo.clone(), ids, hydrator),
alias,
alias.clone(),
);
}
@ -603,7 +603,7 @@ impl<'a> Environment<'a> {
.collect(),
self.instantiate(ret.clone(), ids, hydrator),
),
alias,
alias.clone(),
),
Type::Tuple { elems, alias } => Type::with_alias(
@ -613,7 +613,7 @@ impl<'a> Environment<'a> {
.map(|t| self.instantiate(t.clone(), ids, hydrator))
.collect(),
),
alias,
alias.clone(),
),
}
}
@ -1046,7 +1046,14 @@ impl<'a> Environment<'a> {
.type_from_annotation(resolved_type, self)?
.as_ref()
.to_owned()
.set_alias(name, args);
.set_alias(Some(
TypeAliasAnnotation {
alias: name.to_string(),
parameters: args.to_vec(),
annotation: resolved_type.clone(),
}
.into(),
));
self.insert_type_constructor(
name.clone(),
@ -1842,7 +1849,7 @@ pub(crate) fn generalise(t: Rc<Type>, ctx_level: usize) -> Rc<Type> {
alias: None,
}),
},
alias,
alias.clone(),
),
Type::App {
@ -1873,7 +1880,7 @@ pub(crate) fn generalise(t: Rc<Type>, ctx_level: usize) -> Rc<Type> {
.collect(),
generalise(ret.clone(), ctx_level),
),
alias,
alias.clone(),
),
Type::Tuple { elems, alias } => Type::with_alias(
@ -1883,7 +1890,7 @@ pub(crate) fn generalise(t: Rc<Type>, ctx_level: usize) -> Rc<Type> {
.map(|t| generalise(t.clone(), ctx_level))
.collect(),
),
alias,
alias.clone(),
),
}
}

View File

@ -608,7 +608,11 @@ pub(super) fn simplify(
Ok(Pattern::Constructor(
TUPLE_NAME.to_string(),
vec![tipo::ValueConstructor {
tipo: tipo::Type::Tuple { elems: vec![], alias: None }.into(),
tipo: tipo::Type::Tuple {
elems: vec![],
alias: None,
}
.into(),
public: true,
variant: tipo::ValueConstructorVariant::Record {
name: TUPLE_NAME.to_string(),

View File

@ -2,6 +2,7 @@ use super::{Type, TypeVar};
use crate::{
docvec,
pretty::{nil, *},
tipo::{Annotation, TypeAliasAnnotation},
};
use itertools::Itertools;
use std::{collections::HashMap, rc::Rc};
@ -46,16 +47,21 @@ impl Printer {
// Is this possible? The lifetime would have to go through the Rc<Refcell<Type>>
// for TypeVar::Link'd types.
pub fn print<'a>(&mut self, typ: &Type) -> Document<'a> {
if let Some(TypeAliasAnnotation {
alias,
parameters,
annotation,
}) = typ.alias().as_deref()
{
if let Some(resolved_parameters) = resolve_alias(parameters, annotation, typ) {
return self.type_alias_doc(alias.to_string(), resolved_parameters);
}
}
match typ {
Type::App {
name,
args,
module,
alias,
..
} => match alias {
Some(alias) => self.type_alias_doc(alias.clone()),
None => {
name, args, module, ..
} => {
let doc = if self.name_clashes_if_unqualified(name, module) {
qualify_type_name(module, name)
} else {
@ -70,52 +76,38 @@ impl Printer {
.append(">")
}
}
},
Type::Fn { args, ret, alias } => match alias {
Some(alias) => self.type_alias_doc(alias.clone()),
None => "fn("
Type::Fn { args, ret, .. } => "fn("
.to_doc()
.append(self.args_to_aiken_doc(args))
.append(") ->")
.append(break_("", " ").append(self.print(ret)).nest(INDENT).group()),
},
Type::Var { tipo: typ, alias } => match alias {
Some(alias) => self.type_alias_doc(alias.clone()),
None => self.type_var_doc(&typ.borrow()),
},
Type::Var { tipo: typ, .. } => self.type_var_doc(&typ.borrow()),
Type::Tuple { elems, alias } => match alias {
Some(alias) => self.type_alias_doc(alias.clone()),
None => self.args_to_aiken_doc(elems).surround("(", ")"),
},
Type::Tuple { elems, .. } => self.args_to_aiken_doc(elems).surround("(", ")"),
}
}
fn type_alias_doc<'a>(&mut self, alias: (String, Vec<String>)) -> Document<'a> {
let mut doc = Document::String(alias.0.to_owned());
fn type_alias_doc<'a>(&mut self, alias: String, parameters: Vec<Rc<Type>>) -> Document<'a> {
let doc = Document::String(alias);
if !alias.1.is_empty() {
let args = concat(Itertools::intersperse(
alias.1.into_iter().map(Document::String),
break_(",", ", "),
));
doc = doc
.append("<")
.append(
if !parameters.is_empty() {
doc.append(
break_("", "")
.append(args)
.append(concat(Itertools::intersperse(
parameters.iter().map(|t| self.print(t)),
break_(",", ", "),
)))
.nest(INDENT)
.append(break_(",", ""))
.group(),
.group()
.surround("<", ">"),
)
.append(">");
}
} else {
doc
}
}
fn name_clashes_if_unqualified(&mut self, tipo: &String, module: &String) -> bool {
match self.printed_types.get(tipo) {
@ -205,10 +197,77 @@ fn qualify_type_name(module: &String, typ_name: &str) -> Document<'static> {
}
}
fn resolve_alias(
parameters: &[String],
annotation: &Annotation,
typ: &Type,
) -> Option<Vec<Rc<Type>>> {
let mut types = Vec::new();
fn resolve_one(parameter: &str, annotation: &Annotation, typ: Rc<Type>) -> Option<Rc<Type>> {
match (annotation, typ.as_ref()) {
(
Annotation::Fn {
arguments: args,
ret,
..
},
Type::Fn {
args: t_args,
ret: t_ret,
..
},
) => {
let mut result = resolve_one(parameter, ret, t_ret.clone());
for (ann, t) in args.iter().zip(t_args) {
result = result.or_else(|| resolve_one(parameter, ann, t.clone()));
}
result
}
(
Annotation::Constructor {
arguments: args, ..
},
Type::App { args: t_args, .. },
) => {
let mut result = None;
for (ann, t) in args.iter().zip(t_args) {
result = result.or_else(|| resolve_one(parameter, ann, t.clone()));
}
result
}
(Annotation::Tuple { elems, .. }, Type::Tuple { elems: t_elems, .. }) => {
let mut result = None;
for (ann, t) in elems.iter().zip(t_elems) {
result = result.or_else(|| resolve_one(parameter, ann, t.clone()));
}
result
}
(Annotation::Var { name, .. }, ..) if name == parameter => Some(typ),
_ => None,
}
}
let rc: Rc<Type> = typ.to_owned().into();
for parameter in parameters {
types.push(resolve_one(parameter, annotation, rc.clone())?);
}
Some(types)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::builtins::{function, int};
use crate::{
builtins::{function, int},
tipo::Span,
};
use pretty_assertions::assert_eq;
use std::cell::RefCell;
@ -416,6 +475,143 @@ mod tests {
),
"fn(a) -> b",
);
assert_string!(
Type::Fn {
args: vec![Rc::new(Type::App {
public: true,
module: "".to_string(),
name: "PRNG".to_string(),
args: vec![],
alias: None,
})],
ret: Rc::new(Type::App {
public: true,
module: "".to_string(),
name: "Option".to_string(),
args: vec![Rc::new(Type::Tuple {
elems: vec![
Rc::new(Type::App {
public: true,
module: "".to_string(),
name: "PRNG".to_string(),
args: vec![],
alias: None,
}),
Rc::new(Type::App {
public: true,
module: "".to_string(),
name: "Bool".to_string(),
args: vec![],
alias: None,
}),
],
alias: None,
})],
alias: None,
}),
alias: Some(Rc::new(TypeAliasAnnotation {
alias: "Fuzzer".to_string(),
parameters: vec!["a".to_string(),],
annotation: Annotation::Fn {
location: Span::empty(),
arguments: vec![Annotation::Constructor {
location: Span::empty(),
module: None,
name: "PRNG".to_string(),
arguments: vec![],
},],
ret: Box::new(Annotation::Constructor {
location: Span::empty(),
module: None,
name: "Option".to_string(),
arguments: vec![Annotation::Tuple {
location: Span::empty(),
elems: vec![
Annotation::Constructor {
location: Span::empty(),
module: None,
name: "PRNG".to_string(),
arguments: vec![],
},
Annotation::Var {
location: Span::empty(),
name: "a".to_string(),
},
],
}],
}),
},
})),
},
"Fuzzer<Bool>",
);
assert_string!(
Type::Fn {
args: vec![Rc::new(Type::App {
public: true,
module: "".to_string(),
name: "PRNG".to_string(),
args: vec![],
alias: None,
})],
ret: Rc::new(Type::App {
public: true,
module: "".to_string(),
name: "Option".to_string(),
args: vec![Rc::new(Type::Tuple {
elems: vec![
Rc::new(Type::App {
public: true,
module: "".to_string(),
name: "PRNG".to_string(),
args: vec![],
alias: None,
}),
Rc::new(Type::Var {
tipo: Rc::new(RefCell::new(TypeVar::Generic { id: 0 })),
alias: None,
}),
],
alias: None,
})],
alias: None,
}),
alias: Some(Rc::new(TypeAliasAnnotation {
alias: "Fuzzer".to_string(),
parameters: vec!["a".to_string(),],
annotation: Annotation::Fn {
location: Span::empty(),
arguments: vec![Annotation::Constructor {
location: Span::empty(),
module: None,
name: "PRNG".to_string(),
arguments: vec![],
},],
ret: Box::new(Annotation::Constructor {
location: Span::empty(),
module: None,
name: "Option".to_string(),
arguments: vec![Annotation::Tuple {
location: Span::empty(),
elems: vec![
Annotation::Constructor {
location: Span::empty(),
module: None,
name: "PRNG".to_string(),
arguments: vec![],
},
Annotation::Var {
location: Span::empty(),
name: "a".to_string(),
},
],
}],
}),
},
})),
},
"Fuzzer<a>",
);
}
#[test]

View File

@ -1,10 +1,14 @@
use crate::quickfix::Quickfix;
use std::{
collections::{HashMap, HashSet},
fs,
path::{Path, PathBuf},
use self::lsp_project::LspProject;
use crate::{
cast::{cast_notification, cast_request},
error::Error as ServerError,
quickfix,
quickfix::Quickfix,
utils::{
path_to_uri, span_to_lsp_range, text_edit_replace, uri_to_module_name,
COMPILING_PROGRESS_TOKEN, CREATE_COMPILING_PROGRESS_TOKEN,
},
};
use aiken_lang::{
ast::{Definition, Located, ModuleKind, Span, Use},
error::ExtraData,
@ -32,19 +36,12 @@ use lsp_types::{
DocumentFormattingParams, InitializeParams, TextEdit,
};
use miette::Diagnostic;
use crate::{
cast::{cast_notification, cast_request},
error::Error as ServerError,
quickfix,
utils::{
path_to_uri, span_to_lsp_range, text_edit_replace, uri_to_module_name,
COMPILING_PROGRESS_TOKEN, CREATE_COMPILING_PROGRESS_TOKEN,
},
use std::{
collections::{HashMap, HashSet},
fs,
path::{Path, PathBuf},
};
use self::lsp_project::LspProject;
pub mod lsp_project;
pub mod telemetry;

View File

@ -1125,13 +1125,11 @@ pub mod tests {
#[test]
fn serialize_data_constr_1() {
let schema = Schema::Data(Data::AnyOf(vec![
Constructor {
let schema = Schema::Data(Data::AnyOf(vec![Constructor {
index: 0,
fields: vec![],
}
.into(),
]));
.into()]));
assert_json(
&schema,
json!({
@ -1292,16 +1290,14 @@ pub mod tests {
#[test]
fn deserialize_any_of() {
assert_eq!(
Data::AnyOf(vec![
Constructor {
Data::AnyOf(vec![Constructor {
index: 0,
fields: vec![
Declaration::Referenced(Reference::new("foo")).into(),
Declaration::Referenced(Reference::new("bar")).into()
],
}
.into()
]),
.into()]),
serde_json::from_value(json!({
"anyOf": [{
"index": 0,
@ -1322,16 +1318,14 @@ pub mod tests {
#[test]
fn deserialize_one_of() {
assert_eq!(
Data::AnyOf(vec![
Constructor {
Data::AnyOf(vec![Constructor {
index: 0,
fields: vec![
Declaration::Referenced(Reference::new("foo")).into(),
Declaration::Referenced(Reference::new("bar")).into()
],
}
.into()
]),
.into()]),
serde_json::from_value(json!({
"oneOf": [{
"index": 0,

View File

@ -11,6 +11,7 @@ Schema {
module: "test_module",
name: "Rational",
args: [],
alias: None,
},
],
},

View File

@ -19,9 +19,11 @@ Schema {
module: "test_module",
name: "UUID",
args: [],
alias: None,
},
},
},
alias: None,
},
Var {
tipo: RefCell {
@ -31,11 +33,14 @@ Schema {
module: "",
name: "Int",
args: [],
alias: None,
},
},
},
alias: None,
},
],
alias: None,
},
],
},