Move std::reflection into std::mirror

This moves all code from `std::reflection` into `std::mirror`, finally
removing the need for the two separate modules. We also renamed
`kind_of?` to `implements_trait?`, and implemented both it and
`instance_of?` in pure Inko. This in turn allows us to remove some
specialised VM instructions.

Fixes #153
parent bf0d6d66
Pipeline #39778141 failed with stages
in 7 minutes and 5 seconds
......@@ -94,10 +94,8 @@ module Inkoc
IntegerSmallerOrEqual
FloatGreaterOrEqual
FloatSmallerOrEqual
ObjectIsKindOf
CopyBlocks
SetAttributeToObject
PrototypeChainAttributeContains
FloatIsNan
FloatIsInfinite
FloatFloor
......
......@@ -1087,7 +1087,7 @@ module Inkoc
end
def on_raw_get_toplevel(*)
typedb.top_level
typedb.top_level.new_instance
end
def on_raw_set_prototype(node, _)
......@@ -1127,18 +1127,10 @@ module Inkoc
typedb.boolean_type.new_instance
end
def on_raw_object_is_kind_of(*)
typedb.boolean_type.new_instance
end
def on_raw_copy_blocks(*)
TypeSystem::Void.new
end
def on_raw_prototype_chain_attribute_contains(*)
typedb.boolean_type.new_instance
end
def on_raw_integer_to_string(*)
typedb.string_type.new_instance
end
......@@ -1438,7 +1430,7 @@ module Inkoc
end
def on_raw_get_prototype(*)
TypeSystem::Dynamic.new
typedb.object_type.new_instance
end
def on_raw_get_attribute_names(*)
......
......@@ -863,10 +863,6 @@ module Inkoc
raw_binary_instruction(:ObjectEquals, node, body)
end
def on_raw_object_is_kind_of(node, body)
raw_binary_instruction(:ObjectIsKindOf, node, body)
end
def on_raw_copy_blocks(node, body)
to = process_node(node.arguments.fetch(0), body)
from = process_node(node.arguments.fetch(1), body)
......@@ -874,10 +870,6 @@ module Inkoc
body.instruct(:CopyBlocks, to, from, node.location)
end
def on_raw_prototype_chain_attribute_contains(node, body)
raw_ternary_instruction(:PrototypeChainAttributeContains, node, body)
end
def on_raw_integer_to_string(node, body)
raw_unary_instruction(:IntegerToString, node, body)
end
......
......@@ -2797,7 +2797,7 @@ describe Inkoc::Pass::DefineType do
it 'returns the top-level type' do
type = expression_type('_INKOC.get_toplevel')
expect(type).to eq(state.typedb.top_level)
expect(type).to be_type_instance_of(state.typedb.top_level)
end
end
......
......@@ -15,7 +15,6 @@ import std::index::(Index, SetIndex)
import std::iterator::(Iterator as IteratorTrait)
import std::length::Length
import std::operators::Equal
import std::reflection
## A mutable sequence of bytes, similar to an `Array`.
let ByteArray = _INKOC.get_byte_array_prototype
......@@ -42,11 +41,7 @@ impl ByteArray {
##
## ByteArray.new([10, 20, 30])
def new(bytes: Array!(Integer) = []) -> Self {
let byte_array = _INKOC.byte_array_from_array(bytes)
reflection.set_prototype(byte_array, self)
byte_array as ByteArray
_INKOC.byte_array_from_array(bytes) as ByteArray
}
## Removes all values from this `ByteArray`.
......@@ -365,11 +360,7 @@ impl Inspect for ByteArray {
impl String {
## Returns a `ByteArray` containing the bytes of this `String`.
def to_byte_array -> ByteArray {
let bytes = _INKOC.string_to_byte_array(self)
reflection.set_prototype(bytes, ByteArray)
bytes as ByteArray
_INKOC.string_to_byte_array(self) as ByteArray
}
}
......
......@@ -72,7 +72,6 @@
#! Like most reflection systems mirrors do come with some overhead. In
#! particular a mirror has to be allocated every time you need one. As such we
#! recommend against using reflection in performance critical code.
import std::reflection
import std::fs::path::Path
## The default name to use for objects that don't have an explicit name.
......@@ -81,25 +80,49 @@ let DEFAULT_OBJECT_NAME = 'Object'
## The name of the attribute that contains the name of an object.
let OBJECT_NAME_ATTRIBUTE = '@_object_name'
## The name of the attribute that stores all the implemented traits.
let IMPLEMENTED_TRAITS_ATTRIBUTE = '@_implemented_traits'
## A generic mirror for a regular object.
object ObjectMirror!(T) {
def init(subject: T) {
object ObjectMirror {
def init(subject: Object) {
let @subject = subject
}
## Returns the object reflected upon.
def subject -> T {
def subject -> Object {
@subject
}
## Returns the prototype of the given object.
def prototype {
reflection.prototype(@subject)
##
## # Examples
##
## Obtaining the prototype of an object:
##
## import std::mirror
##
## mirror.reflect_object('hello').prototype # => String
def prototype -> Object {
_INKOC.get_prototype(@subject)
}
## Sets the prototype of an object.
##
## ## Examples
##
## Setting the prototype of an object:
##
## import std::mirror
##
## let obj = Object.new
## let obj_mirror = mirror.reflect_object(obj)
##
## obj_mirror.prototype # => Object
## obj_mirror.prototype = String # => String
## obj_mirror.prototype # => String
def prototype=!(P)(prototype: P) -> P {
reflection.set_prototype(@subject, prototype)
_INKOC.set_prototype(@subject, prototype)
}
## Returns the value of an attribute.
......@@ -197,18 +220,74 @@ object ObjectMirror!(T) {
instance_attributes
}
## Returns `True` if one object is an instance of another object.
## Returns `True` if the subject is an instance of the given object.
##
## # Examples
##
## Checking if a `String` is an instance of `String`:
##
## import std::mirror
##
## let string_mirror = mirror.reflect_object('hello')
##
## See `std::reflection.instance_of?` for more information.
def instance_of?(other) -> Boolean {
reflection.instance_of?(@subject, other)
## string_mirror.instance_of?(String) # => True
## string_mirror.instance_of?(Integer) # => False
def instance_of?(other: Object) -> Boolean {
let mut proto = prototype
{
proto.equal?(other).if_true {
return True
}
proto = _INKOC.get_prototype(proto) as Object
proto.equal?(Nil).if_true {
return False
}
}.loop
}
## Returns `True` if one object is a kind of the other object.
## Returns `True` if the subject implements the given trait.
##
## # Examples
##
## Checking if an object implements a trait:
##
## import std::mirror
##
## See `std::reflection.kind_of?` for more information.
def kind_of?(other) -> Boolean {
reflection.kind_of?(@subject, other)
## trait Trait1 {}
## trait Trait2 {}
##
## object ExampleObject impl Trait1 {}
##
## let mirror = mirror.reflect_object(ExampleObject.new)
##
## mirror.implements_trait?(Trait1) # => True
## mirror.implements_trait?(Trait2) # => False
def implements_trait?(find: Trait) -> Boolean {
let mut subject = @subject
{
let traits = _INKOC.get_attribute(@subject, IMPLEMENTED_TRAITS_ATTRIBUTE)
traits.if_true {
_INKOC.get_attribute(traits, find).if_true {
return True
}
}
subject = _INKOC.get_prototype(subject) as Object
# If an object does not have a prototype (e.g. Object), Nil will be
# returned. Since we don't know the difference between "has no prototype"
# and "the prototype is Nil", we will just bail out once we hit Nil as the
# prototype. This works fine for Nil itself, because there is only a
# single instance of it.
subject.equal?(Nil).if_true {
return False
}
}.loop
}
## Returns the name of an object.
......@@ -362,7 +441,7 @@ object ModuleMirror {
}
## Returns a regular mirror for the given object.
def reflect_object!(T)(subject: T) -> ObjectMirror!(T) {
def reflect_object(subject: Object) -> ObjectMirror {
ObjectMirror.new(subject)
}
......
......@@ -6,6 +6,9 @@ impl Object {
##
## Two objects are considered identical if they reside at the exact same
## memory address. This is also known as referential equality.
##
## This method should not be redefined by other objects, as doing so can break
## various parts of the Inko runtime.
def equal?(other: Self) -> Boolean {
_INKOC.object_equals(self, other)
}
......
......@@ -2,7 +2,6 @@
import std::compare::Compare
import std::conversion::ToArray
import std::operators::(Equal, SmallerOrEqual)
import std::reflection
import std::successor::Successor
## An inclusive range of values of a fixed type.
......
#! Low-level reflection API for objects.
#!
#! This module provides various low-level reflection methods that can be used
#! for regular objects. For a more high-level API you should look at the
#! `std::mirror` module.
#!
#! # When to use std::reflection versus std::mirror
#!
#! `std::reflection` only provides a few methods used for very common reflection
#! operations such as checking if one object is an instance of another. Using
#! mirrors for such operations would result in unnecessary object allocations.
#!
#! If you intend to perform multiple reflection operations (or more complex ones
#! than supported by this module) you should use `std::mirror` instead.
#!
#! # Dependencies
#!
#! This module is imported in a variety of standard library modules. To prevent
#! any recursive dependencies from occurring this module _should not_ import any
#! other modules.
## Returns the prototype of the given object.
##
## # Examples
##
## Retrieving the prototype of a `String`:
##
## import std::reflection
##
## reflection.prototype('hello') # => String
def prototype(subject) {
_INKOC.get_prototype(subject)
}
## Sets the prototype of an object.
##
## # Examples
##
## This will set the prototype of `obj1` to `obj2`:
##
## import std::reflection
##
## let mut obj1 = Object.new
## let mut obj2 = Object.new
##
## reflection.set_prototype(obj1, obj2)
def set_prototype!(T)(subject, prototype: T) -> T {
_INKOC.set_prototype(subject, prototype)
}
## Returns `True` if one object is an instance of another object.
##
## Instance checking is performing by walking the prototype chain of the first
## object and checking if any of the members are identical to the second object.
##
## # Examples
##
## Checking if a string is an instance of `String`:
##
## import std::reflection
##
## reflection.instance_of?('hello', String) # => True
##
## Checking if an integer is an instance of `Integer`:
##
## import std::reflection
##
## reflection.instance_of?(10, Integer) # => True
##
## Checking if an integer is an instance of the same integer:
##
## import std::reflection
##
## reflection.instance_of?(10, 10) # => False
def instance_of?(compare, compare_with) -> Boolean {
_INKOC.object_is_kind_of(compare, compare_with)
}
## Returns `True` if one object is a kind of the other object.
##
## This method combines `std::reflection.instance_of?` with checking if `other`
## is a `Trait` implemented by the first object.
##
## # Examples
##
## Using two regular objects:
##
## import std::reflection
##
## reflection.kind_of?('hello', String) # => True
##
## Comparing an object with a trait:
##
## import std::reflection
## import std::conversion::ToString
##
## reflection.kind_of?('hello', ToString) # => True
def kind_of?(compare, compare_with) -> Boolean {
instance_of?(compare, compare_with).if_true { return True }
_INKOC.prototype_chain_attribute_contains(
compare,
'@_implemented_traits',
compare_with,
)
}
......@@ -18,7 +18,6 @@
#! objects, passing its instance of `RunnerState`. This allows for type-safe
#! message passing, without the need for pattern matching.
import std::process::(self, Receiver, Sender)
import std::reflection
import std::test::config::Configuration
import std::test::formatter::Formatter
import std::test::test::Test
......
......@@ -5,7 +5,8 @@ impl Trait {
##
## Implementing a trait will result in all the trait's methods being copied
## over to the given object. Once copied the trait is tracked in an internal
## object so methods such as `std::reflection.kind_of?` can make use of it.
## object so modules such as `std::mirror` can reflect upon this data during
## runtime.
##
## This method is not really meant for general use, instead it's called by the
## compiler whenever implementing a trait using the `impl` keyword.
......
import std::conversion::ToString
import std::debug
import std::fs
import std::mirror::(
self, BlockMirror, ModuleMirror, OBJECT_NAME_ATTRIBUTE, ObjectMirror
......@@ -7,9 +9,10 @@ import std::test::assert
let CURRENT_FILE = ModuleMirror.new(ThisModule).path
trait DummyTrait {}
trait Trait1 {}
trait Trait2 {}
object Dummy impl DummyTrait {
object Dummy impl Trait1 {
def init {
let @name = 'Alice'
let @age = 28
......@@ -21,7 +24,7 @@ test.group('std::mirror::ObjectMirror.subject') do (g) {
let obj = Dummy.new
let mirror = ObjectMirror.new(obj)
assert.equal(mirror.subject, obj)
assert.equal(mirror.subject as Dummy, obj)
}
}
......@@ -114,17 +117,46 @@ test.group('std::mirror::ObjectMirror.instance_of?') do (g) {
assert.true(mirror.instance_of?(Dummy))
assert.false(mirror.instance_of?(String))
assert.false(mirror.instance_of?(Nil))
}
g.test('Checking if Nil is an instance of Nil') {
let mirror = ObjectMirror.new(Nil)
assert.false(mirror.instance_of?(Nil))
}
g.test('Checking if Nil is an instance of Object') {
let mirror = ObjectMirror.new(Nil)
assert.true(mirror.instance_of?(Object))
}
}
test.group('std::mirror::ObjectMirror.kind_of?') do (g) {
g.test('Checking if an object is a kind of another object') {
test.group('std::mirror::ObjectMirror.implements_trait?') do (g) {
g.test('Checking if an object implements a trait') {
let obj = Dummy.new
let mirror = ObjectMirror.new(obj)
assert.true(mirror.kind_of?(Dummy))
assert.true(mirror.kind_of?(DummyTrait))
assert.false(mirror.kind_of?(String))
assert.true(mirror.implements_trait?(Trait1))
assert.false(mirror.implements_trait?(Trait2))
}
g.test('Checking if a parent object implements a trait') {
let obj1 = Dummy.new
let obj2 = Object.new
let mirror = ObjectMirror.new(obj2)
mirror.prototype = obj1
assert.true(mirror.implements_trait?(Trait1))
}
g.test('Checking if Nil implements a trait') {
let mirror = ObjectMirror.new(Nil)
assert.true(mirror.implements_trait?(ToString))
assert.false(mirror.implements_trait?(Trait1))
}
}
......@@ -155,7 +187,6 @@ test.group('std::mirror::ObjectMirror.name') do (g) {
g.test('Obtaining the name of a built-in object') {
assert.equal(ObjectMirror.new(Object).name, 'Object')
assert.equal(ObjectMirror.new(String).name, 'String')
assert.equal(ObjectMirror.new(Inko).name, 'Inko')
}
}
......@@ -191,8 +222,9 @@ test.group('std::mirror::BlockMirror.path') do (g) {
test.group('std::mirror::BlockMirror.line') do (g) {
g.test('Obtaining the line number of a block') {
let mirror = BlockMirror.new({})
let line = *debug.stacktrace(skip: 1, limit: 1)[0].line - 1
assert.equal(mirror.line, 193)
assert.equal(mirror.line, line)
}
}
......
......@@ -92,10 +92,8 @@ pub enum InstructionType {
IntegerSmallerOrEqual,
FloatGreaterOrEqual,
FloatSmallerOrEqual,
ObjectIsKindOf,
CopyBlocks,
SetAttributeToObject,
PrototypeChainAttributeContains,
FloatIsNan,
FloatIsInfinite,
FloatFloor,
......
......@@ -990,28 +990,6 @@ impl Machine {
context.set_register(reg, res);
}
InstructionType::ObjectIsKindOf => {
let reg = instruction.arg(0);
let comp = context.get_register(instruction.arg(1));
let comp_with = context.get_register(instruction.arg(2));
let res = object::kind_of(&self.state, comp, comp_with);
context.set_register(reg, res);
}
InstructionType::PrototypeChainAttributeContains => {
let reg = instruction.arg(0);
let obj = context.get_register(instruction.arg(1));
let name = context.get_register(instruction.arg(2));
let val = context.get_register(instruction.arg(3));
let res = object::prototype_chain_attribute_contains(
&self.state,
obj,
name,
val,
);
context.set_register(reg, res);
}
InstructionType::GetToplevel => {
context
.set_register(instruction.arg(0), self.state.top_level);
......
......@@ -185,33 +185,6 @@ pub fn kind_of(
}
}
pub fn prototype_chain_attribute_contains(
state: &RcState,
obj_ptr: ObjectPointer,
name_ptr: ObjectPointer,
val_ptr: ObjectPointer,
) -> ObjectPointer {
let mut source = obj_ptr;
let name = state.intern_pointer(name_ptr).unwrap_or_else(|_| name_ptr);
// For every object in the prototype chain (including self)
// we look up the target object, then we check if the value
// is in said object.
loop {
if let Some(obj) = source.lookup_attribute_in_self(&state, name) {
if obj.lookup_attribute(&state, val_ptr).is_some() {
return state.true_object;
}
}
if let Some(proto) = source.prototype(&state) {
source = proto;
} else {
return state.false_object;
}
}
}
pub fn attribute_exists(
state: &RcState,
source_ptr: ObjectPointer,
......
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