Skip to content

Commit

Permalink
Merge pull request #5775 from roc-lang/inspect-derive
Browse files Browse the repository at this point in the history
Derive Inspect
  • Loading branch information
bhansconnect authored Nov 29, 2023
2 parents bdebfc7 + 248976d commit ead9031
Show file tree
Hide file tree
Showing 45 changed files with 4,791 additions and 416 deletions.
2 changes: 1 addition & 1 deletion crates/cli/tests/cli_run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -942,7 +942,7 @@ mod cli_run {
test_roc_app_slim(
"examples",
"inspect-logging.roc",
r#"{people: [{firstName: "John", lastName: "Smith", age: 27, hasBeard: true, favoriteColor: Blue}, {firstName: "Debby", lastName: "Johnson", age: 47, hasBeard: false, favoriteColor: Green}, {firstName: "Jane", lastName: "Doe", age: 33, hasBeard: false, favoriteColor: (RGB (255, 255, 0))}], friends: [{2}, {2}, {0, 1}]}
r#"{friends: [{2}, {2}, {0, 1}], people: [{age: 27, favoriteColor: Blue, firstName: "John", hasBeard: Bool.true, lastName: "Smith"}, {age: 47, favoriteColor: Green, firstName: "Debby", hasBeard: Bool.false, lastName: "Johnson"}, {age: 33, favoriteColor: (RGB (255, 255, 0)), firstName: "Jane", hasBeard: Bool.false, lastName: "Doe"}]}
"#,
UseValgrind::Yes,
)
Expand Down
9 changes: 9 additions & 0 deletions crates/compiler/builtins/roc/Dict.roc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ interface Dict
Str,
Num.{ Nat, U64, U8, I8 },
Hash.{ Hasher, Hash },
Inspect.{ Inspect, Inspector, InspectFormatter },
]

## A [dictionary](https://en.wikipedia.org/wiki/Associative_array) that lets you
Expand Down Expand Up @@ -108,6 +109,9 @@ Dict k v := {
Hash {
hash: hashDict,
},
Inspect {
toInspector: toInspectorDict,
},
]

isEq : Dict k v, Dict k v -> Bool where k implements Hash & Eq, v implements Eq
Expand All @@ -126,6 +130,11 @@ isEq = \xs, ys ->
hashDict : hasher, Dict k v -> hasher where k implements Hash & Eq, v implements Hash, hasher implements Hasher
hashDict = \hasher, dict -> Hash.hashUnordered hasher (toList dict) List.walk

toInspectorDict : Dict k v -> Inspector f where k implements Inspect & Hash & Eq, v implements Inspect, f implements InspectFormatter
toInspectorDict = \dict ->
fmt <- Inspect.custom
Inspect.apply (Inspect.dict dict walk Inspect.toInspector Inspect.toInspector) fmt

## Return an empty dictionary.
## ```
## emptyDict = Dict.empty {}
Expand Down
271 changes: 263 additions & 8 deletions crates/compiler/builtins/roc/Inspect.roc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ interface Inspect
record,
bool,
str,
function,
opaque,
u8,
i8,
Expand All @@ -26,16 +27,19 @@ interface Inspect
i64,
u128,
i128,
nat,
f32,
f64,
dec,
custom,
apply,
toInspector,
DbgFormatter,
toDbgStr,
]
imports [
Bool.{ Bool },
Num.{ U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec },
Num.{ U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec, Nat },
List,
Str,
]
Expand All @@ -56,12 +60,13 @@ InspectFormatter implements
set : set, ElemWalker state set elem, (elem -> Inspector f) -> Inspector f where f implements InspectFormatter
dict : dict, KeyValWalker state dict key value, (key -> Inspector f), (value -> Inspector f) -> Inspector f where f implements InspectFormatter

# Note opaque is used for both opaque types and functions.
# The auto deriver for functions probably could put the function type.
# For regular opaque types, I think we can use the type name, though that may lead to some reflection related issues that still need to be discussed.
# As a simple baseline, it can just use the exact words `opaque` and `function` for now.
# In text, this would render as `<opaque>`, `<function>`, etc
opaque : Str -> Inspector f where f implements InspectFormatter
# In text, this would render as `<opaque>`
# TODO: Pass the type name to opaque so that it can be displayed.
opaque : * -> Inspector f where f implements InspectFormatter

# In text, this would render as `<function>`
# TODO: Maybe pass the the function name or signiture to function so that it can be displayed.
function : * -> Inspector f where f implements InspectFormatter

u8 : U8 -> Inspector f where f implements InspectFormatter
i8 : I8 -> Inspector f where f implements InspectFormatter
Expand All @@ -73,14 +78,15 @@ InspectFormatter implements
i64 : I64 -> Inspector f where f implements InspectFormatter
u128 : U128 -> Inspector f where f implements InspectFormatter
i128 : I128 -> Inspector f where f implements InspectFormatter
nat : Nat -> Inspector f where f implements InspectFormatter
f32 : F32 -> Inspector f where f implements InspectFormatter
f64 : F64 -> Inspector f where f implements InspectFormatter
dec : Dec -> Inspector f where f implements InspectFormatter

Inspector f := f -> f where f implements InspectFormatter

custom : (f -> f) -> Inspector f where f implements InspectFormatter
custom = @Inspector
custom = \fn -> @Inspector fn

apply : Inspector f, f -> f where f implements InspectFormatter
apply = \@Inspector fn, fmt -> fn fmt
Expand All @@ -92,3 +98,252 @@ inspect : val -> f where val implements Inspect, f implements InspectFormatter
inspect = \val ->
(@Inspector valFn) = toInspector val
valFn (init {})

# The current default formatter for inspect.
# This just returns a simple string for debugging.
# More powerful formatters will likely be wanted in the future.
DbgFormatter := { data : Str }
implements [
InspectFormatter {
init: dbgInit,
list: dbgList,
set: dbgSet,
dict: dbgDict,
tag: dbgTag,
tuple: dbgTuple,
record: dbgRecord,
bool: dbgBool,
str: dbgStr,
opaque: dbgOpaque,
function: dbgFunction,
u8: dbgU8,
i8: dbgI8,
u16: dbgU16,
i16: dbgI16,
u32: dbgU32,
i32: dbgI32,
u64: dbgU64,
i64: dbgI64,
u128: dbgU128,
i128: dbgI128,
nat: dbgNat,
f32: dbgF32,
f64: dbgF64,
dec: dbgDec,
},
]

dbgInit : {} -> DbgFormatter
dbgInit = \{} -> @DbgFormatter { data: "" }

dbgList : list, ElemWalker (DbgFormatter, Bool) list elem, (elem -> Inspector DbgFormatter) -> Inspector DbgFormatter
dbgList = \content, walkFn, toDbgInspector ->
f0 <- custom
dbgWrite f0 "["
|> \f1 ->
(f2, prependSep), elem <- walkFn content (f1, Bool.false)
f3 =
if prependSep then
dbgWrite f2 ", "
else
f2

elem
|> toDbgInspector
|> apply f3
|> \f4 -> (f4, Bool.true)
|> .0
|> dbgWrite "]"

dbgSet : set, ElemWalker (DbgFormatter, Bool) set elem, (elem -> Inspector DbgFormatter) -> Inspector DbgFormatter
dbgSet = \content, walkFn, toDbgInspector ->
f0 <- custom
dbgWrite f0 "{"
|> \f1 ->
(f2, prependSep), elem <- walkFn content (f1, Bool.false)
f3 =
if prependSep then
dbgWrite f2 ", "
else
f2

elem
|> toDbgInspector
|> apply f3
|> \f4 -> (f4, Bool.true)
|> .0
|> dbgWrite "}"

dbgDict : dict, KeyValWalker (DbgFormatter, Bool) dict key value, (key -> Inspector DbgFormatter), (value -> Inspector DbgFormatter) -> Inspector DbgFormatter
dbgDict = \d, walkFn, keyToInspector, valueToInspector ->
f0 <- custom
dbgWrite f0 "{"
|> \f1 ->
(f2, prependSep), key, value <- walkFn d (f1, Bool.false)
f3 =
if prependSep then
dbgWrite f2 ", "
else
f2

apply (keyToInspector key) f3
|> dbgWrite ": "
|> \x -> apply (valueToInspector value) x
|> \f4 -> (f4, Bool.true)
|> .0
|> dbgWrite "}"

dbgTag : Str, List (Inspector DbgFormatter) -> Inspector DbgFormatter
dbgTag = \name, fields ->
if List.isEmpty fields then
f0 <- custom
dbgWrite f0 name
else
f0 <- custom
dbgWrite f0 "("
|> dbgWrite name
|> \f1 ->
f2, inspector <- List.walk fields f1
dbgWrite f2 " "
|> \x -> apply inspector x
|> dbgWrite ")"

dbgTuple : List (Inspector DbgFormatter) -> Inspector DbgFormatter
dbgTuple = \fields ->
f0 <- custom
dbgWrite f0 "("
|> \f1 ->
(f2, prependSep), inspector <- List.walk fields (f1, Bool.false)
f3 =
if prependSep then
dbgWrite f2 ", "
else
f2

apply inspector f3
|> \f4 -> (f4, Bool.true)
|> .0
|> dbgWrite ")"

dbgRecord : List { key : Str, value : Inspector DbgFormatter } -> Inspector DbgFormatter
dbgRecord = \fields ->
f0 <- custom
dbgWrite f0 "{"
|> \f1 ->
(f2, prependSep), { key, value } <- List.walk fields (f1, Bool.false)
f3 =
if prependSep then
dbgWrite f2 ", "
else
f2

dbgWrite f3 key
|> dbgWrite ": "
|> \x -> apply value x
|> \f4 -> (f4, Bool.true)
|> .0
|> dbgWrite "}"

dbgBool : Bool -> Inspector DbgFormatter
dbgBool = \b ->
if b then
f0 <- custom
dbgWrite f0 "Bool.true"
else
f0 <- custom
dbgWrite f0 "Bool.false"

dbgStr : Str -> Inspector DbgFormatter
dbgStr = \s ->
f0 <- custom
f0
|> dbgWrite "\""
|> dbgWrite s # TODO: Should we be escaping strings for dbg/logging?
|> dbgWrite "\""

dbgOpaque : * -> Inspector DbgFormatter
dbgOpaque = \_ ->
f0 <- custom
dbgWrite f0 "<opaque>"

dbgFunction : * -> Inspector DbgFormatter
dbgFunction = \_ ->
f0 <- custom
dbgWrite f0 "<function>"

dbgU8 : U8 -> Inspector DbgFormatter
dbgU8 = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)

dbgI8 : I8 -> Inspector DbgFormatter
dbgI8 = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)

dbgU16 : U16 -> Inspector DbgFormatter
dbgU16 = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)

dbgI16 : I16 -> Inspector DbgFormatter
dbgI16 = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)

dbgU32 : U32 -> Inspector DbgFormatter
dbgU32 = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)

dbgI32 : I32 -> Inspector DbgFormatter
dbgI32 = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)

dbgU64 : U64 -> Inspector DbgFormatter
dbgU64 = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)

dbgI64 : I64 -> Inspector DbgFormatter
dbgI64 = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)

dbgU128 : U128 -> Inspector DbgFormatter
dbgU128 = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)

dbgI128 : I128 -> Inspector DbgFormatter
dbgI128 = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)

dbgNat : Nat -> Inspector DbgFormatter
dbgNat = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)

dbgF32 : F32 -> Inspector DbgFormatter
dbgF32 = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)

dbgF64 : F64 -> Inspector DbgFormatter
dbgF64 = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)

dbgDec : Dec -> Inspector DbgFormatter
dbgDec = \num ->
f0 <- custom
dbgWrite f0 (num |> Num.toStr)

dbgWrite : DbgFormatter, Str -> DbgFormatter
dbgWrite = \@DbgFormatter { data }, added ->
@DbgFormatter { data: Str.concat data added }

toDbgStr : DbgFormatter -> Str
toDbgStr = \@DbgFormatter { data } -> data
9 changes: 9 additions & 0 deletions crates/compiler/builtins/roc/Set.roc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ interface Set
Dict.{ Dict },
Num.{ Nat },
Hash.{ Hash, Hasher },
Inspect.{ Inspect, Inspector, InspectFormatter },
]

## Provides a [set](https://en.wikipedia.org/wiki/Set_(abstract_data_type))
Expand All @@ -37,6 +38,9 @@ Set k := Dict.Dict k {} where k implements Hash & Eq
Hash {
hash: hashSet,
},
Inspect {
toInspector: toInspectorSet,
},
]

isEq : Set k, Set k -> Bool where k implements Hash & Eq
Expand All @@ -53,6 +57,11 @@ isEq = \xs, ys ->
hashSet : hasher, Set k -> hasher where k implements Hash & Eq, hasher implements Hasher
hashSet = \hasher, @Set inner -> Hash.hash hasher inner

toInspectorSet : Set k -> Inspector f where k implements Inspect & Hash & Eq, f implements InspectFormatter
toInspectorSet = \set ->
fmt <- Inspect.custom
Inspect.apply (Inspect.set set walk Inspect.toInspector) fmt

## Creates a new empty `Set`.
## ```
## emptySet = Set.empty {}
Expand Down
1 change: 1 addition & 0 deletions crates/compiler/can/src/def.rs
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,7 @@ fn canonicalize_opaque<'a>(

let ability_region = ability.region;

// Op := {} has [Eq]
let (ability, members) = match ability.value {
ast::TypeAnnotation::Apply(module_name, ident, []) => {
match make_apply_symbol(env, region, scope, module_name, ident) {
Expand Down
Loading

0 comments on commit ead9031

Please sign in to comment.