Clean up Nil by splitting it into NilType and Nil

NilType is now the base object/prototype that Nil is an instance of,
instead of Nil being a direct instance of Object. This allows us to
implement traits and redefine methods on the type of Nil (= NilType),
instead of Nil itself. This in turn allows the compiler to use a more
efficient way of representing types versus instances.

In type signatures you can still use Nil (or NilType, though Nil is
preferred), as this signals that you expect or return something
compatible with Nil, which is only Nil itself.
parent b3e718f6
Pipeline #111380139 passed with stages
in 20 minutes and 38 seconds
......@@ -1083,6 +1083,10 @@ module Inkoc
typedb.nil_type.new_instance
end
def on_raw_get_nil_prototype(*)
typedb.nil_type.new_instance
end
def on_raw_run_block(*)
TypeSystem::Dynamic.new
end
......
......@@ -1288,6 +1288,10 @@ module Inkoc
builtin_prototype_instruction(PrototypeID::BOOLEAN, node, body)
end
def on_raw_get_nil_prototype(node, body)
builtin_prototype_instruction(PrototypeID::NIL, node, body)
end
def on_raw_get_byte_array_prototype(node, body)
builtin_prototype_instruction(PrototypeID::BYTE_ARRAY, node, body)
end
......
......@@ -10,5 +10,6 @@ module Inkoc
BLOCK = 5
BOOLEAN = 6
BYTE_ARRAY = 7
NIL = 8
end
end
......@@ -13,7 +13,7 @@
let Boolean = _INKOC.get_boolean_prototype
let True = _INKOC.get_true
let False = _INKOC.get_false
let Nil = _INKOC.get_nil
let NilType = _INKOC.get_nil_prototype
let Object = _INKOC.get_object_prototype
let String = _INKOC.get_string_prototype
......@@ -27,7 +27,7 @@ _INKOC.set_object_name(self, 'Inko')
_INKOC.set_object_name(Boolean, 'Boolean')
_INKOC.set_object_name(True, 'True')
_INKOC.set_object_name(False, 'False')
_INKOC.set_object_name(Nil, 'Nil')
_INKOC.set_object_name(NilType, 'NilType')
_INKOC.set_object_name(Object, 'Object')
_INKOC.set_object_name(String, 'String')
_INKOC.set_object_name(Integer, 'Integer')
......
......@@ -7,6 +7,7 @@ let Boolean = _INKOC.get_boolean_prototype
let True = _INKOC.get_true
let False = _INKOC.get_false
let Nil = _INKOC.get_nil
let NilType = _INKOC.get_nil_prototype
let Object = _INKOC.get_object_prototype
let String = _INKOC.get_string_prototype
......
......@@ -991,7 +991,7 @@ impl Mirrored for Boolean {
}
}
impl Mirrored for Nil {
impl Mirrored for NilType {
def mirror -> NilMirror {
NilMirror.new(self)
}
......
......@@ -16,20 +16,23 @@ import std::hash::(Hasher, Hash)
import std::marker::Optional
import std::unknown_message::UnknownMessage
impl Nil {
impl NilType {
# Always returns `Nil` itself.
#
# # Examples
#
# Sending `new` to a `Nil` produces a new `Nil`:
# Obtaining a new `Nil`:
#
# Nil.new # => Nil
# NilType.new # => Nil
#
# You should not have to do this though, as `Nil` is already an instance of
# `NilType`.
static def new -> Self {
self
}
}
impl ToInteger for Nil {
impl ToInteger for NilType {
# Always return `0`.
#
# # Examples
......@@ -42,7 +45,7 @@ impl ToInteger for Nil {
}
}
impl ToFloat for Nil {
impl ToFloat for NilType {
# Always returns `0.0`.
#
# # Examples
......@@ -55,14 +58,14 @@ impl ToFloat for Nil {
}
}
impl ToString for Nil {
impl ToString for NilType {
# Always returns an empty string.
def to_string -> String {
''
}
}
impl Conditional for Nil {
impl Conditional for NilType {
# Always returns `Nil`.
def if_true!(R)(block: do -> R) -> ?R {
Nil
......@@ -90,7 +93,7 @@ impl Conditional for Nil {
}
}
impl UnknownMessage for Nil {
impl UnknownMessage for NilType {
# Always returns `Nil`.
#
# By always returning `Nil` we can safely work with methods that produce
......@@ -107,11 +110,4 @@ impl UnknownMessage for Nil {
}
}
impl Optional for Nil {}
impl Hash for Nil {
def hash(hasher: Hasher) -> Nil {
_INKOC.hasher_write(hasher, self)
Nil
}
}
impl Optional for NilType {}
import std::test
import std::test::assert
test.group('std::nil::Nil.to_integer') do (g) {
test.group('std::nil::NilType.to_integer') do (g) {
g.test('Converting Nil to an Integer') {
assert.equal(Nil.to_integer, 0)
}
}
test.group('std::nil::Nil.to_float') do (g) {
test.group('std::nil::NilType.to_float') do (g) {
g.test('Converting Nil to a Float') {
assert.equal(Nil.to_float, 0.0)
}
}
test.group('std::nil::Nil.to_string') do (g) {
test.group('std::nil::NilType.to_string') do (g) {
g.test('Converting Nil to an empty String') {
assert.equal(Nil.to_string, '')
}
}
test.group('std::nil::Nil.if_true') do (g) {
test.group('std::nil::NilType.if_true') do (g) {
g.test('The supplied block is never called') {
assert.equal(Nil.if_true({ 42 }), Nil)
}
}
test.group('std::nil::Nil.if_false') do (g) {
test.group('std::nil::NilType.if_false') do (g) {
g.test('The supplied block is always called') {
assert.equal(Nil.if_false({ 42 }), 42)
}
}
test.group('std::nil::Nil.if') do (g) {
test.group('std::nil::NilType.if') do (g) {
g.test('The block passed to the "false" argument is called') {
let actual = Nil.if(true: { 1729 }, false: { 42 })
......@@ -39,19 +39,19 @@ test.group('std::nil::Nil.if') do (g) {
}
}
test.group('std::nil::Nil.and') do (g) {
test.group('std::nil::NilType.and') do (g) {
g.test('The supplied block is never called') {
assert.false(Nil.and { True })
}
}
test.group('std::nil::Nil.or') do (g) {
test.group('std::nil::NilType.or') do (g) {
g.test('The supplied block is always called') {
assert.true(Nil.or { True })
}
}
test.group('std::nil::Nil.an_unknown_message') do (g) {
test.group('std::nil::NilType.unknown_message') do (g) {
g.test('Sending an unknown message to Nil produces another Nil') {
assert.equal(Nil.an_unknown_message, Nil)
}
......
......@@ -52,6 +52,7 @@ pub fn prototype_for_identifier(
5 => state.block_prototype,
6 => state.boolean_prototype,
7 => state.byte_array_prototype,
8 => state.nil_prototype,
_ => return Err(format!("Invalid prototype identifier: {}", id_int)),
};
......
......@@ -104,6 +104,9 @@ pub struct State {
/// The singleton "false" object.
pub false_object: ObjectPointer,
/// The prototype for the "nil" object.
pub nil_prototype: ObjectPointer,
/// The singleton "nil" object.
pub nil_object: ObjectPointer,
......@@ -145,6 +148,7 @@ impl State {
let boolean_proto = perm_alloc.allocate_empty();
let true_obj = perm_alloc.allocate_empty();
let false_obj = perm_alloc.allocate_empty();
let nil_proto = perm_alloc.allocate_empty();
let nil_obj = perm_alloc.allocate_empty();
let byte_array_prototype = perm_alloc.allocate_empty();
......@@ -155,9 +159,10 @@ impl State {
string_proto.set_prototype(object_proto);
array_proto.set_prototype(object_proto);
block_proto.set_prototype(object_proto);
nil_proto.set_prototype(object_proto);
boolean_proto.set_prototype(object_proto);
nil_obj.set_prototype(object_proto);
nil_obj.set_prototype(nil_proto);
true_obj.set_prototype(boolean_proto);
false_obj.set_prototype(boolean_proto);
......@@ -189,6 +194,7 @@ impl State {
boolean_prototype: boolean_proto,
true_object: true_obj,
false_object: false_obj,
nil_prototype: nil_proto,
nil_object: nil_obj,
arguments: Vec::with_capacity(arguments.len()),
default_panic_handler: ObjectPointer::null(),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment