From a578728a947b33c9c698310516bc8dc710986b22 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Fri, 8 Mar 2024 15:53:41 +0100 Subject: [PATCH] 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. --- crates/aiken-lang/src/builtins.rs | 42 ++- crates/aiken-lang/src/tipo.rs | 47 ++- crates/aiken-lang/src/tipo/environment.rs | 23 +- crates/aiken-lang/src/tipo/exhaustive.rs | 6 +- crates/aiken-lang/src/tipo/pretty.rs | 314 ++++++++++++++---- crates/aiken-lsp/src/server.rs | 31 +- crates/aiken-project/src/blueprint/schema.rs | 48 ++- ...ests__opaque_singleton_multi_variants.snap | 1 + ...tor__tests__opaque_singleton_variants.snap | 5 + 9 files changed, 383 insertions(+), 134 deletions(-) diff --git a/crates/aiken-lang/src/builtins.rs b/crates/aiken-lang/src/builtins.rs index cf5a1135..da98a870 100644 --- a/crates/aiken-lang/src/builtins.rs +++ b/crates/aiken-lang/src/builtins.rs @@ -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 { } pub fn fuzzer(a: Rc) -> Rc { + 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(), + ), }) } diff --git a/crates/aiken-lang/src/tipo.rs b/crates/aiken-lang/src/tipo.rs index 3fa954e5..62a3ca8d 100644 --- a/crates/aiken-lang/src/tipo.rs +++ b/crates/aiken-lang/src/tipo.rs @@ -23,6 +23,13 @@ mod pattern; mod pipe; pub mod pretty; +#[derive(Debug, Clone)] +pub struct TypeAliasAnnotation { + pub alias: String, + pub parameters: Vec, + 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>, - alias: Option<(String, Vec)>, + alias: Option>, }, /// The type of a function. It takes arguments and returns a value. @@ -46,14 +53,14 @@ pub enum Type { Fn { args: Vec>, ret: Rc, - alias: Option<(String, Vec)>, + alias: Option>, }, /// A type variable. See the contained `TypeVar` enum for more information. /// Var { tipo: Rc>, - alias: Option<(String, Vec)>, + alias: Option>, }, // /// 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>, - alias: Option<(String, Vec)>, + alias: Option>, }, } @@ -125,15 +132,23 @@ impl PartialEq for Type { } impl Type { - pub fn with_alias(tipo: Rc, alias: &Option<(String, Vec)>) -> Rc { - match alias { - None => tipo, - Some((name, args)) => tipo.as_ref().to_owned().set_alias(name, args), + pub fn alias(&self) -> Option> { + 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 { - let alias = Some((name.to_string(), args.to_vec())); + pub fn with_alias(tipo: Rc, alias: Option>) -> Rc { + match alias { + None => tipo, + Some(alias) => tipo.deref().to_owned().set_alias(Some(alias)), + } + } + + pub fn set_alias(self, alias: Option>) -> Rc { 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 { - tipo: RefCell::new(var.clone()).into(), - alias: None, - } - .into(), - ] + vec![Type::Var { + tipo: RefCell::new(var.clone()).into(), + alias: None, + } + .into()] } } } diff --git a/crates/aiken-lang/src/tipo/environment.rs b/crates/aiken-lang/src/tipo/environment.rs index fccfbbb8..c405af08 100644 --- a/crates/aiken-lang/src/tipo/environment.rs +++ b/crates/aiken-lang/src/tipo/environment.rs @@ -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, ctx_level: usize) -> Rc { alias: None, }), }, - alias, + alias.clone(), ), Type::App { @@ -1873,7 +1880,7 @@ pub(crate) fn generalise(t: Rc, ctx_level: usize) -> Rc { .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, ctx_level: usize) -> Rc { .map(|t| generalise(t.clone(), ctx_level)) .collect(), ), - alias, + alias.clone(), ), } } diff --git a/crates/aiken-lang/src/tipo/exhaustive.rs b/crates/aiken-lang/src/tipo/exhaustive.rs index 3a23a903..12801be4 100644 --- a/crates/aiken-lang/src/tipo/exhaustive.rs +++ b/crates/aiken-lang/src/tipo/exhaustive.rs @@ -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(), diff --git a/crates/aiken-lang/src/tipo/pretty.rs b/crates/aiken-lang/src/tipo/pretty.rs index 8c929598..27da2f6e 100644 --- a/crates/aiken-lang/src/tipo/pretty.rs +++ b/crates/aiken-lang/src/tipo/pretty.rs @@ -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,75 +47,66 @@ impl Printer { // Is this possible? The lifetime would have to go through the Rc> // 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 => { - let doc = if self.name_clashes_if_unqualified(name, module) { - qualify_type_name(module, name) - } else { - self.printed_types.insert(name.clone(), module.clone()); - Document::String(name.clone()) - }; - if args.is_empty() { - doc - } else { - doc.append("<") - .append(self.args_to_aiken_doc(args)) - .append(">") - } + name, args, module, .. + } => { + let doc = if self.name_clashes_if_unqualified(name, module) { + qualify_type_name(module, name) + } else { + self.printed_types.insert(name.clone(), module.clone()); + Document::String(name.clone()) + }; + if args.is_empty() { + doc + } else { + doc.append("<") + .append(self.args_to_aiken_doc(args)) + .append(">") } - }, + } - Type::Fn { args, ret, alias } => match alias { - Some(alias) => self.type_alias_doc(alias.clone()), - None => "fn(" - .to_doc() - .append(self.args_to_aiken_doc(args)) - .append(") ->") - .append(break_("", " ").append(self.print(ret)).nest(INDENT).group()), - }, + 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)) -> Document<'a> { - let mut doc = Document::String(alias.0.to_owned()); + fn type_alias_doc<'a>(&mut self, alias: String, parameters: Vec>) -> 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( - break_("", "") - .append(args) - .nest(INDENT) - .append(break_(",", "")) - .group(), - ) - .append(">"); + if !parameters.is_empty() { + doc.append( + break_("", "") + .append(concat(Itertools::intersperse( + parameters.iter().map(|t| self.print(t)), + break_(",", ", "), + ))) + .nest(INDENT) + .append(break_(",", "")) + .group() + .surround("<", ">"), + ) + } else { + doc } - - doc } fn name_clashes_if_unqualified(&mut self, tipo: &String, module: &String) -> bool { @@ -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>> { + let mut types = Vec::new(); + + fn resolve_one(parameter: &str, annotation: &Annotation, typ: Rc) -> Option> { + 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 = 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", + ); + 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", + ); } #[test] diff --git a/crates/aiken-lsp/src/server.rs b/crates/aiken-lsp/src/server.rs index f2b1300c..c79dba2b 100644 --- a/crates/aiken-lsp/src/server.rs +++ b/crates/aiken-lsp/src/server.rs @@ -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; diff --git a/crates/aiken-project/src/blueprint/schema.rs b/crates/aiken-project/src/blueprint/schema.rs index 3f7538ac..a649c5fc 100644 --- a/crates/aiken-project/src/blueprint/schema.rs +++ b/crates/aiken-project/src/blueprint/schema.rs @@ -1125,13 +1125,11 @@ pub mod tests { #[test] fn serialize_data_constr_1() { - let schema = Schema::Data(Data::AnyOf(vec![ - Constructor { - index: 0, - fields: vec![], - } - .into(), - ])); + let schema = Schema::Data(Data::AnyOf(vec![Constructor { + index: 0, + fields: vec![], + } + .into()])); assert_json( &schema, json!({ @@ -1292,16 +1290,14 @@ pub mod tests { #[test] fn deserialize_any_of() { assert_eq!( - Data::AnyOf(vec![ - Constructor { - index: 0, - fields: vec![ - Declaration::Referenced(Reference::new("foo")).into(), - Declaration::Referenced(Reference::new("bar")).into() - ], - } - .into() - ]), + Data::AnyOf(vec![Constructor { + index: 0, + fields: vec![ + Declaration::Referenced(Reference::new("foo")).into(), + Declaration::Referenced(Reference::new("bar")).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 { - index: 0, - fields: vec![ - Declaration::Referenced(Reference::new("foo")).into(), - Declaration::Referenced(Reference::new("bar")).into() - ], - } - .into() - ]), + Data::AnyOf(vec![Constructor { + index: 0, + fields: vec![ + Declaration::Referenced(Reference::new("foo")).into(), + Declaration::Referenced(Reference::new("bar")).into() + ], + } + .into()]), serde_json::from_value(json!({ "oneOf": [{ "index": 0, diff --git a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__opaque_singleton_multi_variants.snap b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__opaque_singleton_multi_variants.snap index e397e816..333f1d8b 100644 --- a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__opaque_singleton_multi_variants.snap +++ b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__opaque_singleton_multi_variants.snap @@ -11,6 +11,7 @@ Schema { module: "test_module", name: "Rational", args: [], + alias: None, }, ], }, diff --git a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__opaque_singleton_variants.snap b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__opaque_singleton_variants.snap index 18a09e86..f5d9e83c 100644 --- a/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__opaque_singleton_variants.snap +++ b/crates/aiken-project/src/blueprint/snapshots/aiken_project__blueprint__validator__tests__opaque_singleton_variants.snap @@ -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, }, ], },