Skip to content
Snippets Groups Projects
Unverified Commit 94ac7d14 authored by Yorick Peterse's avatar Yorick Peterse
Browse files

Remove hash map literals

In order to simplify the syntax, hash map literals have been removed.
Instead, one now creates a hash map using `Map.new`. To make it easier
to create a map with a bunch of pairs in one expression, we introduce
the `Map.set` method. This method behaves similar to `Map.[]=`, but
returns the Map itself instead of the value written:

    Map
      .new
      .set('foo', 'bar')
      .set('baz', 'quix')

So why were map literals removed? Well, first of all their syntax was
not intuitive: `%[key: value]`. This syntax was taken from Elixir, but
is not used in other languages that we are aware of. Many languages use
curly braces (e.g. `{key => value}`), but these are already used for
closures. Some languages reuse square brackets (e.g. `[key: value]`),
but this makes the meaning of `[]` unclear. We considered using a syntax
similar to Scala:

    Map.new(key -> value)

Here `->` would be a method that returns some sort of tuple, and
`Map.new` would take this list of tuples and use them to fill the map.
The method `->` would have to be available for every object, since
it's perfectly valid to use outside of constructing maps. This means
`->` would have to be defined on `Object`, or in a trait that is
implemented for it. Manually implementing the method/trait would be too
cumbersome. The hypothetical code for this might look as follows:

    impl Object {
      def ->!(V)(other: V) -> Tuple!(Self, V) {
        Tuple.new(self, other)
      }
    }

Unfortunately, the Ruby compiler does not support the use of self types
in generic types well enough to make this work. This is a long standing
issue [1], but it would require an extensive rewrite of the type system
to support. Since we want to rewrite the Ruby compiler in Inko, adding
support for this in the Ruby compiler would be a waste of time.

There are a variety of other approaches, such as passing a closure to
`Map.new` that can be used to fill up the map. All of these suffer from
similar problems: the Ruby compiler's type system is a bit buggy.

To work around all of this, we added the `Map.set` method. While the
resulting code is a bit more verbose, it does not require any compiler
changes. The API should also feel familiar to those used to immutable
programming languages, which typically use a similar approach for
constructing hash maps.

The removal of map literals also allows us to remove various compiler
optimisations of these literals, simplifying the compiler and making the
language more predictable.

[1]: https://gitlab.com/inko-lang/inko/issues/107
parent 7fe9e24f
No related branches found
No related tags found
No related merge requests found
Showing
with 185 additions and 265 deletions
Loading
Loading
@@ -99,10 +99,6 @@ module Inkoc
false
end
 
def hash_map_literal?
false
end
def global?
false
end
Loading
Loading
Loading
Loading
@@ -49,14 +49,6 @@ module Inkoc
name == Config::NEW_MESSAGE
end
 
def hash_map_literal?
receiver&.global? &&
receiver&.name == Config::HASH_MAP_LITERAL_RECEIVER &&
name == Config::FROM_ARRAY_MESSAGE &&
arguments[0]&.array_literal? &&
arguments[1]&.array_literal?
end
def raw_instruction_visitor_method
:"on_raw_#{name}"
end
Loading
Loading
Loading
Loading
@@ -32,7 +32,6 @@ module Inkoc
TRAIT_MODULE = 'trait'
 
INTERNAL_TRAIT_IMPORT = '_inkoc_std_trait'
HASH_MAP_LITERAL_RECEIVER = '_inkoc_hash_map_literal'
 
MARKER_MODULE = 'std::marker'
 
Loading
Loading
@@ -40,7 +39,6 @@ module Inkoc
OBJECT_CONST = 'Object'
TRAIT_CONST = 'Trait'
ARRAY_CONST = 'Array'
HASH_MAP_CONST = 'Map'
BLOCK_CONST = 'Block'
INTEGER_CONST = 'Integer'
FLOAT_CONST = 'Float'
Loading
Loading
@@ -66,7 +64,6 @@ module Inkoc
UNKNOWN_MESSAGE_MESSAGE = 'unknown_message'
UNKNOWN_MESSAGE_TRAIT = 'UnknownMessage'
UNKNOWN_MESSAGE_MODULE = 'std::unknown_message'
FROM_ARRAY_MESSAGE = 'from_array'
SET_INDEX_MESSAGE = '[]='
MODULE_GLOBAL = 'ThisModule'
CALL_MESSAGE = 'call'
Loading
Loading
Loading
Loading
@@ -106,7 +106,7 @@ module Inkoc
when '"' then return double_string
when ':' then return colons
when '/' then return div
when '%' then return modulo_or_hash_open
when '%' then return modulo
when '^' then return bitwise_xor
when '&' then return bitwise_and_or_boolean_and
when '|' then return bitwise_or_or_boolean_or
Loading
Loading
@@ -436,20 +436,8 @@ module Inkoc
new_token(token_type, start, @position)
end
 
def modulo_or_hash_open
start = @position
token_type = :mod
case @input[@position += 1]
when '['
token_type = :hash_open
@position += 1
when '='
token_type = :mod_assign
@position += 1
end
new_token(token_type, start, @position)
def modulo
operator(1, :mod, :mod_assign)
end
 
def bitwise_xor
Loading
Loading
Loading
Loading
@@ -59,7 +59,6 @@ module Inkoc
define
do
float
hash_open
identifier
impl
integer
Loading
Loading
@@ -583,7 +582,6 @@ module Inkoc
when :constant then constant(start)
when :curly_open then block_without_arguments(start)
when :bracket_open then array(start)
when :hash_open then hash(start)
when :define then def_method(start)
when :static then def_static_method(start)
when :do, :lambda then block(start, start.type)
Loading
Loading
@@ -764,42 +762,6 @@ module Inkoc
new_array(values, start)
end
 
# Parses a hash map literal
#
# Example:
#
# %['key': 'value']
def hash(start)
keys = []
vals = []
while (key_tok = @lexer.advance) && key_tok.valid_but_not?(:bracket_close)
key = expression(key_tok)
advance_and_expect!(:colon)
value = expression(advance!)
keys << key
vals << value
break if comma_or_break_on(:bracket_close)
end
location = start.location
receiver = AST::Global.new(Config::HASH_MAP_LITERAL_RECEIVER, location)
keys_array = new_array(keys, start)
vals_array = new_array(vals, start)
AST::Send.new(
Config::FROM_ARRAY_MESSAGE,
receiver,
[],
[keys_array, vals_array],
location
)
end
# Parses a method definition.
#
# Examples:
Loading
Loading
Loading
Loading
@@ -461,8 +461,6 @@ module Inkoc
def receiver_type_for_send_with_receiver(node, scope)
if node.name == Config::NEW_MESSAGE
define_type_instance(node.receiver, scope)
elsif node.hash_map_literal?
@module.lookup_global(Config::HASH_MAP_CONST)
else
define_type(node.receiver, scope)
end
Loading
Loading
Loading
Loading
@@ -522,12 +522,6 @@ module Inkoc
end
 
def on_send(node, body)
# Map literals need to be optimised before we process their
# arguments.
if node.hash_map_literal?
return on_hash_map_literal(node, body)
end
receiver = receiver_for_send(node, body)
args, kwargs = split_send_arguments(node.arguments, body)
 
Loading
Loading
@@ -543,64 +537,6 @@ module Inkoc
)
end
 
# Optimises a Map literal.
#
# This method will turn this:
#
# let x = %['a': 10, 'b': 20]
#
# Into (effectively) the following:
#
# let hash_map = Map.new
#
# hash_map['a'] = 10
# hash_map['b'] = 20
#
# let x = hash_map
#
# While the example above uses a local variable `hash_map`, the generated
# code only uses registers.
def on_hash_map_literal(node, body)
hash_map_global_reg =
get_global(Config::HASH_MAP_CONST, body, node.location)
hash_map_type = hash_map_global_reg.type
new_method = hash_map_type.lookup_method(Config::NEW_MESSAGE).type
set_method = hash_map_type.lookup_method(Config::SET_INDEX_MESSAGE).type
# Initialise an empty Map.
hash_map_reg = send_object_message(
hash_map_global_reg,
new_method.name,
[],
[],
new_method,
node.type,
body,
node.location
)
keys = node.arguments[0].arguments
vals = node.arguments[1].arguments
# Every key-value pair is compiled into a `hash[key] = value`
# expression.
keys.zip(vals).each do |(knode, vnode)|
send_object_message(
hash_map_reg,
set_method.name,
[process_node(knode, body), process_node(vnode, body)],
[],
set_method,
vnode.type,
body,
knode.location
)
end
hash_map_reg
end
def split_send_arguments(arguments, body)
args = []
kwargs = []
Loading
Loading
Loading
Loading
@@ -445,26 +445,18 @@ describe Inkoc::Lexer do
end
end
 
describe '#modulo_or_hash_open' do
describe '#modulo' do
it 'tokenizes the modulo operator' do
lexer = described_class.new('%')
token = lexer.modulo_or_hash_open
token = lexer.modulo
 
expect(token.type).to eq(:mod)
expect(token.value).to eq('%')
end
 
it 'tokenizes the hash-open token' do
lexer = described_class.new('%[')
token = lexer.modulo_or_hash_open
expect(token.type).to eq(:hash_open)
expect(token.value).to eq('%[')
end
it 'tokenizes the module-assign operator' do
lexer = described_class.new('%=')
token = lexer.modulo_or_hash_open
token = lexer.modulo
 
expect(token.type).to eq(:mod_assign)
expect(token.value).to eq('%=')
Loading
Loading
Loading
Loading
@@ -38,6 +38,7 @@ _INKOC.set_object_name(Block, 'Block')
# building blocks of Inko, such as "Object.new" and the bits necessary to allow
# creating of modules.
impl Object {
## Creates a new instance of `self` and sends `init` to the instance.
static def new -> Self {
let obj = _INKOC.set_object(False, self)
 
Loading
Loading
Loading
Loading
@@ -61,18 +61,20 @@ let CURLY_CLOSE = 125
 
## The escape sequence literals supported by a single quoted string, and their
## replacement bytes.
let SINGLE_QUOTED_STRING_ESCAPE_SEQUENCES = %[SINGLE_QUOTE: SINGLE_QUOTE]
let SINGLE_QUOTED_STRING_ESCAPE_SEQUENCES =
Map.new.set(SINGLE_QUOTE, SINGLE_QUOTE)
 
## The escape sequence literals supported by a double quoted string, and their
## replacement bytes.
let DOUBLE_QUOTED_STRING_ESCAPE_SEQUENCES = %[
DOUBLE_QUOTE: DOUBLE_QUOTE,
LOWER_N: NEWLINE,
LOWER_T: TAB,
ZERO: NULL,
LOWER_E: ESCAPE,
LOWER_R: CARRIAGE_RETURN
]
let DOUBLE_QUOTED_STRING_ESCAPE_SEQUENCES =
Map
.new
.set(DOUBLE_QUOTE, DOUBLE_QUOTE)
.set(LOWER_N, NEWLINE)
.set(LOWER_T, TAB)
.set(ZERO, NULL)
.set(LOWER_E, ESCAPE)
.set(LOWER_R, CARRIAGE_RETURN)
 
## A `Lexer` is used for turning Inko source code into a sequence of tokens.
## These tokens in turn can be used by a parser to produce an Abstract Syntax
Loading
Loading
@@ -590,22 +592,9 @@ object Lexer {
}
 
def percent -> Token {
next_byte == BRACKET_OPEN
.if_true {
return hash_open
}
operator(type: 'mod', assign_type: 'mod_assign')
}
 
def hash_open -> Token {
let start = @position
@position += 2
token(type: 'hash_open', start: start, line: @line)
}
def minus -> Token {
let next = next_byte
 
Loading
Loading
Loading
Loading
@@ -68,10 +68,10 @@ def remove(variable: String) -> Nil {
##
## import std::env
##
## env.variables # => %[ 'HOME': '/home/alice', ... ]
## env.variables # => Map { 'HOME': '/home/alice', ... }
def variables -> Map!(String, String) {
let names = _INKOC.env_variables
let map = %[]
let map = Map.new
 
names.each do (name) {
let value = ThisModule[name]
Loading
Loading
Loading
Loading
@@ -419,7 +419,7 @@ object LayoutBuilder {
def init {
@members = []
@types = []
@existing = %[]
@existing = Map.new
@alignment = 0
@padding = True
}
Loading
Loading
@@ -441,7 +441,7 @@ object LayoutBuilder {
 
## Creates a `Layout` that automatically applies padding.
def layout_with_padding -> Layout {
let members = %[]
let members = Map.new
let mut size = 0
let mut offset = 0
let mut remaining_in_hole = @alignment
Loading
Loading
@@ -479,7 +479,7 @@ object LayoutBuilder {
 
## Creates a `Layout` that does not use any padding.
def layout_without_padding -> Layout {
let members = %[]
let members = Map.new
let mut offset = 0
 
@members.each_with_index do (name, index) {
Loading
Loading
Loading
Loading
@@ -186,9 +186,12 @@ impl Inspect for Map!(K, V) {
##
## Inspecting a `Map`:
##
## let map = %['name': 'Alice', 'address': 'Foo Street']
## let map = Map.new
##
## map.inspect # => '%["name": "Alice", "address": "Foo Street"]'
## map['name'] = 'Alice'
## map['address'] = 'Foo Street'
##
## map.inspect # => 'Map { "name": "Alice", "address": "Foo Street" }'
def inspect -> String where K: Inspect, V: Inspect {
::format.inspect(self)
}
Loading
Loading
@@ -198,7 +201,13 @@ impl Inspect for Map!(K, V) {
let last = length - 1
let mut index = 0
 
formatter.push('%[')
formatter.push('Map')
empty?.if_true {
return
}
formatter.push(' { ')
 
each do (key, value) {
formatter.descend {
Loading
Loading
@@ -219,6 +228,6 @@ impl Inspect for Map!(K, V) {
index += 1
}
 
formatter.push(']')
formatter.push(' }')
}
}
Loading
Loading
@@ -80,7 +80,7 @@ object RandomState {
}
}
 
## A single key-value pair
## A key-value pair with a pre-computed hash.
object Pair!(K: Hash + Equal, V) {
## The key that was hashed.
@key: K
Loading
Loading
@@ -172,29 +172,11 @@ object Map!(K: Hash + Equal, V) {
## Returns a `Map` using two arrays: one containing the keys and one
## containing the values.
##
## Using this method is semantically equivalent to creating a `Map` using
## `Map.new` and sending `[]=` to the `Map` for every key-value pair.
## In other words, this:
##
## %['name': 'Alice']
##
## Is the same as this:
##
## let mut map = Map.new
##
## map['name'] = 'Alice'
##
## # Compiler optimisation
##
## To remove the need for allocating two arrays for `Map` literals, the
## compiler may decide to optimise this method into separate `[]=` message
## sends as illustrated above.
##
## # Examples
##
## Creating a `Map` from two arrays:
##
## Map.from_array(['name'], ['Alice']) # => %['name': 'Alice']
## Map.from_array(['name'], ['Alice'])
static def from_array!(K: Hash + Equal, V)(
keys: Array!(K),
values: Array!(V)
Loading
Loading
@@ -219,7 +201,6 @@ object Map!(K: Hash + Equal, V) {
map
}
 
## Creates a new, empty `Map`.
def init {
@random_state = RandomState.new
@buckets = []
Loading
Loading
@@ -368,7 +349,9 @@ object Map!(K: Hash + Equal, V) {
##
## Checking if a `Map` defines a key:
##
## let map = %['name': 'Alice']
## let map = Map.new
##
## map['name'] = 'Alice'
##
## map.key?('name') # => True
## map.key?('city') # => False
Loading
Loading
@@ -380,6 +363,27 @@ object Map!(K: Hash + Equal, V) {
}
}
 
## Inserts the key and value in this `Map`, returning the `Map` itself.
##
## This method makes it possible to create a `Map` and store many key-value
## pairs, all in a single message chain.
##
## # Examples
##
## Inserting multiple key-value pairs:
##
## let map = Map.new.set('a', 10).set('b', 20)
def set(key: K, value: V) -> Self {
@length >= @resize_threshold
.if_true {
rehash
}
_insert_pair(Pair.new(key: key, value: value, hash: _hash_key(key)))
self
}
## Resizes and rehashes `self`.
def rehash {
let old_buckets = @buckets
Loading
Loading
@@ -529,7 +533,13 @@ impl Equal for Map!(K, V) {
##
## Comparing two `Map` instances:
##
## %w['name': 'Alice'] == %w['name': 'Alice'] # => True
## let map1 = Map.new
## let map2 = Map.new
##
## map1['name'] = 'Alice'
## map2['name'] = 'Alice'
##
## map1 == map2 # => True
def ==(other: Self) -> Boolean where V: Equal {
length == other.length
.if_false {
Loading
Loading
@@ -581,8 +591,7 @@ impl Index!(K, V) for Map!(K, V) {
}
 
impl SetIndex!(K, V) for Map!(K, V) {
## Inserts the given key and value into this map, returning the inserted
## value.
## Inserts the key and value in this `Map`, returning the inserted value.
##
## # Examples
##
Loading
Loading
@@ -592,13 +601,7 @@ impl SetIndex!(K, V) for Map!(K, V) {
##
## map['name'] = 'Alice' # => 'Alice'
def []=(key: K, value: V) -> V {
@length >= @resize_threshold
.if_true {
rehash
}
_insert_pair(Pair.new(key: key, value: value, hash: _hash_key(key)))
set(key: key, value: value)
value
}
}
Loading
Loading
Loading
Loading
@@ -592,16 +592,6 @@ world\n" "hello"'
assert.equal(token.location.line_range, 1..1)
}
 
g.test('Lexing the hash open operator') {
let token = lex('%[')
assert.equal(token.type, 'hash_open')
assert.equal(token.value, '%[')
assert.equal(token.location.column, 1)
assert.equal(token.location.line_range, 1..1)
}
g.test('Lexing the XOR operator') {
let token = lex('^')
 
Loading
Loading
Loading
Loading
@@ -428,7 +428,7 @@ test.group('std::ffi::Member.offset') do (g) {
 
test.group('std::ffi::Layout.alignment') do (g) {
g.test('Obtaining the alignment of a Layout') {
let layout = Layout.new(members: %[], alignment: 8, size: 4)
let layout = Layout.new(members: Map.new, alignment: 8, size: 4)
 
assert.equal(layout.alignment, 8)
}
Loading
Loading
@@ -436,7 +436,7 @@ test.group('std::ffi::Layout.alignment') do (g) {
 
test.group('std::ffi::Layout.size') do (g) {
g.test('Obtaining the size of a Layout') {
let layout = Layout.new(members: %[], alignment: 8, size: 4)
let layout = Layout.new(members: Map.new, alignment: 8, size: 4)
 
assert.equal(layout.size, 4)
}
Loading
Loading
@@ -445,14 +445,18 @@ test.group('std::ffi::Layout.size') do (g) {
test.group('std::ffi::Layout.[]') do (g) {
g.test('Obtaining a Member using a valid name') {
let member = Member.new(name: 'tm_sec', type: types.i32, offset: 0)
let layout = Layout.new(members: %['tm_sec': member], alignment: 8, size: 4)
let members = Map.new
members['tm_sec'] = member
let layout = Layout.new(members: members, alignment: 8, size: 4)
 
assert.equal(layout['tm_sec'], member)
}
 
g.test('Obtaining a Member using an invalid name') {
assert.panic {
let layout = Layout.new(members: %[], alignment: 8, size: 4)
let layout = Layout.new(members: Map.new, alignment: 8, size: 4)
 
layout['tm_sec']
}
Loading
Loading
@@ -472,7 +476,7 @@ test.group('std::ffi::Layout.from_pointer') do (g) {
 
test.group('std::ffi::Struct.size') do (g) {
g.test('Obtaining the size of a Struct') {
let layout = Layout.new(members: %[], alignment: 8, size: 4)
let layout = Layout.new(members: Map.new, alignment: 8, size: 4)
let struct = Struct.new(pointer: Pointer.null, layout: layout)
 
assert.equal(struct.size, 4)
Loading
Loading
@@ -481,7 +485,7 @@ test.group('std::ffi::Struct.size') do (g) {
 
test.group('std::ffi::Struct.alignment') do (g) {
g.test('Obtaining the alignment of a Struct') {
let layout = Layout.new(members: %[], alignment: 8, size: 4)
let layout = Layout.new(members: Map.new, alignment: 8, size: 4)
let struct = Struct.new(pointer: Pointer.null, layout: layout)
 
assert.equal(struct.alignment, 8)
Loading
Loading
@@ -490,7 +494,7 @@ test.group('std::ffi::Struct.alignment') do (g) {
 
test.group('std::ffi::Struct.pointer') do (g) {
g.test('Obtaining the alignment of a Struct') {
let layout = Layout.new(members: %[], alignment: 8, size: 4)
let layout = Layout.new(members: Map.new, alignment: 8, size: 4)
let struct = Struct.new(pointer: Pointer.null, layout: layout)
 
assert.equal(struct.pointer, Pointer.null)
Loading
Loading
Loading
Loading
@@ -130,10 +130,20 @@ test.group('std::array::Array.inspect') do (g) {
 
test.group('std::map::Map.inspect') do (g) {
g.test('Inspecting an empty Map') {
assert.equal(%[].inspect, '%[]')
assert.equal(Map.new.inspect, 'Map')
}
 
g.test('Inspecting a non-empty Map') {
assert.equal(%['key': 10].inspect, '%["key": 10]')
let map = Map.new
map['foo'] = 10
map['bar'] = 20
let ins = map.inspect
let valid =
ins == 'Map { "foo": 10, "bar": 20 }'
.or { ins == 'Map { "bar": 20, "foo": 10 }' }
assert.true(valid)
}
}
Loading
Loading
@@ -273,7 +273,9 @@ test.group('std::map::Map._insert_pair') do (g) {
 
test.group('std::map::Map.remove') do (g) {
g.test('Removing an existing key from a Map') {
let map = %['a': 'b']
let map = Map.new
map['a'] = 'b'
 
assert.equal(map.remove('a'), 'b')
assert.equal(map.buckets[0], Nil)
Loading
Loading
@@ -300,26 +302,11 @@ test.group('std::map::Map.remove') do (g) {
}
}
 
test.group('Map literals') do (g) {
g.test('Creating an empty Map') {
assert.equal(%[], Map.new)
}
g.test('Creating a Map with keys and values') {
let expected = Map.new
expected['name'] = 'Alice'
expected['city'] = 'Amsterdam'
assert.equal(%['name': 'Alice', 'city': 'Amsterdam'], expected)
}
}
test.group('std::map::Map.from_array') do (g) {
g.test('Creating a Map without any keys and values') {
let map = Map.from_array([], [])
 
assert.equal(map, %[])
assert.equal(map, Map.new)
}
 
g.test('Creating a Map with keys but without values') {
Loading
Loading
@@ -338,8 +325,13 @@ test.group('std::map::Map.from_array') do (g) {
 
test.group('std::map::Map.empty?') do (g) {
g.test('Checking if a Map is empty') {
assert.true(%[].empty?)
assert.false(%['a': 'b'].empty?)
let map = Map.new
assert.true(map.empty?)
map['a'] = 'b'
assert.false(map.empty?)
}
}
 
Loading
Loading
@@ -347,7 +339,7 @@ test.group('std::map::Map.each') do (g) {
g.test('Iterating over the key-value pairs of an empty Map') {
let mut iters = 0
 
%[].each do (_, _) {
Map.new.each do (_, _) {
iters += 1
}
 
Loading
Loading
@@ -357,8 +349,12 @@ test.group('std::map::Map.each') do (g) {
g.test('Iterating over the key-value pairs of a non-empty Map') {
let mut key_total = 0
let mut val_total = 0
let map = Map.new
map[1] = 10
map[2] = 20
 
%[1: 10, 2: 20].each do (key, val) {
map.each do (key, val) {
key_total += key
val_total += val
}
Loading
Loading
@@ -370,7 +366,7 @@ test.group('std::map::Map.each') do (g) {
 
test.group('std::map::Map.iter') do (g) {
g.test('Creating an Iterator for an empty Map') {
let map = %[]
let map = Map.new
let iter = map.iter
 
assert.false(iter.next?)
Loading
Loading
@@ -378,7 +374,10 @@ test.group('std::map::Map.iter') do (g) {
}
 
g.test('Creating an Iterator for a Map with key-value pairs') {
let map = %['name': 'Alice']
let map = Map.new
map['name'] = 'Alice'
let iter = map.iter
 
assert.true(iter.next?)
Loading
Loading
@@ -392,7 +391,7 @@ test.group('std::map::Map.iter') do (g) {
 
test.group('std::map::Map.keys') do (g) {
g.test('Creating a Keys Iterator for an empty Map') {
let map: Map!(String, String) = %[]
let map: Map!(String, String) = Map.new
let iter = map.keys
 
assert.false(iter.next?)
Loading
Loading
@@ -400,7 +399,10 @@ test.group('std::map::Map.keys') do (g) {
}
 
g.test('Creating a Keys Iterator for a Map with key-value pairs') {
let map = %['name': 'Alice']
let map = Map.new
map['name'] = 'Alice'
let iter = map.keys
 
assert.true(iter.next?)
Loading
Loading
@@ -411,7 +413,7 @@ test.group('std::map::Map.keys') do (g) {
 
test.group('std::map::Map.values') do (g) {
g.test('Creating a Values Iterator for an empty Map') {
let map: Map!(String, String) = %[]
let map: Map!(String, String) = Map.new
let iter = map.values
 
assert.false(iter.next?)
Loading
Loading
@@ -419,7 +421,10 @@ test.group('std::map::Map.values') do (g) {
}
 
g.test('Creating a Values Iterator for a Map with key-value pairs') {
let map = %['name': 'Alice']
let map = Map.new
map['name'] = 'Alice'
let iter = map.values
 
assert.true(iter.next?)
Loading
Loading
@@ -430,33 +435,70 @@ test.group('std::map::Map.values') do (g) {
 
test.group('std::map::Map.==') do (g) {
g.test('Comparing two identical Map instances') {
assert.equal(%['name': 'Alice'], %['name': 'Alice'])
let map1 = Map.new
let map2 = Map.new
map1['name'] = 'Alice'
map2['name'] = 'Alice'
assert.equal(map1, map2)
}
 
g.test('Comparing two different Map instances') {
assert.not_equal(%[], %['name': 'Alice'])
assert.not_equal(%['foo': 'bar'], %['name': 'Alice'])
let map1 = Map.new
let map2 = Map.new
let map3 = Map.new
map2['name'] = 'Alice'
map3['foo'] = 'bar'
assert.not_equal(map1, map2)
assert.not_equal(map2, map3)
}
}
 
test.group('std::map::Map.key?') do (g) {
g.test('Checking if a Map defines a key') {
let map = %['name': 'Alice']
let map = Map.new
map['name'] = 'Alice'
 
assert.true(map.key?('name'))
assert.false(map.key?('city'))
}
}
 
test.group('std::map::Map.set') do (g) {
g.test('Setting the value of a non-existing key') {
let map = Map.new
assert.equal(map.set('city', 'Amsterdam'), map)
assert.equal(map['city'], 'Amsterdam')
}
g.test('Setting the value of an existing key') {
let map = Map.new
map.set('name', 'Alice')
map.set('name', 'Bob')
assert.equal(map['name'], 'Bob')
}
}
test.group('std::map::Map.[]') do (g) {
g.test('Obtaining the value of a non-existing key') {
let map = %['name': 'Alice']
let map = Map.new
map['name'] = 'Alice'
 
assert.equal(map['city'], Nil)
}
 
g.test('Obtaining the value of an existing key') {
let map = %['name': 'Alice']
let map = Map.new
map['name'] = 'Alice'
 
assert.equal(map['name'], 'Alice')
}
Loading
Loading
@@ -464,14 +506,18 @@ test.group('std::map::Map.[]') do (g) {
 
test.group('std::map::Map.[]=') do (g) {
g.test('Setting the value of a non-existing key') {
let map = %['name': 'Alice']
let map = Map.new
map['name'] = 'Alice'
 
assert.equal(map['city'] = 'Amsterdam', 'Amsterdam')
assert.equal(map['city'], 'Amsterdam')
}
 
g.test('Setting the value of an existing key') {
let map = %['name': 'Alice']
let map = Map.new
map['name'] = 'Alice'
 
assert.equal(map['name'] = 'Bob', 'Bob')
assert.equal(map['name'], 'Bob')
Loading
Loading
@@ -480,11 +526,18 @@ test.group('std::map::Map.[]=') do (g) {
 
test.group('std::map::Map.length') do (g) {
g.test('Obtaining the length of an empty Map') {
assert.equal(%[].length, 0)
assert.equal(Map.new.length, 0)
}
 
g.test('Obtaining the length of a Map with key-value pairs') {
assert.equal(%['name': 'Alice'].length, 1)
assert.equal(%['name': 'Alice', 'city': 'Amsterdam'].length, 2)
let map1 = Map.new
let map2 = Map.new
map1['name'] = 'Alice'
map2['name'] = 'Alice'
map2['city'] = 'Amsterdam'
assert.equal(map1.length, 1)
assert.equal(map2.length, 2)
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment