diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2324c63663..ebe271d27c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -429,6 +429,7 @@
[3964]: https://github.com/enso-org/enso/pull/3964
[3967]: https://github.com/enso-org/enso/pull/3967
[3987]: https://github.com/enso-org/enso/pull/3987
+[3878]: https://github.com/enso-org/enso/pull/3878
[3997]: https://github.com/enso-org/enso/pull/3997
[4013]: https://github.com/enso-org/enso/pull/4013
[4026]: https://github.com/enso-org/enso/pull/4026
@@ -514,6 +515,7 @@
- [Sync language server with file system after VCS restore][4020]
- [`ArrayOverBuffer` behaves like an `Array` and `Array.sort` no longer sorts in
place][4022]
+- [Implement hashing functionality for all objects][3878]
- [Introducing Meta.atom_with_hole][4023]
- [Report failures in name resolution in type signatures][4030]
- [Attach visualizations to sub-expressions][4048]
diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Map.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Map.enso
index 6e6921eaee..feb240088d 100644
--- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Map.enso
+++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Map.enso
@@ -1,181 +1,89 @@
-import project.Any.Any
import project.Data.Numbers.Integer
-import project.Data.Ordering.Ordering
-import project.Data.Map.Internal
+import project.Data.Vector.Vector
import project.Data.Pair.Pair
import project.Data.Text.Text
-import project.Data.Vector.Vector
-import project.Error.Error
-import project.Error.No_Such_Key.No_Such_Key
-import project.Nothing.Nothing
from project.Data.Boolean import Boolean, True, False
+from project import Error, Nothing, Any, Panic
+from project.Error.No_Such_Key import No_Such_Key
-## A key-value store. This type assumes all keys are pairwise comparable,
- using the `<`, `>` and `==` operators.
-type Map
+
+## A key-value store. It is possible to use any type as keys and values and mix them in
+ one Map. Keys are checked for equality based on their hash code and `==` operator, which
+ is both an internal part of Enso. Enso is capable of computing a hash code, and checking
+ for equality any objects that can appear in Enso - primitives, Atoms, values coming from
+ different languages, etc.
+
+ A single key-value pair is called an *entry*.
+
+ It is possible to pass a Map created in Enso to foreign functions, where it will be treated
+ as appropriate map structures - in Python that is a dictionary, and in JavaScript, it is
+ a `Map`. And likewise, it is possible to pass a foreign map into Enso, where it will be
+ treated as a Map.
+@Builtin_Type
+type Map key value
## Returns an empty map.
-
- > Example
- Create an empty map.
-
- import Standard.Base.Data.Map.Map
-
- example_empty = Map.empty
empty : Map
- empty = Map.Tip
+ empty = @Builtin_Method "Map.empty"
- ## Returns a single-element map with the given key and value present.
+ ## Returns a single-element map with the given key and value.
+ A Call to `Map.singleton key value` is the same as a call to
+ `Map.empty.insert key value`.
Arguments:
- - key: The key to update in the map.
- - value: The value to store against 'key' in the map.
+ - key: The key to to use for `value` in the map.
+ - value: The value to store under 'key' in the map.
> Example
- Create a single element map storing the key 1 and the value 2.
+ Create a single element map storing the key "my_key" and the value 2.
import Standard.Base.Data.Map.Map
- example_singleton = Map.singleton 1 2
+ example_singleton = Map.singleton "my_key" 2
singleton : Any -> Any -> Map
- singleton key value = Map.Bin 1 key value Map.Tip Map.Tip
+ singleton key value = Map.empty.insert key value
- ## Builds a map from a vector of key-value pairs.
+ ## Builds a map from a vector of key-value pairs, with each key-value pair
+ represented as a 2 element vector.
Arguments:
- - vec: A vector of key-value pairs.
+ - vec: A vector of key-value pairs (2 element vectors).
> Example
Building a map containing two key-value pairs.
import Standard.Base.Data.Map.Map
- example_from_vector = Map.from_vector [[1, 2], [3, 4]]
+ example_from_vector = Map.from_vector [["A", 1], ["B", 2]]
from_vector : Vector Any -> Map
from_vector vec = vec.fold Map.empty (m -> el -> m.insert (el.at 0) (el.at 1))
- ## PRIVATE
- A key-value store. This type assumes all keys are pairwise comparable,
- using the `<`, `>` and `==` operators.
- Tip
-
- ## PRIVATE
- A key-value store. This type assumes all keys are pairwise comparable,
- using the `<`, `>` and `==` operators.
-
- Arguments:
- - s: The size of the tree at this node.
- - key: The key stored at this node.
- - value: The value stored at this node.
- - left: The left subtree.
- - right: The right subtree.
- Bin s key value left right
-
- ## Checks if the map is empty.
-
- > Example
- Check if a map is empty.
-
- import Standard.Base.Data.Map.Map
- import Standard.Examples
-
- example_is_empty = Examples.map.is_empty
+ ## Returns True iff the Map is empty, i.e., does not have any entries.
is_empty : Boolean
- is_empty self = case self of
- Map.Bin _ _ _ _ _ -> False
- Map.Tip -> True
+ is_empty self = self.size == 0
- ## Checks if the map is not empty.
-
- > Example
- Check if a map is not empty.
-
- import Standard.Base.Data.Map.Map
- import Standard.Examples
-
- example_not_empty = Examples.map.not_empty
+ ## Returns True iff the Map is not empty, i.e., has at least one entry.
not_empty : Boolean
not_empty self = self.is_empty.not
## Returns the number of entries in this map.
-
- > Example
- Get the size of a map.
-
- import Standard.Base.Data.Map.Map
- import Standard.Examples
-
- example_size = Examples.map.size
size : Integer
- size self = case self of
- Map.Bin s _ _ _ _ -> s
- Map.Tip -> 0
-
- ## Converts the map into a vector of `[key, value]` pairs.
-
- The returned vector is sorted in the increasing order of keys.
-
- > Example
- Convert a map to a vector.
-
- import Standard.Base.Data.Map.Map
- import Standard.Examples
-
- example_to_vector = Examples.map.to_vector
- to_vector : Vector Any
- to_vector self =
- builder = Vector.new_builder
- to_vector_with_builder m = case m of
- Map.Bin _ k v l r ->
- to_vector_with_builder l
- builder.append [k, v]
- to_vector_with_builder r
- Nothing
- Map.Tip -> Nothing
- to_vector_with_builder self
- result = builder.to_vector
- result
-
- ## Returns a text representation of this map.
-
- > Example
- Convert a map to text.
-
- import Standard.Base.Data.Map.Map
- import Standard.Examples
-
- example_to_text = Examples.map.to_text
- to_text : Text
- to_text self = self.to_vector.to_text
-
- ## Checks if this map is equal to another map.
-
- Arguments:
- - that: The map to compare `self` to.
-
- Maps are equal when they contained the same keys and the values
- associated with each key are pairwise equal.
-
- > Example
- Checking two maps for equality.
-
- import Standard.Base.Data.Map.Map
- import Standard.Examples
-
- example_equals =
- other = Map.empty . insert 1 "one" . insert 3 "three" . insert 5 "five"
- Examples.map == other
- == : Map -> Boolean
- == self that = case that of
- _ : Map -> self.to_vector == that.to_vector
- _ -> False
+ size self = @Builtin_Method "Map.size"
## Inserts a key-value mapping into this map, overriding any existing
instance of `key` with the new `value`.
+ Note that since the return type is also a `Map`, multiple `insert`
+ calls can be chained, e.g., `map.insert "A" 1 . insert "B" 2`.
+
+ Due to the limitation of the current implementation, inserts with a
+ key that is already contained in the map, or insert on a map instance that
+ is re-used in other computations, have a linear time complexity.
+ For all the other cases, the time complexity of this method is constant.
+
Arguments:
- key: The key to insert the value for.
- - value: The value to associate with `key`.
+ - value: The value to associate with the `key`.
> Example
Insert the value "seven" into the map for the key 7.
@@ -185,27 +93,50 @@ type Map
example_insert = Examples.map.insert 7 "seven"
insert : Any -> Any -> Map
- insert self key value = Internal.insert self key value
+ insert self key value = @Builtin_Method "Map.insert"
- ## Gets the value associated with `key` in this map, or throws a
- `No_Such_Key.Error` if `key` is not present.
+ ## Removes an entry specified by the given key from this map, and
+ returns a new map without this entry. Throw `No_Such_Key.Error`
+ if `key` is not present.
Arguments:
- key: The key to look up in the map.
> Example
- Get the value for the key 1 in a map.
+ Remove key "A" from a map
+
+ import Standard.Data.Map.Map
+
+ Examples.map.remove "A"
+
+ remove : Any -> Map ! No_Such_Key
+ remove self key =
+ Panic.catch Any (self.remove_builtin key) _->
+ Error.throw No_Such_Key.Error self key
+
+ ## Gets the value associated with `key` in this map, or throws a
+ `No_Such_Key.Error` if `key` is not present.
+
+ This method has a constant time complexity.
+
+ Arguments:
+ - key: The key to look up in the map.
+
+ > Example
+ Looks up the value for the key "A" in a map.
import Standard.Base.Data.Map.Map
import Standard.Examples
- example_at = Examples.map.at 1
+ example_at = Examples.map.at "A"
at : Any -> Any ! No_Such_Key
at self key = self.get key (Error.throw (No_Such_Key.Error self key))
## Gets the value associated with `key` in this map, or returns
`if_missing` if it isn't present.
+ This method has a constant time complexity.
+
Arguments:
- key: The key to look up in the map.
- if_missing: The value to use if the key isn't present.
@@ -219,57 +150,19 @@ type Map
example_get = Examples.map.get 2 "zero"
get : Any -> Any -> Any
- get self key ~if_missing=Nothing =
- go map = case map of
- Map.Tip -> if_missing
- Map.Bin _ k v l r -> case Internal.compare_allow_nothing key k of
- Ordering.Equal -> v
- Ordering.Less -> @Tail_Call go l
- Ordering.Greater -> @Tail_Call go r
- result = go self
- result
+ get self key ~if_missing=Nothing = self.get_builtin key if_missing
- ## Checks if a key is in the map.
-
- Arguments:
- - key: The key to look up in the map.
-
- > Example
- Checks the key 2 is in a map.
-
- import Standard.Base.Data.Map.Map
- import Standard.Examples
-
- example_contains = Examples.map.contains_key 2
+ ## Returns True iff the Map contains the given `key`.
contains_key : Any -> Boolean
- contains_key self key =
- go map = case map of
- Map.Tip -> False
- Map.Bin _ k _ l r -> case Internal.compare_allow_nothing key k of
- Ordering.Equal -> True
- Ordering.Less -> @Tail_Call go l
- Ordering.Greater -> @Tail_Call go r
- go self
+ contains_key self key = @Builtin_Method "Map.contains_key"
- ## Transforms the map's keys and values to create a new map.
+ ## Returns an unsorted vector of all the keys in this Map.
+ keys : Vector Any
+ keys self = self.to_vector.map pair-> pair.at 0
- Arguments:
- - function: The function used to transform the map, taking a key and a
- value and returning a pair of `[key, value]`.
-
- > Example
- Turn all keys into `Text` and append "_word" to the values in the map.
-
- import Standard.Base.Data.Map.Map
- import Standard.Examples
-
- example_transform =
- Examples.map.transform (k -> v -> [k.to_text, v + "_word"])
- transform : (Any -> Any -> [Any, Any]) -> Map
- transform self function =
- func_pairs = p -> function (p.at 0) (p.at 1)
- vec_transformed = self.to_vector.map func_pairs
- Map.from_vector vec_transformed
+ ## Returns an unsorted vector of all the values in this Map.
+ values : Vector Any
+ values self = self.to_vector.map pair-> pair.at 1
## Maps a function over each value in this map.
@@ -306,11 +199,10 @@ type Map
Examples.map.map_with_key (k -> v -> k.to_text + "-" + v)
map_with_key : (Any -> Any -> Any) -> Map
map_with_key self function =
- go map = case map of
- Map.Bin s k v l r ->
- Map.Bin s k (function k v) (go l) (go r)
- Map.Tip -> Map.Tip
- go self
+ Map.from_vector <| self.to_vector.map pair->
+ key = pair.first
+ value = pair.last
+ [key, (function key value)]
## Maps a function over each key in this map.
@@ -330,6 +222,62 @@ type Map
trans_function = k -> v -> [function k, v]
self.transform trans_function
+ ## Transforms the map's keys and values to create a new map.
+
+ Arguments:
+ - function: The function used to transform the map, taking a key and a
+ value and returning a pair of `[key, value]`.
+
+ > Example
+ Turn all keys into `Text` and append "_word" to the values in the map.
+
+ import Standard.Base.Data.Map.Map
+ import Standard.Examples
+
+ example_transform =
+ Examples.map.transform (k -> v -> [k.to_text, v + "_word"])
+ transform : (Any -> Any -> [Any, Any]) -> Map
+ transform self function =
+ func_pairs = p -> function (p.at 0) (p.at 1)
+ vec_transformed = self.to_vector.map func_pairs
+ Map.from_vector vec_transformed
+
+ ## Combines the values in the map.
+
+ Arguments:
+ - init: The initial value for the fold.
+ - function: A binary function to apply to pairs of values in the map.
+
+ > Example
+ Find the length of the longest word in the map.
+
+ import Standard.Base.Data.Map.Map
+ import Standard.Examples
+
+ example_fold = Examples.map.fold 0 (l -> r -> Math.max l r.length)
+ fold : Any -> (Any -> Any -> Any) -> Any
+ fold self init function = self.values.fold init function
+
+ ## Combines the key-value pairs in the map.
+
+ Arguments:
+ - init: The initial value for the fold.
+ - function: A function taking the left value, the current key, and the
+ current value, and combining them to yield a single value.
+
+ > Example
+ Glue the values in the map together with the keys.
+
+ import Standard.Base.Data.Map.Map
+ import Standard.Examples
+
+ example_fold_with_key =
+ Examples.map.fold_with_key "" (l -> k -> v -> l + k.to_text + v)
+ fold_with_key : Any -> (Any -> Any -> Any -> Any) -> Any
+ fold_with_key self init function =
+ self.to_vector.fold init acc-> pair->
+ function acc pair.first pair.last
+
## Applies a function to each value in the map.
Arguments:
@@ -371,121 +319,20 @@ type Map
IO.println v
each_with_key : (Any -> Any -> Any) -> Nothing
each_with_key self function =
- go map = case map of
- Map.Bin _ k v l r ->
- go l
- function k v
- go r
- Nothing
- Map.Tip -> Nothing
- go self
+ self.to_vector.each pair->
+ function pair.first pair.last
- ## Combines the values in the map.
+ ## Returns an unsorted vector of key-value pairs (nested 2 element vectors).
+ `Map.from_vector` method is an inverse method, so the following expression
+ is true for all maps: `Map.from_vector map.to_vector == map`.
+ to_vector : Vector Any
+ to_vector self = @Builtin_Method "Map.to_vector"
- Arguments:
- - init: The initial value for the fold.
- - function: A binary function to apply to pairs of values in the map.
+ ## Returns a text representation of this Map.
+ to_text : Text
+ to_text self = @Builtin_Method "Map.to_text"
- > Example
- Find the length of the longest word in the map.
+ ## PRIVATE
+ get_builtin : Any -> Any -> Any
+ get_builtin self key ~if_missing = @Builtin_Method "Map.get_builtin"
- import Standard.Base.Data.Map.Map
- import Standard.Examples
-
- example_fold = Examples.map.fold 0 (l -> r -> Math.max l r.length)
- fold : Any -> (Any -> Any -> Any) -> Any
- fold self init function =
- go map init = case map of
- Map.Bin _ _ v l r ->
- y = go l init
- z = function y v
- go r z
- Map.Tip -> init
- go self init
-
- ## Combines the key-value pairs in the map.
-
- Arguments:
- - init: The initial value for the fold.
- - function: A function taking the left value, the current key, and the
- current value, and combining them to yield a single value.
-
- > Example
- Glue the values in the map together with the keys.
-
- import Standard.Base.Data.Map.Map
- import Standard.Examples
-
- example_fold_with_key =
- Examples.map.fold_with_key "" (l -> k -> v -> l + k.to_text + v)
- fold_with_key : Any -> (Any -> Any -> Any -> Any) -> Any
- fold_with_key self init function =
- go map init = case map of
- Map.Bin _ k v l r ->
- y = go l init
- z = function y k v
- go r z
- Map.Tip -> init
- go self init
-
- ## Get a vector containing the keys in the map.
-
- > Example
- Get the keys from the map `m`.
-
- import Standard.Base.Data.Map.Map
- import Standard.Examples
-
- example_keys = Examples.map.keys
- keys : Vector
- keys self =
- builder = Vector.new_builder
- to_vector_with_builder m = case m of
- Map.Bin _ k _ l r ->
- to_vector_with_builder l
- builder.append k
- to_vector_with_builder r
- Nothing
- Map.Tip -> Nothing
- to_vector_with_builder self
- builder.to_vector
-
- ## Get a vector containing the values in the map.
-
- > Example
- Get the values from the map `m`.
-
- import Standard.Base.Data.Map.Map
- import Standard.Examples
-
- example_values = Examples.map.values
- values : Vector
- values self =
- builder = Vector.new_builder
- to_vector_with_builder m = case m of
- Map.Bin _ _ v l r ->
- to_vector_with_builder l
- builder.append v
- to_vector_with_builder r
- Nothing
- Map.Tip -> Nothing
- to_vector_with_builder self
- builder.to_vector
-
- ## Get a key value pair of the lowest key in the map.
- If the map is empty, returns Nothing.
- first : Pair
- first self =
- first p m = case m of
- Map.Bin _ k v l _ -> @Tail_Call first (Pair.new k v) l
- Map.Tip -> p
- first Nothing self
-
- ## Get a key value pair of the highest key in the map.
- If the map is empty, returns Nothing.
- last : Pair
- last self =
- last p m = case m of
- Map.Bin _ k v _ r -> @Tail_Call last (Pair.new k v) r
- Map.Tip -> p
- last Nothing self
diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Map/Internal.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Map/Internal.enso
deleted file mode 100644
index b25945550a..0000000000
--- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Map/Internal.enso
+++ /dev/null
@@ -1,164 +0,0 @@
-import project.Any.Any
-import project.Data.Map.Map
-import project.Data.Numbers.Integer
-import project.Data.Ordering.Ordering
-
-## PRIVATE
- Compares keys allowing for the possibility that one or both keys are Nothing.
-compare_allow_nothing : Any -> Any -> Ordering
-compare_allow_nothing x y = if x == y then Ordering.Equal else
- if x.is_nothing then Ordering.Less else
- if y.is_nothing then Ordering.Greater else
- x.compare_to y
-
-## PRIVATE
-
- A helper used in the insert operation to insert into the left subtree.
-
- Arguments:
- - key: The key to insert.
- - value: The value to insert.
- - k: The previous top key of the left subtree.
- - v: The previous top value of the left subtree.
- - l: The left subtree.
- - r: The right subtree.
-insert_l : Any -> Any -> Any -> Any -> Map -> Map -> Map
-insert_l key value k v l r =
- new_left = insert l key value
- balance_left k v new_left r
-
-## PRIVATE
-
- A helper used in the insert operation to insert into the right subtree.
-
- Arguments:
- - key: The key to insert.
- - value: The value to insert.
- - k: The previous top key of the right subtree.
- - v: The previous top value of the right subtree.
- - l: The left subtree.
- - r: The right subtree.
-insert_r : Any -> Any -> Any -> Any -> Map -> Map -> Map
-insert_r key value k v l r =
- new_right = insert r key value
- balance_right k v l new_right
-
-## PRIVATE
-
- Helper for inserting a new key-value pair into a map.
-
- Arguments:
- - map: The map into which the insertion is performed.
- - key: The key for which to insert the value into the map.
- - value: The value to insert into the map at the given key.
-
- The algorithm used here is based on the paper "Implementing Sets Efficiently
- in a Functional Language" by Stephen Adams. The implementation is based on
- Haskell's `Data.Map.Strict` as implemented in the `containers` package.
-insert : Map -> Any -> Any -> Map
-insert map key value = case map of
- Map.Bin s k v l r -> case compare_allow_nothing key k of
- Ordering.Less -> @Tail_Call insert_l key value k v l r
- Ordering.Greater -> @Tail_Call insert_r key value k v l r
- Ordering.Equal -> Map.Bin s key value l r
- _ -> Map.Bin 1 key value Map.Tip Map.Tip
-
-## PRIVATE
-
- Re-balances the map after the left subtree grows.
-
- Arguments:
- - k: The old top key of the left subtree.
- - x: The old top value of the left subtree.
- - l: The left subtree.
- - r: The right subtree.
-balance_left : Any -> Any -> Map -> Map -> Map
-balance_left k x l r = case r of
- Map.Bin rs _ _ _ _ -> case l of
- Map.Bin ls lk lx ll lr ->
- if ls <= delta*rs then Map.Bin 1+ls+rs k x l r else
- lls = size ll
- case lr of
- Map.Bin lrs lrk lrx lrl lrr ->
- if lrs < ratio*lls then Map.Bin 1+ls+rs lk lx ll (Map.Bin 1+rs+lrs k x lr r) else
- lrls = size lrl
- lrrs = size lrr
- Map.Bin 1+ls+rs lrk lrx (Map.Bin 1+lls+lrls lk lx ll lrl) (Map.Bin 1+rs+lrrs k x lrr r)
- _ -> Map.Bin 1+rs k x Map.Tip r
- _ -> case l of
- Map.Tip -> Map.Bin 1 k x Map.Tip Map.Tip
- Map.Bin _ _ _ Map.Tip Map.Tip -> Map.Bin 2 k x l Map.Tip
- Map.Bin _ lk lx Map.Tip (Map.Bin _ lrk lrx _ _) -> Map.Bin 3 lrk lrx (Map.Bin 1 lk lx Map.Tip Map.Tip) (Map.Bin 1 k x Map.Tip Map.Tip)
- Map.Bin _ lk lx ll Map.Tip -> Map.Bin 3 lk lx ll (Map.Bin 1 k x Map.Tip Map.Tip)
- Map.Bin ls lk lx ll lr -> case lr of
- Map.Bin lrs lrk lrx lrl lrr ->
- lls = size ll
- if lrs < ratio*lls then Map.Bin 1+ls lk lx ll (Map.Bin 1+lrs k x lr Map.Tip) else
- lrls = size lrl
- lrrs = size lrr
- Map.Bin 1+ls lrk lrx (Map.Bin 1+lls+lrls lk lx ll lrl) (Map.Bin 1+lrrs k x lrr Map.Tip)
-
-## PRIVATE
-
- Re-balances the map after the right subtree grows.
-
- Arguments:
- - k: The old top key of the right subtree.
- - x: The old top value of the right subtree.
- - l: The left subtree.
- - r: The right subtree.
-balance_right : Any -> Any -> Map -> Map -> Map
-balance_right k x l r = case l of
- Map.Bin ls _ _ _ _ -> case r of
- Map.Bin rs rk rx rl rr ->
- if rs <= delta*ls then Map.Bin 1+ls+rs k x l r else
- case rl of
- Map.Bin rls rlk rlx rll rlr ->
- rrs = size rr
- if rls < ratio*rrs then Map.Bin 1+ls+rs rk rx (Map.Bin 1+ls+rls k x l rl) rr else
- rlls = size rll
- rlrs = size rlr
- Map.Bin 1+ls+rs rlk rlx (Map.Bin 1+ls+rlls k x l rll) (Map.Bin 1+rrs+rlrs rk rx rlr rr)
- _ -> Map.Bin 1+ls k x l Map.Tip
- _ -> case r of
- Map.Tip -> Map.Bin 1 k x Map.Tip Map.Tip
- Map.Bin _ _ _ Map.Tip Map.Tip -> Map.Bin 2 k x Map.Tip r
- Map.Bin _ rk rx Map.Tip rr -> Map.Bin 3 rk rx (Map.Bin 1 k x Map.Tip Map.Tip) rr
- Map.Bin _ rk rx (Map.Bin _ rlk rlx _ _) Map.Tip -> Map.Bin 3 rlk rlx (Map.Bin 1 k x Map.Tip Map.Tip) (Map.Bin 1 rk rx Map.Tip Map.Tip)
- Map.Bin rs rk rx rl rr -> case rl of
- Map.Bin rls rlk rlx rll rlr -> case rr of
- Map.Bin rrs _ _ _ _ ->
- if rls < ratio*rrs then Map.Bin 1+rs rk rx (Map.Bin 1+rls k x Map.Tip rl) rr else
- srll = size rll
- srlr = size rlr
- Map.Bin 1+rs rlk rlx (Map.Bin 1+srll k x Map.Tip rll) (Map.Bin 1+rrs+srlr rk rx rlr rr)
-
-## PRIVATE
-
- Controls the difference between inner and outer siblings of a heavy subtree.
- Used to decide between a double and a single rotation.
-
- The choice of values for `ratio` and `delta` is taken from the Haskell
- implementation.
-ratio : Integer
-ratio = 2
-
-## PRIVATE
-
- Controls the maximum size difference between subtrees.
-
- The choice of values for `ratio` and `delta` is taken from the Haskell
- implementation.
-delta : Integer
-delta = 3
-
-## PRIVATE
-
- Gets the size of a map.
-
- Arguments:
- - m: The map to get the size of.
-size : Map -> Integer
-size m = case m of
- Map.Bin s _ _ _ _ -> s
- _ -> 0
diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Encoding.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Encoding.enso
index 508d617d7d..44e23ed7a8 100644
--- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Encoding.enso
+++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Encoding.enso
@@ -15,8 +15,7 @@ type Encoding
Used to provide auto completion in the UI.
all_character_sets : Vector Text
all_character_sets =
- java_array = Charset.availableCharsets.keySet.toArray
- Vector.from_polyglot_array java_array
+ Charset.availableCharsets.keys
## Get all available Encodings.
all_encodings : Vector Encoding
diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Text_Ordering.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Text_Ordering.enso
index da10b1d249..07099d3759 100644
--- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Text_Ordering.enso
+++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Text_Ordering.enso
@@ -27,7 +27,7 @@ type Text_Ordering
this to `True` results in a "Natural" ordering.
Case_Sensitive (sort_digits_as_numbers:Boolean=False)
- ## Case sensitive ordering of values.
+ ## Case insensitive ordering of values.
It will ensure case-insensitive ordering regardless of backend defaults.
This may make database queries more complicated and may result in being
diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Zone.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Zone.enso
index e637deb220..00757b76b9 100644
--- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Zone.enso
+++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Time_Zone.enso
@@ -99,9 +99,9 @@ type Time_Zone
> Example
Get time zone 1 hour 1 minute and 50 seconds from UTC.
- from Standard.Base import Zone
+ from Standard.Base.Time.Time_Zone import Time_Zone
- example_new = Zone.new 1 1 50
+ example_new = Time_Zone.new 1 1 50
new : Integer -> Integer -> Integer -> Time_Zone
new (hours = 0) (minutes = 0) (seconds = 0) =
new_builtin hours minutes seconds
diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/JDBC_Connection.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/JDBC_Connection.enso
index a9106bed07..01fa278f38 100644
--- a/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/JDBC_Connection.enso
+++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Internal/JDBC_Connection.enso
@@ -138,8 +138,9 @@ create : Text -> Vector -> JDBC_Connection
create url properties = handle_sql_errors <|
java_props = Properties.new
properties.each pair->
- if pair.second.is_nothing.not then java_props.setProperty pair.first pair.second else
- java_props.remove pair.first
+ case pair.second of
+ Nothing -> Polyglot.invoke java_props "remove" [pair.first]
+ _ -> Polyglot.invoke java_props "setProperty" [pair.first, pair.second]
java_connection = JDBCProxy.getConnection url java_props
resource = Managed_Resource.register java_connection close_connection
diff --git a/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/runtime/PolyglotProxy.java b/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/runtime/PolyglotProxy.java
index 72524df563..3c259ddf7f 100644
--- a/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/runtime/PolyglotProxy.java
+++ b/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/runtime/PolyglotProxy.java
@@ -9,6 +9,7 @@ import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.UnknownIdentifierException;
+import com.oracle.truffle.api.interop.UnknownKeyException;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.interop.UnsupportedTypeException;
import com.oracle.truffle.api.library.CachedLibrary;
@@ -422,6 +423,131 @@ public final class PolyglotProxy implements TruffleObject {
}
}
+ @ExportMessage
+ public boolean hasHashEntries(
+ @CachedLibrary("this.delegate") InteropLibrary hashMaps,
+ @CachedLibrary("this") InteropLibrary node,
+ @CachedLibrary(limit = "5") InteropLibrary errors,
+ @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode,
+ @Cached @Cached.Exclusive BranchProfile profile) {
+ Object p = enterOrigin(node);
+ try {
+ return hashMaps.hasHashEntries(this.delegate);
+ } catch (Throwable e) {
+ profile.enter();
+ if (errors.isException(e)) {
+ // `isException` means this must be AbstractTruffleException
+ //noinspection ConstantConditions
+ throw contextRewrapExceptionNode.execute((AbstractTruffleException) e, origin, target);
+ } else {
+ throw e;
+ }
+ } finally {
+ leaveOrigin(node, p);
+ }
+ }
+
+ @ExportMessage
+ public long getHashSize(
+ @CachedLibrary("this.delegate") InteropLibrary hashes,
+ @CachedLibrary("this") InteropLibrary node,
+ @CachedLibrary(limit = "5") InteropLibrary errors,
+ @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode,
+ @Cached @Cached.Exclusive BranchProfile profile)
+ throws UnsupportedMessageException {
+ Object p = enterOrigin(node);
+ try {
+ return hashes.getHashSize(this.delegate);
+ } catch (Throwable e) {
+ profile.enter();
+ if (errors.isException(e)) {
+ // `isException` means this must be AbstractTruffleException
+ //noinspection ConstantConditions
+ throw contextRewrapExceptionNode.execute((AbstractTruffleException) e, origin, target);
+ } else {
+ throw e;
+ }
+ } finally {
+ leaveOrigin(node, p);
+ }
+ }
+
+ @ExportMessage
+ public boolean isHashEntryReadable(
+ Object key,
+ @CachedLibrary("this.delegate") InteropLibrary hashes,
+ @CachedLibrary("this") InteropLibrary node,
+ @CachedLibrary(limit = "5") InteropLibrary errors,
+ @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode,
+ @Cached @Cached.Exclusive BranchProfile profile) {
+ Object p = enterOrigin(node);
+ try {
+ return hashes.isHashEntryReadable(this.delegate, key);
+ } catch (Throwable e) {
+ profile.enter();
+ if (errors.isException(e)) {
+ // `isException` means this must be AbstractTruffleException
+ //noinspection ConstantConditions
+ throw contextRewrapExceptionNode.execute((AbstractTruffleException) e, origin, target);
+ } else {
+ throw e;
+ }
+ } finally {
+ leaveOrigin(node, p);
+ }
+ }
+
+ @ExportMessage
+ public Object readHashValue(
+ Object key,
+ @CachedLibrary("this.delegate") InteropLibrary hashes,
+ @CachedLibrary("this") InteropLibrary node,
+ @CachedLibrary(limit = "5") InteropLibrary errors,
+ @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode,
+ @Cached @Cached.Exclusive BranchProfile profile)
+ throws UnsupportedMessageException, UnknownKeyException {
+ Object p = enterOrigin(node);
+ try {
+ return hashes.readHashValue(this.delegate, key);
+ } catch (Throwable e) {
+ profile.enter();
+ if (errors.isException(e)) {
+ // `isException` means this must be AbstractTruffleException
+ //noinspection ConstantConditions
+ throw contextRewrapExceptionNode.execute((AbstractTruffleException) e, origin, target);
+ } else {
+ throw e;
+ }
+ } finally {
+ leaveOrigin(node, p);
+ }
+ }
+
+ @ExportMessage
+ public Object getHashEntriesIterator(
+ @CachedLibrary("this.delegate") InteropLibrary hashes,
+ @CachedLibrary("this") InteropLibrary node,
+ @CachedLibrary(limit = "5") InteropLibrary errors,
+ @Cached @Cached.Exclusive ContextRewrapExceptionNode contextRewrapExceptionNode,
+ @Cached @Cached.Exclusive BranchProfile profile)
+ throws UnsupportedMessageException {
+ Object p = enterOrigin(node);
+ try {
+ return hashes.getHashEntriesIterator(this.delegate);
+ } catch (Throwable e) {
+ profile.enter();
+ if (errors.isException(e)) {
+ // `isException` means this must be AbstractTruffleException
+ //noinspection ConstantConditions
+ throw contextRewrapExceptionNode.execute((AbstractTruffleException) e, origin, target);
+ } else {
+ throw e;
+ }
+ } finally {
+ leaveOrigin(node, p);
+ }
+ }
+
@ExportMessage
public boolean isString(
@CachedLibrary("this.delegate") InteropLibrary strings,
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java
index d45e723738..1b4a81301b 100644
--- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java
+++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java
@@ -272,6 +272,28 @@ public abstract class InvokeMethodNode extends BaseNode {
return invokeFunctionNode.execute(function, frame, state, arguments);
}
+ @Specialization(
+ guards = {
+ "!types.hasType(self)",
+ "!types.hasSpecialDispatch(self)",
+ "getPolyglotCallType(self, symbol, interop, methodResolverNode) == CONVERT_TO_HASH_MAP",
+ })
+ Object doConvertHashMap(
+ VirtualFrame frame,
+ State state,
+ UnresolvedSymbol symbol,
+ Object self,
+ Object[] arguments,
+ @CachedLibrary(limit = "10") InteropLibrary interop,
+ @CachedLibrary(limit = "10") TypesLibrary types,
+ @Cached MethodResolverNode methodResolverNode) {
+ var ctx = EnsoContext.get(this);
+ var hashMapType = ctx.getBuiltins().map();
+ var function = methodResolverNode.expectNonNull(self, hashMapType, symbol);
+ arguments[0] = self;
+ return invokeFunctionNode.execute(function, frame, state, arguments);
+ }
+
@Specialization(
guards = {
"!types.hasType(self)",
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/resolver/HostMethodCallNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/resolver/HostMethodCallNode.java
index 3377996513..fa9d6f62ce 100644
--- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/resolver/HostMethodCallNode.java
+++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/resolver/HostMethodCallNode.java
@@ -80,6 +80,11 @@ public abstract class HostMethodCallNode extends Node {
* Standard.Base.Data.Time.Time_Zone} and dispatching natively.
*/
CONVERT_TO_TIME_ZONE,
+ /**
+ * The method call should be handled by converting {@code self} to a {@code
+ * Standard.Base.Data.Map} and dispatching natively.
+ */
+ CONVERT_TO_HASH_MAP,
/** The method call should be handled by dispatching through the {@code Any} type. */
NOT_SUPPORTED;
@@ -99,7 +104,8 @@ public abstract class HostMethodCallNode extends Node {
&& this != CONVERT_TO_DURATION
&& this != CONVERT_TO_ZONED_DATE_TIME
&& this != CONVERT_TO_TIME_OF_DAY
- && this != CONVERT_TO_TIME_ZONE;
+ && this != CONVERT_TO_TIME_ZONE
+ && this != CONVERT_TO_HASH_MAP;
}
}
@@ -163,6 +169,8 @@ public abstract class HostMethodCallNode extends Node {
return PolyglotCallType.CONVERT_TO_ARRAY;
}
}
+ } else if (library.hasHashEntries(self)) {
+ return PolyglotCallType.CONVERT_TO_HASH_MAP;
}
String methodName = symbol.getName();
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/EqualsAnyNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/EqualsAnyNode.java
index 990717acfe..6da55f4415 100644
--- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/EqualsAnyNode.java
+++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/EqualsAnyNode.java
@@ -1,6 +1,7 @@
package org.enso.interpreter.node.expression.builtin.meta;
import com.ibm.icu.text.Normalizer;
+import com.ibm.icu.text.Normalizer2;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Fallback;
@@ -9,7 +10,9 @@ import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.interop.ArityException;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
+import com.oracle.truffle.api.interop.StopIterationException;
import com.oracle.truffle.api.interop.UnknownIdentifierException;
+import com.oracle.truffle.api.interop.UnknownKeyException;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.interop.UnsupportedTypeException;
import com.oracle.truffle.api.library.CachedLibrary;
@@ -31,6 +34,7 @@ import org.enso.interpreter.runtime.callable.atom.Atom;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.data.Type;
+import org.enso.interpreter.runtime.data.text.Text;
import org.enso.interpreter.runtime.error.WarningsLibrary;
import org.enso.interpreter.runtime.number.EnsoBigInteger;
import org.enso.interpreter.runtime.state.State;
@@ -157,18 +161,46 @@ public abstract class EqualsAnyNode extends Node {
}
}
+ @Specialization(limit = "3")
+ boolean equalsTexts(Text selfText, Text otherText,
+ @CachedLibrary("selfText") InteropLibrary selfInterop,
+ @CachedLibrary("otherText") InteropLibrary otherInterop) {
+ if (selfText.is_normalized() && otherText.is_normalized()) {
+ return selfText.toString().compareTo(otherText.toString()) == 0;
+ } else {
+ return equalsStrings(selfText, otherText, selfInterop, otherInterop);
+ }
+ }
+
/** Interop libraries **/
@Specialization(guards = {
- "selfInterop.isNull(selfNull)",
- "otherInterop.isNull(otherNull)"
+ "selfInterop.isNull(selfNull) || otherInterop.isNull(otherNull)",
}, limit = "3")
boolean equalsNull(
Object selfNull, Object otherNull,
@CachedLibrary("selfNull") InteropLibrary selfInterop,
@CachedLibrary("otherNull") InteropLibrary otherInterop
) {
- return true;
+ return selfInterop.isNull(selfNull) && otherInterop.isNull(otherNull);
+ }
+
+ @Specialization(guards = {
+ "isHostObject(selfHostObject)",
+ "isHostObject(otherHostObject)",
+ })
+ boolean equalsHostObjects(
+ Object selfHostObject, Object otherHostObject,
+ @CachedLibrary(limit = "5") InteropLibrary interop
+ ) {
+ try {
+ return interop.asBoolean(
+ interop.invokeMember(selfHostObject, "equals", otherHostObject)
+ );
+ } catch (UnsupportedMessageException | ArityException | UnknownIdentifierException |
+ UnsupportedTypeException e) {
+ throw new IllegalStateException(e);
+ }
}
@Specialization(guards = {
@@ -373,6 +405,43 @@ public abstract class EqualsAnyNode extends Node {
}
}
+ @Specialization(guards = {
+ "selfInterop.hasHashEntries(selfHashMap)",
+ "otherInterop.hasHashEntries(otherHashMap)"
+ }, limit = "3")
+ boolean equalsHashMaps(Object selfHashMap, Object otherHashMap,
+ @CachedLibrary("selfHashMap") InteropLibrary selfInterop,
+ @CachedLibrary("otherHashMap") InteropLibrary otherInterop,
+ @CachedLibrary(limit = "5") InteropLibrary entriesInterop,
+ @Cached EqualsAnyNode equalsNode) {
+ try {
+ int selfHashSize = (int) selfInterop.getHashSize(selfHashMap);
+ int otherHashSize = (int) otherInterop.getHashSize(otherHashMap);
+ if (selfHashSize != otherHashSize) {
+ return false;
+ }
+ Object selfEntriesIter = selfInterop.getHashEntriesIterator(selfHashMap);
+ while (entriesInterop.hasIteratorNextElement(selfEntriesIter)) {
+ Object selfKeyValue = entriesInterop.getIteratorNextElement(selfEntriesIter);
+ Object key = entriesInterop.readArrayElement(selfKeyValue, 0);
+ Object selfValue = entriesInterop.readArrayElement(selfKeyValue, 1);
+ if (otherInterop.isHashEntryExisting(otherHashMap, key)
+ && otherInterop.isHashEntryReadable(otherHashMap, key)) {
+ Object otherValue = otherInterop.readHashValue(otherHashMap, key);
+ if (!equalsNode.execute(selfValue, otherValue)) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+ return true;
+ } catch (UnsupportedMessageException | StopIterationException | UnknownKeyException |
+ InvalidArrayIndexException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
/** Equals for Atoms and AtomConstructors */
@Specialization
@@ -534,24 +603,13 @@ public abstract class EqualsAnyNode extends Node {
@TruffleBoundary
boolean equalsGeneric(Object left, Object right,
@CachedLibrary(limit = "5") InteropLibrary interop) {
- EnsoContext ctx = EnsoContext.get(interop);
- if (isHostObject(ctx, left) && isHostObject(ctx, right)) {
- try {
- return interop.asBoolean(
- interop.invokeMember(left, "equals", right)
- );
- } catch (UnsupportedMessageException | ArityException | UnknownIdentifierException |
- UnsupportedTypeException e) {
- throw new IllegalStateException(e);
- }
- } else {
return left == right
- || left.equals(right)
- || interop.isIdentical(left, right, interop);
- }
+ || interop.isIdentical(left, right, interop)
+ || left.equals(right);
}
- private static boolean isHostObject(EnsoContext context, Object object) {
- return context.getEnvironment().isHostObject(object);
+ @TruffleBoundary
+ boolean isHostObject(Object object) {
+ return EnsoContext.get(this).getEnvironment().isHostObject(object);
}
}
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/HashCodeAnyNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/HashCodeAnyNode.java
new file mode 100644
index 0000000000..25f976f9aa
--- /dev/null
+++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/HashCodeAnyNode.java
@@ -0,0 +1,393 @@
+package org.enso.interpreter.node.expression.builtin.meta;
+
+import com.ibm.icu.text.Normalizer2;
+import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.dsl.Cached;
+import com.oracle.truffle.api.dsl.GenerateUncached;
+import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.api.interop.ArityException;
+import com.oracle.truffle.api.interop.InteropLibrary;
+import com.oracle.truffle.api.interop.InvalidArrayIndexException;
+import com.oracle.truffle.api.interop.StopIterationException;
+import com.oracle.truffle.api.interop.UnknownIdentifierException;
+import com.oracle.truffle.api.interop.UnsupportedMessageException;
+import com.oracle.truffle.api.interop.UnsupportedTypeException;
+import com.oracle.truffle.api.library.CachedLibrary;
+import com.oracle.truffle.api.nodes.Node;
+import com.oracle.truffle.api.profiles.ConditionProfile;
+import com.oracle.truffle.api.profiles.LoopConditionProfile;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.time.ZonedDateTime;
+import java.util.Arrays;
+import org.enso.interpreter.dsl.AcceptsError;
+import org.enso.interpreter.node.expression.builtin.number.utils.BigIntegerOps;
+import org.enso.interpreter.runtime.EnsoContext;
+import org.enso.interpreter.runtime.callable.atom.Atom;
+import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
+import org.enso.interpreter.runtime.data.text.Text;
+import org.enso.interpreter.runtime.error.WarningsLibrary;
+import org.enso.interpreter.runtime.number.EnsoBigInteger;
+
+/**
+ * Implements {@code hash_code} functionality.
+ *
+ *
Make sure that the hashing contract is retained after any modification.
+ *
+ *
Hashing contract:
+ *
+ *
+ *
Whenever two objects are equal ({@code EqualsAnyNode} returns {@code true}), their hashcode
+ * should equal. More formally: {@code For all objects o1, o2: if o1 == o2 then hash(o1) ==
+ * hash(o2)}
+ *
Whenever two hash codes are different, their associated objects are different: {@code For all objects
+ * o1, o2: if hash(o1) != hash(o2) then o1 != o2.
+ *
+ */
+@GenerateUncached
+public abstract class HashCodeAnyNode extends Node {
+
+ public static HashCodeAnyNode build() {
+ return HashCodeAnyNodeGen.create();
+ }
+
+ public abstract long execute(@AcceptsError Object self);
+
+ /** Specializations for primitive values * */
+ @Specialization
+ long hashCodeForShort(short s) {
+ return s;
+ }
+
+ @Specialization
+ long hashCodeForByte(byte b) {
+ return b;
+ }
+
+ @Specialization
+ long hashCodeForLong(long l) {
+ return Long.hashCode(l);
+ }
+
+ @Specialization
+ long hashCodeForInt(int i) {
+ return i;
+ }
+
+ @Specialization
+ long hashCodeForFloat(float f) {
+ return Float.hashCode(f);
+ }
+
+ @Specialization
+ @TruffleBoundary
+ long hashCodeForDouble(double d) {
+ if (d % 1.0 != 0.0) {
+ return Double.hashCode(d);
+ } else {
+ if (BigIntegerOps.fitsInLong(d)) {
+ return hashCodeForLong(Double.valueOf(d).longValue());
+ } else {
+ try {
+ return BigDecimal.valueOf(d).toBigIntegerExact().hashCode();
+ } catch (ArithmeticException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+ }
+
+ @Specialization
+ @TruffleBoundary
+ long hashCodeForBigInteger(EnsoBigInteger bigInteger) {
+ return bigInteger.getValue().hashCode();
+ }
+
+ @Specialization
+ long hashCodeForAtomConstructor(AtomConstructor atomConstructor) {
+ return System.identityHashCode(atomConstructor);
+ }
+
+ /** How many {@link HashCodeAnyNode} nodes should be created for fields in atoms. */
+ static final int hashCodeNodeCountForFields = 10;
+
+ static HashCodeAnyNode[] createHashCodeNodes(int size) {
+ HashCodeAnyNode[] nodes = new HashCodeAnyNode[size];
+ Arrays.fill(nodes, HashCodeAnyNode.build());
+ return nodes;
+ }
+
+ @Specialization
+ long hashCodeForAtom(
+ Atom atom,
+ @Cached(value = "createHashCodeNodes(hashCodeNodeCountForFields)", allowUncached = true)
+ HashCodeAnyNode[] fieldHashCodeNodes,
+ @Cached ConditionProfile isHashCodeCached,
+ @Cached ConditionProfile enoughHashCodeNodesForFields,
+ @Cached LoopConditionProfile loopProfile) {
+ if (isHashCodeCached.profile(atom.getHashCode() != null)) {
+ return atom.getHashCode();
+ }
+ // TODO[PM]: If atom overrides hash_code, call that method (Will be done in a follow-up PR for
+ // https://www.pivotaltracker.com/story/show/183945328)
+ int fieldsCount = atom.getFields().length;
+ Object[] fields = atom.getFields();
+ // hashes stores hash codes for all fields, and for constructor.
+ int[] hashes = new int[fieldsCount + 1];
+ if (enoughHashCodeNodesForFields.profile(fieldsCount <= hashCodeNodeCountForFields)) {
+ loopProfile.profileCounted(fieldsCount);
+ for (int i = 0; loopProfile.inject(i < fieldsCount); i++) {
+ hashes[i] = (int) fieldHashCodeNodes[i].execute(fields[i]);
+ }
+ } else {
+ hashCodeForAtomFieldsUncached(fields, hashes);
+ }
+
+ int ctorHashCode = System.identityHashCode(atom.getConstructor());
+ hashes[hashes.length - 1] = ctorHashCode;
+
+ int atomHashCode = Arrays.hashCode(hashes);
+ atom.setHashCode(atomHashCode);
+ return atomHashCode;
+ }
+
+ @TruffleBoundary
+ private void hashCodeForAtomFieldsUncached(Object[] fields, int[] fieldHashes) {
+ for (int i = 0; i < fields.length; i++) {
+ fieldHashes[i] = (int) HashCodeAnyNodeGen.getUncached().execute(fields[i]);
+ }
+ }
+
+ @Specialization(
+ guards = {"warnLib.hasWarnings(selfWithWarning)"},
+ limit = "3")
+ long hashCodeForWarning(
+ Object selfWithWarning,
+ @CachedLibrary("selfWithWarning") WarningsLibrary warnLib,
+ @Cached HashCodeAnyNode hashCodeNode) {
+ try {
+ return hashCodeNode.execute(warnLib.removeWarnings(selfWithWarning));
+ } catch (UnsupportedMessageException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ /** Specializations for interop values * */
+ @Specialization(
+ guards = {"interop.isBoolean(selfBool)"},
+ limit = "3")
+ long hashCodeForBooleanInterop(
+ Object selfBool, @CachedLibrary("selfBool") InteropLibrary interop) {
+ try {
+ return Boolean.hashCode(interop.asBoolean(selfBool));
+ } catch (UnsupportedMessageException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @TruffleBoundary
+ @Specialization(
+ guards = {
+ "!interop.isDate(selfTimeZone)",
+ "!interop.isTime(selfTimeZone)",
+ "interop.isTimeZone(selfTimeZone)",
+ },
+ limit = "3")
+ long hashCodeForTimeZoneInterop(
+ Object selfTimeZone, @CachedLibrary("selfTimeZone") InteropLibrary interop) {
+ try {
+ return interop.asTimeZone(selfTimeZone).hashCode();
+ } catch (UnsupportedMessageException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @TruffleBoundary
+ @Specialization(
+ guards = {
+ "interop.isDate(selfZonedDateTime)",
+ "interop.isTime(selfZonedDateTime)",
+ "interop.isTimeZone(selfZonedDateTime)",
+ },
+ limit = "3")
+ long hashCodeForZonedDateTimeInterop(
+ Object selfZonedDateTime, @CachedLibrary("selfZonedDateTime") InteropLibrary interop) {
+ try {
+ return ZonedDateTime.of(
+ interop.asDate(selfZonedDateTime),
+ interop.asTime(selfZonedDateTime),
+ interop.asTimeZone(selfZonedDateTime))
+ .hashCode();
+ } catch (UnsupportedMessageException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Specialization(
+ guards = {
+ "interop.isDate(selfDateTime)",
+ "interop.isTime(selfDateTime)",
+ "!interop.isTimeZone(selfDateTime)",
+ },
+ limit = "3")
+ long hashCodeForDateTimeInterop(
+ Object selfDateTime, @CachedLibrary("selfDateTime") InteropLibrary interop) {
+ try {
+ return LocalDateTime.of(interop.asDate(selfDateTime), interop.asTime(selfDateTime))
+ .hashCode();
+ } catch (UnsupportedMessageException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Specialization(
+ guards = {
+ "!interop.isDate(selfTime)",
+ "interop.isTime(selfTime)",
+ "!interop.isTimeZone(selfTime)",
+ },
+ limit = "3")
+ long hashCodeForTimeInterop(Object selfTime, @CachedLibrary("selfTime") InteropLibrary interop) {
+ try {
+ return interop.asTime(selfTime).hashCode();
+ } catch (UnsupportedMessageException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Specialization(
+ guards = {
+ "interop.isDate(selfDate)",
+ "!interop.isTime(selfDate)",
+ "!interop.isTimeZone(selfDate)",
+ },
+ limit = "3")
+ long hashCodeForDateInterop(Object selfDate, @CachedLibrary("selfDate") InteropLibrary interop) {
+ try {
+ return interop.asDate(selfDate).hashCode();
+ } catch (UnsupportedMessageException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Specialization(
+ guards = {
+ "interop.isDuration(selfDuration)",
+ },
+ limit = "3")
+ long hashCodeForDurationInterop(
+ Object selfDuration, @CachedLibrary("selfDuration") InteropLibrary interop) {
+ try {
+ return interop.asDuration(selfDuration).hashCode();
+ } catch (UnsupportedMessageException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Specialization
+ long hashCodeForText(Text text, @CachedLibrary(limit = "3") InteropLibrary interop) {
+ if (text.is_normalized()) {
+ return text.toString().hashCode();
+ } else {
+ return hashCodeForString(text, interop);
+ }
+ }
+
+ @TruffleBoundary
+ @Specialization(
+ guards = {"interop.isString(selfStr)"},
+ limit = "3")
+ long hashCodeForString(Object selfStr, @CachedLibrary("selfStr") InteropLibrary interop) {
+ String str;
+ try {
+ str = interop.asString(selfStr);
+ } catch (UnsupportedMessageException e) {
+ throw new IllegalStateException(e);
+ }
+ Normalizer2 normalizer = Normalizer2.getNFDInstance();
+ if (normalizer.isNormalized(str)) {
+ return str.hashCode();
+ } else {
+ return normalizer.normalize(str).hashCode();
+ }
+ }
+
+ @Specialization(
+ guards = {"interop.hasArrayElements(selfArray)"},
+ limit = "3")
+ long hashCodeForArray(
+ Object selfArray,
+ @CachedLibrary("selfArray") InteropLibrary interop,
+ @Cached HashCodeAnyNode hashCodeNode,
+ @Cached("createCountingProfile()") LoopConditionProfile loopProfile) {
+ try {
+ long arraySize = interop.getArraySize(selfArray);
+ loopProfile.profileCounted(arraySize);
+ int[] elemHashCodes = new int[(int) arraySize];
+ for (int i = 0; loopProfile.inject(i < arraySize); i++) {
+ if (interop.isArrayElementReadable(selfArray, i)) {
+ elemHashCodes[i] = (int) hashCodeNode.execute(interop.readArrayElement(selfArray, i));
+ }
+ }
+ return Arrays.hashCode(elemHashCodes);
+ } catch (UnsupportedMessageException | InvalidArrayIndexException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ /**
+ * Two maps are considered equal, if they have the same entries. Note that we do not care about
+ * ordering.
+ */
+ @Specialization(guards = "interop.hasHashEntries(selfMap)")
+ long hashCodeForMap(
+ Object selfMap,
+ @CachedLibrary(limit = "5") InteropLibrary interop,
+ @Cached HashCodeAnyNode hashCodeNode) {
+ int mapSize;
+ long keysHashCode = 0;
+ long valuesHashCode = 0;
+ try {
+ mapSize = (int) interop.getHashSize(selfMap);
+ Object entriesIterator = interop.getHashEntriesIterator(selfMap);
+ while (interop.hasIteratorNextElement(entriesIterator)) {
+ Object entry = interop.getIteratorNextElement(entriesIterator);
+ Object key = interop.readArrayElement(entry, 0);
+ Object value = interop.readArrayElement(entry, 1);
+ // We don't care about the order of keys and values, so we just sum all their hash codes.
+ keysHashCode += hashCodeNode.execute(key);
+ valuesHashCode += hashCodeNode.execute(value);
+ }
+ } catch (UnsupportedMessageException | StopIterationException | InvalidArrayIndexException e) {
+ throw new IllegalStateException(e);
+ }
+ return Arrays.hashCode(new long[] {keysHashCode, valuesHashCode, mapSize});
+ }
+
+ @Specialization(
+ guards = {"interop.isNull(selfNull)"},
+ limit = "3")
+ long hashCodeForNull(Object selfNull, @CachedLibrary("selfNull") InteropLibrary interop) {
+ return 0;
+ }
+
+ @Specialization(guards = "isHostObject(hostObject)")
+ long hashCodeForHostObject(
+ Object hostObject, @CachedLibrary(limit = "3") InteropLibrary interop) {
+ try {
+ Object hashCodeRes = interop.invokeMember(hostObject, "hashCode");
+ assert interop.fitsInInt(hashCodeRes);
+ return interop.asInt(hashCodeRes);
+ } catch (UnsupportedMessageException
+ | ArityException
+ | UnknownIdentifierException
+ | UnsupportedTypeException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @TruffleBoundary
+ boolean isHostObject(Object object) {
+ return EnsoContext.get(this).getEnvironment().isHostObject(object);
+ }
+}
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/smallInteger/AbsNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/smallInteger/AbsNode.java
index 23f3c26bcc..ca74efe59e 100644
--- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/smallInteger/AbsNode.java
+++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/smallInteger/AbsNode.java
@@ -6,7 +6,7 @@ import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.node.expression.builtin.number.utils.BigIntegerOps;
import org.enso.interpreter.node.expression.builtin.number.utils.ToEnsoNumberNode;
-@BuiltinMethod(type = "Small_Integer", name = "abs", description = "Negation for numbers.")
+@BuiltinMethod(type = "Small_Integer", name = "abs", description = "Absolute value of a number")
public abstract class AbsNode extends Node {
private @Child ToEnsoNumberNode toEnsoNumberNode = ToEnsoNumberNode.build();
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java
index c250d3e471..9886fbd722 100644
--- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java
+++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java
@@ -92,6 +92,7 @@ public class Builtins {
private final Builtin text;
private final Builtin array;
private final Builtin vector;
+ private final Builtin map;
private final Builtin dataflowError;
private final Builtin ref;
private final Builtin managedResource;
@@ -137,6 +138,7 @@ public class Builtins {
text = builtins.get(Text.class);
array = builtins.get(Array.class);
vector = builtins.get(Vector.class);
+ map = builtins.get(org.enso.interpreter.node.expression.builtin.Map.class);
dataflowError = builtins.get(org.enso.interpreter.node.expression.builtin.Error.class);
ref = builtins.get(Ref.class);
managedResource = builtins.get(ManagedResource.class);
@@ -552,6 +554,10 @@ public class Builtins {
return vector.getType();
}
+ public Type map() {
+ return map.getType();
+ }
+
/** @return the Ref constructor. */
public Type ref() {
return ref.getType();
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/Atom.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/Atom.java
index dc57e8253c..f6a3f2eeca 100644
--- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/Atom.java
+++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/Atom.java
@@ -1,7 +1,15 @@
package org.enso.interpreter.runtime.callable.atom;
+import com.oracle.truffle.api.Assumption;
import com.oracle.truffle.api.CompilerDirectives;
+import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.Truffle;
+import com.oracle.truffle.api.dsl.Cached.Shared;
+import com.oracle.truffle.api.dsl.Fallback;
+import com.oracle.truffle.api.profiles.ConditionProfile;
+import com.oracle.truffle.api.profiles.ValueProfile;
+import com.oracle.truffle.api.utilities.TriState;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.exception.AbstractTruffleException;
@@ -12,11 +20,16 @@ import com.oracle.truffle.api.library.ExportMessage;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.nodes.UnexpectedResultException;
import com.oracle.truffle.api.profiles.BranchProfile;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.data.Array;
import org.enso.interpreter.runtime.data.Type;
import org.enso.interpreter.runtime.data.text.Text;
+import org.enso.interpreter.runtime.error.PanicException;
import org.enso.interpreter.runtime.library.dispatch.TypesLibrary;
import org.enso.interpreter.runtime.type.TypesGen;
@@ -30,6 +43,7 @@ import org.enso.interpreter.runtime.error.WarningsLibrary;
public final class Atom implements TruffleObject {
final AtomConstructor constructor;
private final Object[] fields;
+ private Integer hashCode;
/**
* Creates a new Atom for a given constructor.
@@ -60,6 +74,15 @@ public final class Atom implements TruffleObject {
return fields;
}
+ public void setHashCode(int hashCode) {
+ assert this.hashCode == null : "setHashCode must be called at most once";
+ this.hashCode = hashCode;
+ }
+
+ public Integer getHashCode() {
+ return hashCode;
+ }
+
private void toString(StringBuilder builder, boolean shouldParen, int depth) {
if (depth <= 0) {
builder.append("...");
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java
index b9944db7d7..f8954952b1 100644
--- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java
+++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java
@@ -10,6 +10,7 @@ import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import com.oracle.truffle.api.nodes.RootNode;
+import com.oracle.truffle.api.utilities.TriState;
import org.enso.interpreter.node.ClosureRootNode;
import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.node.callable.argument.ReadArgumentNode;
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/hash/EnsoHashMap.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/hash/EnsoHashMap.java
new file mode 100644
index 0000000000..1af429894d
--- /dev/null
+++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/hash/EnsoHashMap.java
@@ -0,0 +1,194 @@
+package org.enso.interpreter.runtime.data.hash;
+
+import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.dsl.Cached;
+import com.oracle.truffle.api.interop.InteropLibrary;
+import com.oracle.truffle.api.interop.TruffleObject;
+import com.oracle.truffle.api.interop.UnknownKeyException;
+import com.oracle.truffle.api.interop.UnsupportedMessageException;
+import com.oracle.truffle.api.library.CachedLibrary;
+import com.oracle.truffle.api.library.ExportLibrary;
+import com.oracle.truffle.api.library.ExportMessage;
+import com.oracle.truffle.api.profiles.ConditionProfile;
+import org.enso.interpreter.dsl.Builtin;
+import org.enso.interpreter.node.expression.builtin.meta.EqualsAnyNode;
+import org.enso.interpreter.node.expression.builtin.meta.HashCodeAnyNode;
+import org.enso.interpreter.runtime.EnsoContext;
+import org.enso.interpreter.runtime.data.Type;
+import org.enso.interpreter.runtime.data.Vector;
+import org.enso.interpreter.runtime.data.hash.EnsoHashMapBuilder.StorageEntry;
+import org.enso.interpreter.runtime.library.dispatch.TypesLibrary;
+
+/**
+ * Implementation of a hash map structure, capable of holding any types of keys and values. The
+ * actual hash map storage is implemented in {@link EnsoHashMapBuilder}, and every {@link
+ * EnsoHashMap} has just a reference to the builder and its size, which allows us to implement
+ * {@code insert} operation in constant time. In other words, every map is just a snapshot of its
+ * builder.
+ *
+ *
Users should not use Enso objects as keys to Java maps, because equals won't work the same way
+ * as it works in Enso.
+ */
+@ExportLibrary(TypesLibrary.class)
+@ExportLibrary(InteropLibrary.class)
+@Builtin(stdlibName = "Standard.Base.Data.Map.Map", name = "Map")
+public final class EnsoHashMap implements TruffleObject {
+ private final EnsoHashMapBuilder mapBuilder;
+ /**
+ * Size of this Map. Basically an index into {@link EnsoHashMapBuilder}'s storage. See {@link
+ * #isEntryInThisMap(StorageEntry)}.
+ */
+ private final int snapshotSize;
+ /**
+ * True iff {@code insert} method was already called. If insert was already called, and we are
+ * calling {@code insert} again, the {@link #mapBuilder} should be duplicated for the newly
+ * created Map.
+ */
+ private boolean insertCalled;
+
+ private Object cachedVectorRepresentation;
+
+ private EnsoHashMap(EnsoHashMapBuilder mapBuilder, int snapshotSize) {
+ this.mapBuilder = mapBuilder;
+ this.snapshotSize = snapshotSize;
+ assert snapshotSize <= mapBuilder.getSize();
+ }
+
+ static EnsoHashMap createWithBuilder(EnsoHashMapBuilder mapBuilder, int snapshotSize) {
+ return new EnsoHashMap(mapBuilder, snapshotSize);
+ }
+
+ static EnsoHashMap createEmpty(HashCodeAnyNode hashCodeAnyNode, EqualsAnyNode equalsNode) {
+ return new EnsoHashMap(EnsoHashMapBuilder.create(hashCodeAnyNode, equalsNode), 0);
+ }
+
+ EnsoHashMapBuilder getMapBuilder() {
+ return mapBuilder;
+ }
+
+ Object getCachedVectorRepresentation() {
+ return getCachedVectorRepresentation(ConditionProfile.getUncached());
+ }
+
+ Object getCachedVectorRepresentation(ConditionProfile isNotCachedProfile) {
+ if (isNotCachedProfile.profile(cachedVectorRepresentation == null)) {
+ Object[] keys = new Object[snapshotSize];
+ Object[] values = new Object[snapshotSize];
+ int arrIdx = 0;
+ for (StorageEntry entry : mapBuilder.getStorage().getValues()) {
+ if (entry.index() < snapshotSize) {
+ keys[arrIdx] = entry.key();
+ values[arrIdx] = entry.value();
+ arrIdx++;
+ }
+ }
+ cachedVectorRepresentation =
+ Vector.fromArray(HashEntriesVector.createFromKeysAndValues(keys, values));
+ }
+ return cachedVectorRepresentation;
+ }
+
+ public boolean isInsertCalled() {
+ return insertCalled;
+ }
+
+ public void setInsertCalled() {
+ assert !insertCalled : "setInsertCalled should be called at most once";
+ insertCalled = true;
+ }
+
+ @Builtin.Method
+ @Builtin.Specialize
+ public static EnsoHashMap empty(
+ @Cached HashCodeAnyNode hashCodeNode, @Cached EqualsAnyNode equalsNode) {
+ return createEmpty(hashCodeNode, equalsNode);
+ }
+
+ @ExportMessage
+ boolean hasHashEntries() {
+ return true;
+ }
+
+ @ExportMessage
+ int getHashSize() {
+ return snapshotSize;
+ }
+
+ @ExportMessage
+ boolean isHashEntryExisting(Object key) {
+ return isEntryInThisMap(mapBuilder.get(key));
+ }
+
+ @ExportMessage
+ boolean isHashEntryReadable(Object key) {
+ return isHashEntryExisting(key);
+ }
+
+ @ExportMessage
+ Object readHashValue(Object key) throws UnknownKeyException {
+ StorageEntry entry = mapBuilder.get(key);
+ if (isEntryInThisMap(entry)) {
+ return entry.value();
+ } else {
+ throw UnknownKeyException.create(key);
+ }
+ }
+
+ @ExportMessage
+ Object getHashEntriesIterator(@CachedLibrary(limit = "3") InteropLibrary interop) {
+ try {
+ return interop.getIterator(getCachedVectorRepresentation());
+ } catch (UnsupportedMessageException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @ExportMessage(library = TypesLibrary.class)
+ boolean hasType() {
+ return true;
+ }
+
+ @ExportMessage(library = TypesLibrary.class)
+ Type getType(@CachedLibrary("this") TypesLibrary thisLib) {
+ return EnsoContext.get(thisLib).getBuiltins().map();
+ }
+
+ @ExportMessage
+ boolean hasMetaObject() {
+ return true;
+ }
+
+ @ExportMessage
+ Type getMetaObject(@CachedLibrary("this") InteropLibrary thisLib) {
+ return EnsoContext.get(thisLib).getBuiltins().map();
+ }
+
+ @ExportMessage
+ @TruffleBoundary
+ Object toDisplayString(boolean allowSideEffects) {
+ var sb = new StringBuilder();
+ sb.append("{");
+ boolean empty = true;
+ for (StorageEntry entry : mapBuilder.getStorage().getValues()) {
+ if (isEntryInThisMap(entry)) {
+ empty = false;
+ sb.append(entry.key()).append("=").append(entry.value()).append(", ");
+ }
+ }
+ if (!empty) {
+ // Delete last comma
+ sb.delete(sb.length() - 2, sb.length());
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ @Override
+ public String toString() {
+ return (String) toDisplayString(true);
+ }
+
+ private boolean isEntryInThisMap(StorageEntry entry) {
+ return entry != null && entry.index() < snapshotSize;
+ }
+}
diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/hash/EnsoHashMapBuilder.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/hash/EnsoHashMapBuilder.java
new file mode 100644
index 0000000000..fc986cd1d1
--- /dev/null
+++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/hash/EnsoHashMapBuilder.java
@@ -0,0 +1,188 @@
+package org.enso.interpreter.runtime.data.hash;
+
+import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import java.util.ArrayList;
+import java.util.List;
+import org.enso.interpreter.node.expression.builtin.meta.EqualsAnyNode;
+import org.enso.interpreter.node.expression.builtin.meta.HashCodeAnyNode;
+import org.graalvm.collections.EconomicMap;
+import org.graalvm.collections.Equivalence;
+
+/**
+ * A storage for a {@link EnsoHashMap}. For one builder, there may be many snapshots ({@link
+ * EnsoHashMap}). There should be at most one snapshot for a given size. All the snapshots should
+ * have size smaller than this builder size.
+ */
+public final class EnsoHashMapBuilder {
+ private final EconomicMap