From: Brendan Hansen Date: Mon, 20 Mar 2023 21:29:43 +0000 (-0500) Subject: added: `#doc` sets notes on procedures X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=2e7f6acd27cc08d86edb3232fda83f0275996780;p=onyx.git added: `#doc` sets notes on procedures --- diff --git a/compiler/include/astnodes.h b/compiler/include/astnodes.h index 4569d856..f41112b0 100644 --- a/compiler/include/astnodes.h +++ b/compiler/include/astnodes.h @@ -1046,7 +1046,7 @@ struct AstDistinctType { }; // Top level nodes -struct AstBinding { AstTyped_base; AstNode* node; }; +struct AstBinding { AstTyped_base; AstNode* node; OnyxToken *documentation; }; struct AstAlias { AstTyped_base; AstTyped* alias; }; struct AstInclude { AstNode_base; AstTyped* name_node; char* name; }; struct AstInjection { diff --git a/compiler/include/parser.h b/compiler/include/parser.h index 76961fd9..bdeede54 100644 --- a/compiler/include/parser.h +++ b/compiler/include/parser.h @@ -47,6 +47,11 @@ typedef struct OnyxParser { // in other files. u32 overload_count; + // Used by `#doc` directives to store their documentation + // string. This is then used by binding nodes to capture + // documentation. + OnyxToken *last_documentation_token; + u16 tag_depth : 16; b32 hit_unexpected_token : 1; diff --git a/compiler/src/doc.c b/compiler/src/doc.c index 5ec29f83..5dd73334 100644 --- a/compiler/src/doc.c +++ b/compiler/src/doc.c @@ -337,6 +337,14 @@ static void write_entity_header(bh_buffer *buffer, AstBinding *binding, OnyxFile static b32 write_doc_procedure(bh_buffer *buffer, AstBinding *binding, AstNode *proc); +static void write_doc_notes(bh_buffer *buffer, AstBinding *binding) { + if (!binding || !binding->documentation) { + write_cstring(buffer, ""); + } else { + write_string(buffer, binding->documentation->length, binding->documentation->text); + } +} + static b32 write_doc_function(bh_buffer *buffer, AstBinding *binding, AstNode *proc) { AstFunction *func = (void *) proc; if (func->kind == Ast_Kind_Macro) { @@ -346,7 +354,7 @@ static b32 write_doc_function(bh_buffer *buffer, AstBinding *binding, AstNode *p write_entity_header(buffer, binding, func->token->pos); // Notes - write_cstring(buffer, ""); + write_doc_notes(buffer, binding); // Flags bh_buffer_write_u32(buffer, proc->kind == Ast_Kind_Macro ? Doc_Procedure_Flag_Macro : 0); @@ -378,7 +386,7 @@ static b32 write_doc_overloaded_function(bh_buffer *buffer, AstBinding *binding, write_entity_header(buffer, binding, ofunc->token->pos); // Notes - write_cstring(buffer, ""); + write_doc_notes(buffer, binding); // Flags bh_buffer_write_u32(buffer, Doc_Procedure_Flag_Overloaded); @@ -414,7 +422,7 @@ static b32 write_doc_polymorphic_proc(bh_buffer *buffer, AstBinding *binding, As write_entity_header(buffer, binding, func->token->pos); // Notes - write_cstring(buffer, ""); + write_doc_notes(buffer, binding); // Flags bh_buffer_write_u32(buffer, proc->kind == Ast_Kind_Macro ? Doc_Procedure_Flag_Macro : 0); diff --git a/compiler/src/parser.c b/compiler/src/parser.c index c10afe1d..2ae6a784 100644 --- a/compiler/src/parser.c +++ b/compiler/src/parser.c @@ -3525,7 +3525,7 @@ static void parse_top_level_statement(OnyxParser* parser) { // This is a future feature I want to add to the language, proper docstrings. // For now (and so I start documenting thing...), #doc can be used anywhere // at top level, followed by a string to add a doc string. - expect_token(parser, Token_Type_Literal_String); + parser->last_documentation_token = expect_token(parser, Token_Type_Literal_String); return; } else { @@ -3567,6 +3567,11 @@ submit_binding_to_entities: { if (!binding) return; + if (parser->last_documentation_token) { + binding->documentation = parser->last_documentation_token; + parser->last_documentation_token = NULL; + } + // // If this binding is inside an #inject block, // swap the binding node for an injection node @@ -3717,6 +3722,7 @@ OnyxParser onyx_parser_create(bh_allocator alloc, OnyxTokenizer *tokenizer) { parser.tag_depth = 0; parser.overload_count = 0; parser.injection_point = NULL; + parser.last_documentation_token = NULL; parser.polymorph_context = (PolymorphicContext) { .root_node = NULL, diff --git a/core/container/iter.onyx b/core/container/iter.onyx index bc08cf93..ec7e10d5 100644 --- a/core/container/iter.onyx +++ b/core/container/iter.onyx @@ -28,29 +28,31 @@ use core.intrinsics.types {type_is_struct} -// -// The standard function to convert something to an Iterator. -// For-loops currently do not use this function to determine -// how to iterate over something unknown, but that could be -// a feature down the line. +#doc """ + The standard function to convert something to an Iterator. + For-loops currently do not use this function to determine + how to iterate over something unknown, but that could be + a feature down the line. +""" as_iter :: #match -> Iterator {} -// -// Helper interface to test if something can be passed to -// as_iter successfully. +#doc """ + Helper interface to test if something can be passed to + as_iter successfully. +""" Iterable :: interface (t: $T) { { as_iter(t) } -> Iterator; } -// -// Helper function to get the next value out of an iterator. +#doc "Helper function to get the next value out of an iterator." next :: (it: Iterator) -> (it.Iter_Type, bool) { return it.next(it.data); } -// -// Helper function to close an iterator, if a close function -// is defined. +#doc """ + Helper function to close an iterator, if a close function + is defined. +""" close :: (it: Iterator) { if it.close != null_proc { it.close(it.data); @@ -102,8 +104,7 @@ HasAsIter :: interface (t: $T) { // Most of these procedures come in two variants, // one that takes a context paramter, and one that does not. -// -// Only yields the values for which the predicate is true. +#doc "Only yields the values for which the predicate is true." filter :: #match #local {} #overload @@ -149,9 +150,10 @@ filter :: (it: Iterator($T), ctx: $Ctx, predicate: (T, Ctx) -> bool) => fi => { close(fi.iterator); }); -// -// Transforms every value that comes out of an iterator -// using the transform function. +#doc """ + Transforms every value that comes out of an iterator + using the transform function. +""" map :: #match #local {} #overload @@ -187,8 +189,7 @@ map :: (it: Iterator($T), ctx: $Ctx, transform: (T, Ctx) -> $R) => mi => { close(mi.iterator); }) -// -// Only yields the first `count` values, then closes. +#doc "Only yields the first `count` values, then closes." take :: (it: Iterator($T), count: u32) -> Iterator(T) { return generator( &.{ iterator = it, remaining = count }, @@ -206,8 +207,7 @@ take :: (it: Iterator($T), count: u32) -> Iterator(T) { } -// -// Yields values while the predicate returns true. +#doc "Yields values while the predicate returns true." take_while :: (it: Iterator($T), predicate: (T) -> bool) -> Iterator(T) { return generator( &.{ iterator = it, predicate = predicate }, @@ -223,8 +223,7 @@ take_while :: (it: Iterator($T), predicate: (T) -> bool) -> Iterator(T) { } -// -// Discards the first `count` values and yields all remaining values, +#doc "Discards the first `count` values and yields all remaining values." skip :: (it: Iterator($T), count: u32) -> Iterator(T) { return generator( &.{ iterator = it, to_skip = count, skipped = false }, @@ -247,8 +246,7 @@ skip :: (it: Iterator($T), count: u32) -> Iterator(T) { } -// -// Discards values while the predicate is true, then yields all values. +#doc "Discards values while the predicate is true, then yields all values." skip_while :: #match #local {} #overload @@ -304,9 +302,10 @@ skip_while :: (it: Iterator($T), ctx: $Ctx, predicate: (T, Ctx) -> bool) -> Iter } -// -// Combines two iterators into one by yielding a Pair of -// the value from each of the iterators. +#doc """ + Combines two iterators into one by yielding a Pair of + the value from each of the iterators. +""" zip :: (left_iterator: Iterator($T), right_iterator: Iterator($R)) -> Iterator(Pair(T, R)) { return generator( &.{ left_iter = left_iterator, right_iter = right_iterator }, @@ -322,9 +321,11 @@ zip :: (left_iterator: Iterator($T), right_iterator: Iterator($R)) -> Iterator(P } -// -// Combines iterators by first yielding all values from -// one, then yielding all values from the next, and so on. + +#doc """ + Combines iterators by first yielding all values from + one, then yielding all values from the next, and so on. +""" concat :: (iters: ..Iterator($T)) -> Iterator(T) { return generator( &.{ @@ -353,16 +354,17 @@ concat :: (iters: ..Iterator($T)) -> Iterator(T) { }); } -// -// Yields the same value indefinitely. Useful with iter.zip. +#doc "Yields the same value indefinitely. Useful with `iter.zip`." const :: (value: $T) -> Iterator(T) { return generator(&.{ v = value }, c => (c.v, true)); } -// -// Yields a value that contains: 1) the value from the iterator, -// and 2) an incrementing integer. +#doc """ + Yields a value that contains: + 1) the value from the iterator, + 2) an incrementing integer. +""" enumerate :: #match #local {} #overload @@ -394,23 +396,26 @@ enumerate :: (it: Iterator($T), start_index: i32 = 0) -> Iterator(Enumeration_Va -// -// Extract the next value out of an iterator. Closes it when -// the iterator is out of values, if no_close is false. +#doc """ + Extract the next value out of an iterator. Closes it when + the iterator is out of values, if no_close is false. +""" take_one :: (it: Iterator($T), no_close := false) -> (T, bool) { ret, cont := next(it); if !cont && !no_close { close(it); } return ret, cont; } -// Macro that allows you to extract elements from an iterator in a simple way: -// -// value: i32; -// iterator: Iterator(i32) = ...; -// -// if #(value) << iterator { -// ...iterater closed... -// } +#doc """ + Macro that allows you to extract elements from an iterator in a simple way: + + value: i32; + iterator: Iterator(i32) = ...; + + if #(value) << iterator { + ...iterater closed... + } +""" #operator << macro (dest: Code, it: Iterator($T)) -> bool { take_one :: take_one @@ -429,15 +434,16 @@ take_one :: (it: Iterator($T), no_close := false) -> (T, bool) { #overload as_iter :: from_array -// -// `from_array` has two almost identical implementations, -// but the details are important here. Normally, `from_array` -// returns an iterator by value, unless the array is of -// structures, then it returns an iterator by pointer. -// This seems weird, but in practice it is closer to what -// you want, as you don't want to have to copy every structure -// out of the array. While for primitives, you don't want to -// dereference it everywhere. +#doc """ + `from_array` has two almost identical implementations, + but the details are important here. Normally, `from_array` + returns an iterator by value, unless the array is of + structures, then it returns an iterator by pointer. + This seems weird, but in practice it is closer to what + you want, as you don't want to have to copy every structure + out of the array. While for primitives, you don't want to + dereference it everywhere. +""" from_array :: #match #local {} #overload @@ -483,9 +489,10 @@ from_array :: (arr: [] $T) => generator( ); -// -// Iterators created from pointers to dynamic arrays are -// special, because they support the #remove directive. +#doc """ + Iterators created from pointers to dynamic arrays are + special, because they support the #remove directive. +""" #local generic_dynamic_array_as_iter :: (x: &[..] $T, $access: Code, $return_type: type_expr) => { Context :: struct (T: type_expr) { @@ -563,10 +570,11 @@ as_iter :: (r: range) => generator( // Iterator reducing // -// -// Incremently calls `combine` on the yielded value and the -// accumulated value, producing a new accumulated value. Returns -// the final accumulated value. +#doc """ + Incremently calls `combine` on the yielded value and the + accumulated value, producing a new accumulated value. Returns + the final accumulated value. +""" fold :: #match #local {} #overload @@ -583,8 +591,7 @@ fold :: (it: Iterator($T), initial_value: $R, combine: (T, R) -> R) -> R { } -// -// Returns how many times the `cond` was true. +#doc "Returns how many times the `cond` was true." count :: #match #local {} #overload @@ -600,8 +607,7 @@ count :: (it: Iterator($T), cond: (T) -> bool) -> i32 { -// -// Returns if `cond` returned true for *any* yielded value. +#doc "Returns if `cond` returned true for *any* yielded value." some :: #match #local {} #overload @@ -615,8 +621,7 @@ some :: (it: Iterator($T), cond: (T) -> bool) -> bool { } -// -// Returns if `cond` returned true for *all* yielded values. +#doc "Returns if `cond` returned true for *all* yielded values." every :: #match #local {} #overload @@ -630,9 +635,10 @@ every :: (it: Iterator($T), cond: (T) -> bool) -> bool { } -// -// Places all yielded values into a dynamically allocated array, -// using the allocator provided (context.allocator by default). +#doc """ + Places all yielded values into a dynamically allocated array, + using the allocator provided (context.allocator by default). +""" to_array :: (it: Iterator($T), allocator := context.allocator) -> [..] T { arr := array.make(T, allocator=allocator); for v: it do array.push(&arr, v); @@ -641,17 +647,18 @@ to_array :: (it: Iterator($T), allocator := context.allocator) -> [..] T { } -// -// Produces an iterator that first yields all values from the -// first iterable, combined with the first yield value from the -// second iterable. Then, steps the second iterable, and repeats. -// -// For example, -// -// iter.prod(1 .. 4, 1 .. 3) -// -// Would yield: -// (1, 1), (2, 1), (3, 1), (1, 2), (2, 2), (3, 2) +#doc """ + Produces an iterator that first yields all values from the + first iterable, combined with the first yield value from the + second iterable. Then, steps the second iterable, and repeats. + + For example, + + iter.prod(1 .. 4, 1 .. 3) + + Would yield: + (1, 1), (2, 1), (3, 1), (1, 2), (2, 2), (3, 2) +""" prod :: #match #local {} #overload @@ -691,14 +698,15 @@ prod :: (x: $I1/Iterable, y_iter: Iterator($Y)) => { } -// -// Simple iterator comprehensions, in the same vein -// as Pythons comprehension syntax. -// -// Python: -// results = [it * 2 for it in [1, 2, 3, 4, 5]] -// Onyx: -// results := iter.comp(u32.[1, 2, 3, 4, 5], #(it * 2)); +#doc """ + Simple iterator comprehensions, in the same vein + as Pythons comprehension syntax. + + Python: + results = [it * 2 for it in [1, 2, 3, 4, 5]] + Onyx: + results := iter.comp(u32.[1, 2, 3, 4, 5], #(it * 2)); +""" comp :: #match #local {} #overload @@ -727,13 +735,14 @@ comp :: macro (i: $I/Iterable, value: Code) => #this_package.comp(#this_package.as_iter(i), value); -// -// Using the polymorph solving system, you can write type -// free versions of arbitrary iterators. This is used -// heavily by many of the functions defined above. -// -// Maybe at some point an alternate allocator would be good -// for this? For now, I think the temporary allocator is sufficient. +#doc """ + Using the polymorph solving system, you can write type + free versions of arbitrary iterators. This is used + heavily by many of the functions defined above. + + Maybe at some point an alternate allocator would be good + for this? For now, I think the temporary allocator is sufficient. +""" generator :: #match #local {} #overload @@ -820,14 +829,14 @@ generator_no_copy :: (ctx: &$Ctx, gen: (&Ctx) -> ($T, bool), close: (&Ctx) -> vo return .{c, #solidify next {T=it.Iter_Type}, #solidify close {T=it.Iter_Type}}; } - // - // Allows you to easily write a parallelized for-loop over an iterator. - // For example, - // - // iter.parallel_for(1 .. 100, 4, &.{}) { - // printf("Thread {} has {}!\n", context.thread_id, it); - // } - // + #doc """ + Allows you to easily write a parallelized for-loop over an iterator. + For example, + + iter.parallel_for(1 .. 100, 4, &.{}) { + printf("Thread {} has {}!\n", context.thread_id, it); + } + """ parallel_for :: #match #local {} #overload diff --git a/core/container/map.onyx b/core/container/map.onyx index f3a927d0..1d69cf40 100644 --- a/core/container/map.onyx +++ b/core/container/map.onyx @@ -4,9 +4,9 @@ use core {array, hash, memory, math, conv, Optional} use core.intrinsics.onyx { __initialize } #doc """ - Map is a generic hash-map implementation that uses chaining. - Values can be of any type. Keys must of a type that supports - the core.hash.hash, and the '==' operator. + Map is a generic hash-map implementation that uses chaining. + Values can be of any type. Keys must of a type that supports + the core.hash.hash, and the '==' operator. """ @conv.Custom_Format.{ #solidify format_map {K=Key_Type, V=Value_Type} } Map :: struct (Key_Type: type_expr, Value_Type: type_expr) where ValidKey(Key_Type) { @@ -70,8 +70,7 @@ make :: macro ($Key: type_expr, $Value: type_expr, default := Value.{}) -> Map(K return map; } -// -// Initializes a map. +#doc "Initializes a map." init :: (use map: &Map($K, $V), default := V.{}) { __initialize(map); @@ -84,20 +83,21 @@ init :: (use map: &Map($K, $V), default := V.{}) { array.init(&entries, allocator=allocator); } -// -// Allows for deletion of a Map using delete(&map). +#doc "Allows for deletion of a Map using `delete(&map)`." #match builtin.delete core.map.free -// -// Destroys a map and frees all memory. +#doc """ + Destroys a map and frees all memory. +""" free :: (use map: &Map) { if hashes.data != null do memory.free_slice(&hashes, allocator=allocator); if entries.data != null do array.free(&entries); } -// -// Sets the value at the specified key, or creates a new entry -// if the key was not already present. +#doc """ + Sets the value at the specified key, or creates a new entry + if the key was not already present. +""" put :: (use map: &Map, key: map.Key_Type, value: map.Value_Type) { lr := lookup(map, key); @@ -112,20 +112,21 @@ put :: (use map: &Map, key: map.Key_Type, value: map.Value_Type) { if full(map) do grow(map); } -// -// Returns true if the map contains the key. +#doc """ + Returns true if the map contains the key. +""" has :: (use map: &Map, key: map.Key_Type) -> bool { lr := lookup(map, key); return lr.entry_index >= 0; } -// -// Returns the value at the specified key, or map.default_value if -// the key is not present. -// -// This is subject to change with the addition of Optional to the -// standard library. -// +#doc """ + Returns the value at the specified key, or map.default_value if + the key is not present. + + This is subject to change with the addition of Optional to the + standard library. +""" get :: (use map: &Map, key: map.Key_Type) -> map.Value_Type { lr := lookup(map, key); if lr.entry_index >= 0 do return entries[lr.entry_index].value; @@ -133,9 +134,10 @@ get :: (use map: &Map, key: map.Key_Type) -> map.Value_Type { return default_value; } -// -// Returns a pointer to the value at the specified key, or null if -// the key is not present. +#doc """ + Returns a pointer to the value at the specified key, or null if + the key is not present. +""" get_ptr :: (use map: &Map, key: map.Key_Type) -> &map.Value_Type { lr := lookup(map, key); if lr.entry_index >= 0 do return &entries[lr.entry_index].value; @@ -143,10 +145,11 @@ get_ptr :: (use map: &Map, key: map.Key_Type) -> &map.Value_Type { return null; } -// -// Returns a pointer to the value at the specified key. If the key -// is not in the map, a new value is created and inserted, then the -// pointer to that value is returned. +#doc """ + Returns a pointer to the value at the specified key. If the key + is not in the map, a new value is created and inserted, then the + pointer to that value is returned. +""" get_ptr_or_create :: (use map: &Map, key: map.Key_Type) -> &map.Value_Type { lr := lookup(map, key); if lr.entry_index < 0 { @@ -157,10 +160,11 @@ get_ptr_or_create :: (use map: &Map, key: map.Key_Type) -> &map.Value_Type { return &entries[lr.entry_index].value; } -// -// Returns an Optional of the value at the specified key. The Optional -// has a value if the key is present, otherwise the optional does not -// have a value. +#doc """ + Returns an Optional of the value at the specified key. The Optional + has a value if the key is present, otherwise the optional does not + have a value. +""" get_opt :: (use map: &Map, key: map.Key_Type) -> ?map.Value_Type { lr := lookup(map, key); if lr.entry_index >= 0 do return Optional.make(entries[lr.entry_index].value); @@ -168,8 +172,7 @@ get_opt :: (use map: &Map, key: map.Key_Type) -> ?map.Value_Type { return .{}; } -// -// Removes an entry from the map. +#doc "Removes an entry from the map." delete :: (use map: &Map, key: map.Key_Type) { lr := lookup(map, key); if lr.entry_index < 0 do return; @@ -211,23 +214,24 @@ update :: macro (map: ^Map, key: map.Key_Type, body: Code) { } } -// -// Removes all entries from the hash map. Does NOT -// modify memory, so be wary of dangling pointers! +#doc """ + Removes all entries from the hash map. Does NOT + modify memory, so be wary of dangling pointers! +""" clear :: (use map: &Map) { for i: 0 .. hashes.count do hashes.data[i] = -1; entries.count = 0; } -// -// Returns if the map does not contain any elements. +#doc "Returns if the map does not contain any elements." empty :: (use map: &Map) -> bool { return entries.count == 0; } -// -// Helper procedure to nicely format a Map when printing. -// Rarely ever called directly, instead used by conv.format_any. +#doc """ + Helper procedure to nicely format a Map when printing. + Rarely ever called directly, instead used by conv.format_any. +""" format_map :: (output: &conv.Format_Output, format: &conv.Format, x: &Map($K, $V)) { if format.pretty_printing { output->write("{\n"); @@ -246,14 +250,14 @@ format_map :: (output: &conv.Format_Output, format: &conv.Format, x: &Map($K, $V } } -// -// Quickly create a Map with some entries. -// -// Map.literal(str, i32, .[ -// .{ "test", 123 }, -// .{ "foo", 456 }, -// ]); -// +#doc """ + Quickly create a Map with some entries. + + Map.literal(str, i32, .[ + .{ "test", 123 }, + .{ "foo", 456 }, + ]); +""" literal :: ($Key: type_expr, $Value: type_expr, values: [] MapLiteralValue(Key, Value)) => { m := core.map.make(Key, Value); for & values { @@ -269,9 +273,10 @@ MapLiteralValue :: struct (K: type_expr, V: type_expr) { value: V; } -// -// Produces an iterator that yields all values of the map, -// in an unspecified order, as Map is unordered. +#doc """ + Produces an iterator that yields all values of the map, + in an unspecified order, as Map is unordered. +""" as_iter :: (m: &Map) => core.iter.generator( &.{ m = m, i = 0 }, diff --git a/core/encoding/base64.onyx b/core/encoding/base64.onyx index e1153c39..e8345cd5 100644 --- a/core/encoding/base64.onyx +++ b/core/encoding/base64.onyx @@ -9,10 +9,11 @@ use core {array} // standards. // -// -// Encodes the given data in base64 into a new buffer, allocated -// from the allocator provided. It is the callers responsibilty -// to free this memory. +#doc """ + Encodes the given data in base64 into a new buffer, allocated + from the allocator provided. It is the callers responsibilty + to free this memory. +""" encode :: (data: [] u8, allocator := context.allocator) -> [] u8 { out := array.make(u8, allocator=allocator); @@ -46,9 +47,10 @@ encode :: (data: [] u8, allocator := context.allocator) -> [] u8 { return out; } -// -// Decodes the given base64 data into a new buffer, allocated -// from the allocator provided. +#doc """ + Decodes the given base64 data into a new buffer, allocated + from the allocator provided. +""" decode :: (data: [] u8, allocator := context.allocator) -> [] u8 { if data.count % 4 != 0 do return null_str; diff --git a/core/hash/hash.onyx b/core/hash/hash.onyx index b3345d50..2639ec24 100644 --- a/core/hash/hash.onyx +++ b/core/hash/hash.onyx @@ -6,19 +6,19 @@ package core.hash // core.hash.hash instead. to_u32 :: hash -// -// This overloaded procedure defines how to hash something. -// It is used throughout the standard library to hash values. -// There are many overloads to it in the standard library, and -// more can be added using #overload. Or, a hash method can be -// defined for a structure or distinct type. -// -// Person :: struct { -// hash :: (p: Person) { -// return // ... -// } -// } -// +#doc """ + This overloaded procedure defines how to hash something. + It is used throughout the standard library to hash values. + There are many overloads to it in the standard library, and + more can be added using #overload. Or, a hash method can be + defined for a structure or distinct type. + + Person :: struct { + hash :: (p: Person) { + return // ... + } + } +""" hash :: #match -> u32 { // Does this need to have a higher precedence value? // Because if I wanted to have a custom type as the key