From b2aae73c69b4cd536876089fdd67db32f33be3f9 Mon Sep 17 00:00:00 2001 From: eldesh Date: Tue, 12 Nov 2024 01:01:47 +0900 Subject: [PATCH] Remove unneeded parenthesis (#203) * Remove parenthesis from type application e.g. `(Int a)` to `Int a` * Remove unneeded parentheses from fun, app and forall type expr * Add priority of pair type * Passing cache to resolve TypeVariableId * Fix: avoid parenthesis for primitive int and float * Fix unit tests * Fix: polymorphic and concrete numeral types are now distinguished before: > foo : (Foo String Int a) > add_one : forall a. (Maybe I32 -> Maybe I32 can a) after: > foo : (Foo String (Int a)) > add_one : forall a. (Maybe I32 -> Maybe I32 can a) -- No change for concrete types refs: https://github.com/jfecher/ante/pull/203#discussion_r1835741542 * Fix the test named_constructor.an * Update src/types/mod.rs --------- Co-authored-by: jfecher --- .../129_int_defaulting_generalization.an | 6 +- examples/typechecking/bind.an | 8 +- .../typechecking/completeness_checking.an | 2 +- examples/typechecking/effects.an | 12 +-- examples/typechecking/extern.an | 8 +- examples/typechecking/functor_and_monad.an | 6 +- examples/typechecking/generalization.an | 2 +- examples/typechecking/impl.an | 2 +- examples/typechecking/instantiation.an | 10 +- examples/typechecking/member_access.an | 10 +- examples/typechecking/mutual_recursion.an | 8 +- examples/typechecking/named_constructor.an | 6 +- examples/typechecking/repeated_traits.an | 2 +- examples/typechecking/trait_fundep_result.an | 2 +- examples/typechecking/trait_generalization.an | 2 +- examples/typechecking/trait_propagation.an | 6 +- examples/typechecking/type_annotations.an | 10 +- src/types/mod.rs | 67 ++++++++++++- src/types/typeprinter.rs | 96 ++++++++++++------- 19 files changed, 176 insertions(+), 89 deletions(-) diff --git a/examples/regressions/129_int_defaulting_generalization.an b/examples/regressions/129_int_defaulting_generalization.an index ced90501..a973b740 100644 --- a/examples/regressions/129_int_defaulting_generalization.an +++ b/examples/regressions/129_int_defaulting_generalization.an @@ -12,9 +12,9 @@ second b + 2i64 // args: --check --show-types // expected stdout: -// a : (U64, I64) -// b : (U64, I64) -// c : (U64, I64) +// a : U64, I64 +// b : U64, I64 +// c : U64, I64 // Expected results with the function block uncommented: // f : (forall a. (Unit -> (U64, I64) can a)) diff --git a/examples/typechecking/bind.an b/examples/typechecking/bind.an index 0a480b86..0c654a33 100644 --- a/examples/typechecking/bind.an +++ b/examples/typechecking/bind.an @@ -12,7 +12,7 @@ add_one x = // args: --check --show-types // expected stdout: -// add_one : (forall a. ((Maybe I32) -> (Maybe I32) can a)) -// bind : (forall a b c d. ((Maybe c) - (c => (Maybe b) can d) -> (Maybe b) can d)) -// ret : (forall a b. (a -> (Maybe a) can b)) -// x : (Maybe I32) +// add_one : forall a. (Maybe I32 -> Maybe I32 can a) +// bind : forall a b c d. (Maybe c - (c => Maybe b can d) -> Maybe b can d) +// ret : forall a b. (a -> Maybe a can b) +// x : Maybe I32 diff --git a/examples/typechecking/completeness_checking.an b/examples/typechecking/completeness_checking.an index 9665c08e..283560da 100644 --- a/examples/typechecking/completeness_checking.an +++ b/examples/typechecking/completeness_checking.an @@ -46,5 +46,5 @@ match (1, 2, 3, 4) // completeness_checking.an:20:1 error: Missing case (false, false) // match (true, true) // -// completeness_checking.an:25:4 error: This pattern of type ((Int a), (Int b)) does not match the type ((Int a), ((Int b), ((Int c), (Int d)))) that is being matched on +// completeness_checking.an:25:4 error: This pattern of type Int a, Int b does not match the type Int a, Int b, Int c, Int d that is being matched on // | (1, 2) -> 1 diff --git a/examples/typechecking/effects.an b/examples/typechecking/effects.an index 1678ebd0..1c18210d 100644 --- a/examples/typechecking/effects.an +++ b/examples/typechecking/effects.an @@ -23,10 +23,10 @@ does_use x = // args: --check --show-types // expected stdout: -// does_use : (forall a b. (a -> Unit can (Use a, b))) +// does_use : forall a b. (a -> Unit can (Use a, b)) // given Add a -// get : (forall a b. (Unit -> a can (Use a, b))) -// handle_basic : (forall a. (Unit -> Unit can (Use String, a))) -// log : (forall a. (String -> Unit can (Log, a))) -// set : (forall a b. (a -> Unit can (Use a, b))) -// use_resume : (forall a. (Unit -> Unit can a)) +// get : forall a b. (Unit -> a can (Use a, b)) +// handle_basic : forall a. (Unit -> Unit can (Use String, a)) +// log : forall a. (String -> Unit can (Log, a)) +// set : forall a b. (a -> Unit can (Use a, b)) +// use_resume : forall a. (Unit -> Unit can a) diff --git a/examples/typechecking/extern.an b/examples/typechecking/extern.an index bed9dae8..a500e4ec 100644 --- a/examples/typechecking/extern.an +++ b/examples/typechecking/extern.an @@ -12,7 +12,7 @@ exit2 0 // args: --check --show-types // expected stdout: -// add : (forall a. (I32 - I32 -> I32 can a)) -// exit2 : (forall a b. (I32 -> a can b)) -// foo : (forall a b c. (a -> b can c)) -// puts2 : (forall a. (String -> Unit can a)) +// add : forall a. (I32 - I32 -> I32 can a) +// exit2 : forall a b. (I32 -> a can b) +// foo : forall a b c. (a -> b can c) +// puts2 : forall a. (String -> Unit can a) diff --git a/examples/typechecking/functor_and_monad.an b/examples/typechecking/functor_and_monad.an index b2be22d4..777ef908 100644 --- a/examples/typechecking/functor_and_monad.an +++ b/examples/typechecking/functor_and_monad.an @@ -23,9 +23,9 @@ impl Monad Maybe with // args: --check --show-types // expected stdout: -// bind : (forall a b c d e. ((a b) - (b -> (a c) can d) -> (a c) can e)) +// bind : forall a b c d e. (a b - (b -> a c can d) -> a c can e) // given Monad a -// map : (forall a b c d e. ((a b) - (b -> c can d) -> (a c) can e)) +// map : forall a b c d e. (a b - (b -> c can d) -> a c can e) // given Functor a -// wrap : (forall a b c. (b -> (a b) can c)) +// wrap : forall a b c. (b -> a b can c) // given Monad a diff --git a/examples/typechecking/generalization.an b/examples/typechecking/generalization.an index 64c8864d..2eeba95b 100644 --- a/examples/typechecking/generalization.an +++ b/examples/typechecking/generalization.an @@ -6,4 +6,4 @@ foo x = // args: --check --show-types // expected stdout: -// foo : (forall a b c d. (a -> (b => a can c) can d)) +// foo : forall a b c d. (a -> b => a can c can d) diff --git a/examples/typechecking/impl.an b/examples/typechecking/impl.an index 184b7e3f..b4f72c0b 100644 --- a/examples/typechecking/impl.an +++ b/examples/typechecking/impl.an @@ -23,5 +23,5 @@ c = foo "one" "two" // a : I32 // b : F64 // c : String -// foo : (forall a b. (a - a -> a can b)) +// foo : forall a b. (a - a -> a can b) // given Foo a diff --git a/examples/typechecking/instantiation.an b/examples/typechecking/instantiation.an index 1f19dff2..369e1b45 100644 --- a/examples/typechecking/instantiation.an +++ b/examples/typechecking/instantiation.an @@ -14,8 +14,8 @@ id x = x // args: --check --show-types // expected stdout: -// add : (forall a b c d e f g h i. ((a - c => e can g) - (a - b => c can g) -> (a => (b => e can g) can h) can i)) -// id : (forall a b. (a -> a can b)) -// one : (forall a b c d. ((a => b can d) - a -> b can d)) -// two1 : (forall a b c. ((b => b can c) - b -> b can c)) -// two2 : ((a => a can c) => (a => a can c) can d) +// add : forall a b c d e f g h i. ((a - c => e can g) - (a - b => c can g) -> a => b => e can g can h can i) +// id : forall a b. (a -> a can b) +// one : forall a b c d. ((a => b can d) - a -> b can d) +// two1 : forall a b c. ((b => b can c) - b -> b can c) +// two2 : (a => a can c) => a => a can c can d diff --git a/examples/typechecking/member_access.an b/examples/typechecking/member_access.an index 6aae4494..4741e459 100644 --- a/examples/typechecking/member_access.an +++ b/examples/typechecking/member_access.an @@ -22,11 +22,11 @@ foo_and_bar foo bar // // expected stdout: -// Bar : (forall a. (Char -> Bar can a)) -// Foo : (forall a. (F64 -> Foo can a)) -// FooBar : (forall a. (I32 - String -> FooBar can a)) +// Bar : forall a. (Char -> Bar can a) +// Foo : forall a. (F64 -> Foo can a) +// FooBar : forall a. (I32 - String -> FooBar can a) // bar : Bar // foo : Foo -// foo_and_bar : (forall a b c d. ({ foo: a, ..b } - { bar: String, ..c } -> String can d)) +// foo_and_bar : forall a b c d. ({ foo: a, ..b } - { bar: String, ..c } -> String can d) // foobar : FooBar -// stringify : (forall a. (String -> String can a)) +// stringify : forall a. (String -> String can a) diff --git a/examples/typechecking/mutual_recursion.an b/examples/typechecking/mutual_recursion.an index 4eb5c325..e460faee 100644 --- a/examples/typechecking/mutual_recursion.an +++ b/examples/typechecking/mutual_recursion.an @@ -16,7 +16,7 @@ is_even 4 // TODO: is_odd here uses `forall a c.` instead of `forall a b.` // expected stdout: -// is_even : (forall a b. ((Int a) -> Bool can b)) -// given Eq (Int a), Print (Int a), Sub (Int a) -// is_odd : (forall a c. ((Int a) -> Bool can c)) -// given Eq (Int a), Print (Int a), Sub (Int a) +// is_even : forall a b. (Int a -> Bool can b) +// given Eq Int a, Print Int a, Sub Int a +// is_odd : forall a c. (Int a -> Bool can c) +// given Eq Int a, Print Int a, Sub Int a diff --git a/examples/typechecking/named_constructor.an b/examples/typechecking/named_constructor.an index f4146bb4..e3d292ef 100644 --- a/examples/typechecking/named_constructor.an +++ b/examples/typechecking/named_constructor.an @@ -8,6 +8,6 @@ foo = hello_foo 42 // args: --check --show-types // expected stdout: -// Foo : (forall a b c. (a - b -> (Foo a b) can c)) -// foo : (Foo String (Int a)) -// hello_foo : (forall a b. (a -> (Foo String a) can b)) +// Foo : forall a b c. (a - b -> Foo a b can c) +// foo : Foo String (Int a) +// hello_foo : forall a b. (a -> Foo String a can b) diff --git a/examples/typechecking/repeated_traits.an b/examples/typechecking/repeated_traits.an index e4a31999..f4e5fc73 100644 --- a/examples/typechecking/repeated_traits.an +++ b/examples/typechecking/repeated_traits.an @@ -5,5 +5,5 @@ foo a = // Make sure output is not "... given Print a, Print a" // args: --check --show-types // expected stdout: -// foo : (forall a b. (a -> Unit can b)) +// foo : forall a b. (a -> Unit can b) // given Print a diff --git a/examples/typechecking/trait_fundep_result.an b/examples/typechecking/trait_fundep_result.an index 1f3a051b..f60abc28 100644 --- a/examples/typechecking/trait_fundep_result.an +++ b/examples/typechecking/trait_fundep_result.an @@ -9,6 +9,6 @@ str = foo 0i32 // args: --check --show-types // expected stdout: -// foo : (forall a b c. (a -> b can c)) +// foo : forall a b c. (a -> b can c) // given Foo a b // str : String diff --git a/examples/typechecking/trait_generalization.an b/examples/typechecking/trait_generalization.an index f86ca965..9c45a2ff 100644 --- a/examples/typechecking/trait_generalization.an +++ b/examples/typechecking/trait_generalization.an @@ -1,4 +1,4 @@ a = "test".c_string // args: --check --show-types -// expected stdout: a : (Ptr Char) +// expected stdout: a : Ptr Char diff --git a/examples/typechecking/trait_propagation.an b/examples/typechecking/trait_propagation.an index 97ffca46..2e72241a 100644 --- a/examples/typechecking/trait_propagation.an +++ b/examples/typechecking/trait_propagation.an @@ -11,10 +11,10 @@ foo () = baz bar // // args: --check --show-types // expected stdout: -// bar : (forall a. a) -// baz : (forall a b. (a -> Unit can b)) +// bar : forall a. a +// baz : forall a b. (a -> Unit can b) // given Baz a -// foo : (forall a. (Unit -> Unit can a)) +// foo : forall a. (Unit -> Unit can a) // expected stderr: // trait_propagation.an:6:10 error: No impl found for Baz a diff --git a/examples/typechecking/type_annotations.an b/examples/typechecking/type_annotations.an index e9d63b73..0f519a42 100644 --- a/examples/typechecking/type_annotations.an +++ b/examples/typechecking/type_annotations.an @@ -22,8 +22,8 @@ puts2 // // expected stdout: -// bar : (forall a. (I32 - I32 -> I32 can a)) -// baz : (forall a b. (Usz -> (Ptr a) can b)) -// exit2 : (forall a b. (I32 -> a can b)) -// foo : (forall a. (I32 - String -> Char can a)) -// puts2 : (forall a. ((Ptr Char) -> I32 can a)) +// bar : forall a. (I32 - I32 -> I32 can a) +// baz : forall a b. (Usz -> Ptr a can b) +// exit2 : forall a b. (I32 -> a can b) +// foo : forall a. (I32 - String -> Char can a) +// puts2 : forall a. (Ptr Char -> I32 can a) diff --git a/src/types/mod.rs b/src/types/mod.rs index c0e0fe35..a51efb1c 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -9,8 +9,8 @@ use std::collections::BTreeMap; use crate::cache::{DefinitionInfoId, ModuleCache}; use crate::error::location::{Locatable, Location}; use crate::lexer::token::{FloatKind, IntegerKind}; -use crate::util::fmap; use crate::util; +use crate::util::fmap; use self::typeprinter::TypePrinter; use crate::types::effects::EffectSet; @@ -27,6 +27,30 @@ pub mod typeprinter; #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct TypeVariableId(pub usize); +/// Priority of operator on Types +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct TypePriority(u8); + +impl From for TypePriority { + fn from(priority: u8) -> Self { + Self(priority) + } +} + +impl std::fmt::Display for TypePriority { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "priority {}", self.0) + } +} + +impl TypePriority { + pub const MAX: TypePriority = TypePriority(u8::MAX); + pub const APP: TypePriority = TypePriority(4); + pub const FORALL: TypePriority = TypePriority(3); + pub const PAIR: TypePriority = TypePriority(2); + pub const FUN: TypePriority = TypePriority(1); +} + /// Primitive types are the easy cases when unifying types. /// They're equal simply if the other type is also the same PrimitiveType variant, /// there is no recursion needed like with other Types. If the `Type` @@ -217,6 +241,36 @@ impl Type { } } + pub fn priority(&self, cache: &ModuleCache<'_>) -> TypePriority { + use Type::*; + match self { + Primitive(_) | UserDefined(_) | Struct(_, _) | Tag(_) => TypePriority::MAX, + TypeVariable(id) => match &cache.type_bindings[id.0] { + TypeBinding::Bound(typ) => typ.priority(cache), + TypeBinding::Unbound(..) => TypePriority::MAX, + }, + Function(_) => TypePriority::FUN, + TypeApplication(ctor, args) if ctor.is_polymorphic_int_type() || ctor.is_polymorphic_float_type() => { + if matches!(cache.follow_typebindings_shallow(&args[0]), Type::TypeVariable(_)) { + // type variable is unbound variable + TypePriority::APP + } else { + // type variable is bound (polymorphic int) + TypePriority::MAX + } + }, + TypeApplication(ctor, _) => { + if ctor.is_pair_type() { + TypePriority::PAIR + } else { + TypePriority::APP + } + }, + Ref { .. } => TypePriority::APP, + Effects(_) => unimplemented!("Type::priority for Effects"), + } + } + /// Pretty-print each type with each typevar substituted for a, b, c, etc. pub fn display<'a, 'b>(&self, cache: &'a ModuleCache<'b>) -> typeprinter::TypePrinter<'a, 'b> { let typ = GeneralizedType::MonoType(self.clone()); @@ -257,7 +311,7 @@ impl Type { sharedness.traverse_rec(cache, f); mutability.traverse_rec(cache, f); lifetime.traverse_rec(cache, f); - } + }, Type::TypeApplication(constructor, args) => { constructor.traverse_rec(cache, f); for arg in args { @@ -356,7 +410,7 @@ impl Type { let mutable = mutability.approx_to_string(); let lifetime = lifetime.approx_to_string(); format!("{}{} '{}", mutable, shared, lifetime) - } + }, Type::Struct(fields, id) => { let fields = fmap(fields, |(name, typ)| format!("{}: {}", name, typ.approx_to_string())); format!("{{ {}, ..tv{} }}", fields.join(", "), id.0) @@ -440,6 +494,13 @@ impl GeneralizedType { GeneralizedType::PolyType(_, _) => unreachable!(), } } + + pub fn priority(&self, cache: &ModuleCache<'_>) -> TypePriority { + match self { + GeneralizedType::MonoType(typ) => typ.priority(cache), + GeneralizedType::PolyType(..) => TypePriority::FORALL, + } + } } #[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord)] diff --git a/src/types/typeprinter.rs b/src/types/typeprinter.rs index d3fe87df..01c2030f 100644 --- a/src/types/typeprinter.rs +++ b/src/types/typeprinter.rs @@ -15,8 +15,9 @@ use std::fmt::{Debug, Display, Formatter}; use colored::*; use super::effects::EffectSet; -use super::GeneralizedType; use super::typechecker::follow_bindings_in_cache; +use super::GeneralizedType; +use super::TypePriority; /// Wrapper containing the information needed to print out a type pub struct TypePrinter<'a, 'b> { @@ -186,9 +187,14 @@ impl<'a, 'b> TypePrinter<'a, 'b> { } fn fmt_function(&self, function: &FunctionType, f: &mut Formatter) -> std::fmt::Result { - write!(f, "{}", "(".blue())?; for (i, param) in function.parameters.iter().enumerate() { + if TypePriority::FUN >= param.priority(&self.cache) { + write!(f, "{}", "(".blue())?; + } self.fmt_type(param, f)?; + if TypePriority::FUN >= param.priority(&self.cache) { + write!(f, "{}", ")".blue())?; + } write!(f, " ")?; if i != function.parameters.len() - 1 { @@ -206,12 +212,19 @@ impl<'a, 'b> TypePrinter<'a, 'b> { write!(f, "{}", "=> ".blue())?; } + // No parentheses are necessary if the precedence is the same, because `->` is right associative. + // i.e. `a -> b -> c` means `a -> (b -> c)` + if TypePriority::FUN > function.return_type.priority(&self.cache) { + write!(f, "{}", "(".blue())?; + } self.fmt_type(function.return_type.as_ref(), f)?; + if TypePriority::FUN > function.return_type.priority(&self.cache) { + write!(f, "{}", ")".blue())?; + } write!(f, "{}", " can ".blue())?; self.fmt_type(&function.effects, f)?; - - write!(f, "{}", ")".blue()) + Ok(()) } fn fmt_type_variable(&self, id: TypeVariableId, f: &mut Formatter) -> std::fmt::Result { @@ -232,55 +245,62 @@ impl<'a, 'b> TypePrinter<'a, 'b> { fn fmt_type_application(&self, constructor: &Type, args: &[Type], f: &mut Formatter) -> std::fmt::Result { if constructor.is_polymorphic_int_type() { - self.fmt_polymorphic_numeral(args, f, "Int") + self.fmt_polymorphic_numeral(&args[0], f, "Int") } else if constructor.is_polymorphic_float_type() { - self.fmt_polymorphic_numeral(args, f, "Float") + self.fmt_polymorphic_numeral(&args[0], f, "Float") } else { - write!(f, "{}", "(".blue())?; - if constructor.is_pair_type() { - self.fmt_pair(args, f)?; + self.fmt_pair(&args[0], &args[1], f)?; } else { self.fmt_type(constructor, f)?; - for arg in args.iter() { + for arg in args { write!(f, " ")?; + // `(app f (app a b))` should be represented as `f (a b)` + if TypePriority::APP >= arg.priority(&self.cache) { + write!(f, "{}", "(".blue())?; + } self.fmt_type(arg, f)?; + if TypePriority::APP >= arg.priority(&self.cache) { + write!(f, "{}", ")".blue())?; + } } } - - write!(f, "{}", ")".blue()) + Ok(()) } } - fn fmt_pair(&self, args: &[Type], f: &mut Formatter) -> std::fmt::Result { - assert_eq!(args.len(), 2); - - self.fmt_type(&args[0], f)?; - + fn fmt_pair(&self, arg1: &Type, arg2: &Type, f: &mut Formatter) -> std::fmt::Result { + if TypePriority::PAIR >= arg1.priority(&self.cache) { + write!(f, "{}", "(".blue())?; + } + self.fmt_type(arg1, f)?; + if TypePriority::PAIR >= arg1.priority(&self.cache) { + write!(f, "{}", ")".blue())?; + } write!(f, "{}", ", ".blue())?; - - match &args[1] { - Type::TypeApplication(constructor, args) if constructor.is_pair_type() => self.fmt_pair(args, f), - other => self.fmt_type(other, f), + // Because `(,)` is right-associative, omit parentheses if it has equal priority. + // e.g. `a, b, .., n, m` means `(a, (b, (.. (n, m)..)))`. + if TypePriority::PAIR > arg2.priority(&self.cache) { + write!(f, "{}", "(".blue())?; + } + self.fmt_type(arg2, f)?; + if TypePriority::PAIR > arg2.priority(&self.cache) { + write!(f, "{}", ")".blue())?; } + Ok(()) } - fn fmt_polymorphic_numeral(&self, args: &[Type], f: &mut Formatter, kind: &str) -> std::fmt::Result { - assert_eq!(args.len(), 1); - - match self.cache.follow_typebindings_shallow(&args[0]) { + fn fmt_polymorphic_numeral(&self, arg: &Type, f: &mut Formatter, kind: &str) -> std::fmt::Result { + match self.cache.follow_typebindings_shallow(arg) { Type::TypeVariable(_) => { - write!(f, "{}{} ", "(".blue(), kind.blue())?; - self.fmt_type(&args[0], f)?; - write!(f, "{}", ")".blue()) + write!(f, "{} ", kind.blue())?; + self.fmt_type(arg, f) }, other => self.fmt_type(other, f), } } - fn fmt_ref( - &self, shared: &Type, mutable: &Type, lifetime: &Type, f: &mut Formatter, - ) -> std::fmt::Result { + fn fmt_ref(&self, shared: &Type, mutable: &Type, lifetime: &Type, f: &mut Formatter) -> std::fmt::Result { let mutable = follow_bindings_in_cache(mutable, self.cache); let shared = follow_bindings_in_cache(shared, self.cache); let parenthesize = matches!(shared, Type::Tag(_)) || self.debug; @@ -310,14 +330,20 @@ impl<'a, 'b> TypePrinter<'a, 'b> { } fn fmt_forall(&self, typevars: &[TypeVariableId], typ: &Type, f: &mut Formatter) -> std::fmt::Result { - write!(f, "{}", "(forall".blue())?; - for typevar in typevars.iter() { + write!(f, "{}", "forall".blue())?; + for typevar in typevars { write!(f, " ")?; self.fmt_type_variable(*typevar, f)?; } write!(f, "{}", ". ".blue())?; - self.fmt_type(typ, f)?; - write!(f, "{}", ")".blue()) + if TypePriority::FORALL > typ.priority(&self.cache) { + write!(f, "{}", "(".blue())?; + self.fmt_type(typ, f)?; + write!(f, "{}", ")".blue())?; + } else { + self.fmt_type(typ, f)?; + } + Ok(()) } fn fmt_struct(