Remove type parameters when re-opening objects

When implementing a trait for an object or when re-opening an object,
the compiler required that you specify the type parameter names of the
object. For example, to implement the Equal trait for Triple you would
write:

    impl Equal for Triple!(A, B, C) {
      ...
    }

The original idea of this syntax was to make it more clear that type
parameters used in the body originated from the object, not the trait.

Over time, I realised that this doesn't actually help and in fact makes
the compiler's life more difficult in a bunch of places. In this commit,
we remove the need for specifying the type parameters, allowing you to
write the following instead:

    impl Equal for Triple {
      ...
    }
parent 4541d246
Pipeline #115344416 passed with stages
in 13 minutes and 6 seconds
......@@ -1087,28 +1087,44 @@ module Inkoc
# ...
# }
def implement_trait(start)
trait_or_object_name = type_name(advance_and_expect!(:constant))
first_name = advance_and_expect!(:constant)
if @lexer.next_type_is?(:for)
advance_and_expect!(:for)
object_name = type_name(advance_and_expect!(:constant))
body = block_body(advance_and_expect!(:curly_open))
AST::TraitImplementation.new(
trait_or_object_name,
object_name,
body,
if @lexer.next_type_is?(:type_args_open)
implement_trait_for_object(type_name(first_name), start.location)
elsif @lexer.next_type_is?(:for)
implement_trait_for_object(
AST::TypeName.new(
constant_from_token(first_name),
[],
first_name.location
),
start.location
)
else
body = block_body(advance_and_expect!(:curly_open))
AST::ReopenObject
.new(trait_or_object_name, body, start.location)
reopen_object(constant_from_token(first_name), start.location)
end
end
def implement_trait_for_object(trait_name, location)
advance_and_expect!(:for)
object_name = constant_from_token(advance_and_expect!(:constant))
body = block_body(advance_and_expect!(:curly_open))
AST::TraitImplementation.new(
trait_name,
object_name,
body,
location
)
end
def reopen_object(name, location)
body = block_body(advance_and_expect!(:curly_open))
AST::ReopenObject.new(name, body, location)
end
# Parses a return statement.
#
# Example:
......
......@@ -505,7 +505,7 @@ module Inkoc
end
def on_reopen_object(node, scope)
type = on_type_name_reference(node.name, scope)
type = define_type(node.name, scope)
return type if type.error?
......@@ -531,7 +531,7 @@ module Inkoc
end
def on_trait_implementation(node, scope)
object = on_type_name_reference(node.object_name, scope)
object = define_type(node.object_name, scope)
return object if object.error?
......
......@@ -389,7 +389,7 @@ module Inkoc
def on_trait_implementation(node, body)
loc = node.location
trait = get_global(node.trait_name.type_name, body, loc)
object = get_global(node.object_name.type_name, body, loc)
object = get_global(node.object_name.name, body, loc)
implement_trait(object, trait, body, loc)
......@@ -437,7 +437,7 @@ module Inkoc
def on_reopen_object(node, body)
loc = node.location
object = get_global(node.name.type_name, body, loc)
object = get_global(node.name.name, body, loc)
block = define_block(
Config::IMPL_NAME,
......
......@@ -17,7 +17,7 @@ module Inkoc
end
def on_trait_implementation(node, scope)
object = on_type_name_reference(node.object_name, scope)
object = define_type(node.object_name, scope)
return object if object.error?
......
......@@ -106,30 +106,6 @@ module Inkoc
)
end
def on_type_name_reference(node, scope)
type = define_type(node.name, scope)
return type if type.error?
if same_type_parameters?(node, type)
wrap_optional_type(node, type)
else
TypeSystem::Error.new
end
end
def same_type_parameters?(node, type)
node_names = node.type_parameters.map(&:type_name)
type_names = type.type_parameters.map(&:name)
if node_names == type_names
true
else
diagnostics.invalid_type_parameters(type, node_names, node.location)
false
end
end
def define_type(node, scope, *extra)
type = process_node(node, scope, *extra)
......
......@@ -2282,7 +2282,7 @@ describe Inkoc::Pass::DefineType do
.self_type
.define_attribute('Foo', object)
type = expression_type('impl Foo!(T) { def foo {} }')
type = expression_type('impl Foo { def foo {} }')
expect(type).to eq(object)
expect(object.lookup_method('foo')).to be_any
......@@ -2300,21 +2300,6 @@ describe Inkoc::Pass::DefineType do
expect(type).to be_error
expect(state.diagnostics.errors?).to eq(true)
end
it 'errors when not using the same type parameters' do
object = Inkoc::TypeSystem::Object.new(name: 'Foo')
object.define_type_parameter('T')
type_scope
.self_type
.define_attribute('Foo', object)
type = expression_type('impl Foo {}')
expect(type).to be_error
expect(state.diagnostics.errors?).to eq(true)
end
end
describe '#on_type_cast' do
......@@ -2398,24 +2383,6 @@ describe Inkoc::Pass::DefineType do
.define_attribute(trait.name, trait)
end
it 'errors if the object type parameters do not match' do
object.define_type_parameter('T')
type = expression_type('impl Inspect for List {}')
expect(type).to be_error
expect(state.diagnostics.errors?).to eq(true)
end
it 'errors if the trait type parameters do not match' do
trait.define_type_parameter('T')
type = expression_type('impl Inspect for List {}')
expect(type).to be_error
expect(state.diagnostics.errors?).to eq(true)
end
it 'errors if a required method is not implemented' do
method = Inkoc::TypeSystem::Block
.named_method('foo', state.typedb.block_type)
......
......@@ -18,7 +18,7 @@ import std::index::(Index, SetIndex)
import std::length::Length
import std::operators::Equal
impl Array!(T) {
impl Array {
# Returns a new Array containing the given values.
#
# # Examples
......@@ -202,7 +202,7 @@ impl Array!(T) {
}
}
impl Length for Array!(T) {
impl Length for Array {
# Returns the number of values in this Array.
#
# # Examples
......@@ -219,7 +219,7 @@ impl Length for Array!(T) {
}
}
impl Index!(Integer, T) for Array!(T) {
impl Index!(Integer, T) for Array {
# Returns the value at the given index, or Nil if no value was found.
#
# # Examples
......@@ -247,7 +247,7 @@ impl Index!(Integer, T) for Array!(T) {
}
}
impl SetIndex!(Integer, T) for Array!(T) {
impl SetIndex!(Integer, T) for Array {
# Stores a value at the given index, then returns it.
#
# If the index is out of bounds then all preceding indexes that are not set
......@@ -282,7 +282,7 @@ impl SetIndex!(Integer, T) for Array!(T) {
}
}
impl ToArray!(T) for Array!(T) {
impl ToArray!(T) for Array {
# Always returns `self`.
#
# # Examples
......@@ -295,7 +295,7 @@ impl ToArray!(T) for Array!(T) {
}
}
impl Equal for Array!(T) {
impl Equal for Array {
# Returns `True` if `self` and the given `Array` are identical.
#
# # Examples
......
......@@ -8,7 +8,7 @@ import std::conversion::ToString
import std::iterator::(self, Iterator)
import std::string_buffer::StringBuffer
impl Array!(T) {
impl Array {
# Returns an `Iterator` that iterates over all values in `self`.
#
# # Examples
......
......@@ -253,23 +253,36 @@ object Parser {
}
def implement_or_reopen(token: Token) !! ParseError -> Node {
let trait_or_object_name = try next_as_constant
let first_name = try token_of_type('constant')
let peeked = peek_token.type
(peek_token.type == 'for').if_true {
(peeked == 'type_args_open').if_true {
let trait_name = try constant(first_name)
try token_of_type('for')
return try implement_trait(token: token, trait_name: trait_name)
}
(peeked == 'for').if_true {
# Skip the "for"
next_token
return try implement_trait(token: token, trait_name: trait_or_object_name)
let trait_name = constant_from_token(first_name)
return try implement_trait(token: token, trait_name: trait_name)
}
try reopen_object(token: token, object_name: trait_or_object_name)
let object_name = constant_from_token(first_name)
try reopen_object(token: token, object_name: object_name)
}
def implement_trait(
token: Token,
trait_name: Constant
) !! ParseError -> ImplementTrait {
let object_name = try next_as_constant
let object_name = constant_from_token(try token_of_type('constant'))
let trait_bounds = try trait_bounds
let body = try next_as_restricted_body
......@@ -855,7 +868,7 @@ object Parser {
}
def constant(token: Token) !! ParseError -> Constant {
let node = Constant.new(name: token.value, location: token.location)
let node = constant_from_token(token)
(peek_token.type == 'type_args_open').if_true {
# Skip the "!("
......@@ -1202,6 +1215,10 @@ object Parser {
try constant(try token_of_type('constant'))
}
def constant_from_token(token: Token) -> Constant {
Constant.new(name: token.value, location: token.location)
}
def next_as_body !! ParseError -> Body {
try block_body(try token_of_type('curly_open'))
}
......
......@@ -62,7 +62,7 @@
# }
#
# # Next up we need to implement the Iterator trait.
# impl Iterator!(T) for LimitedIterator!(T) {
# impl Iterator!(T) for LimitedIterator {
# def next? -> Boolean {
# @index < 5
# }
......@@ -382,7 +382,7 @@ object Enumerator!(T) {
}
}
impl Iterator!(T) for Enumerator!(T) {
impl Iterator!(T) for Enumerator {
# Advances the iterator and returns the next value.
#
# See the documentation of `Iterator.next` for more information.
......
......@@ -513,7 +513,7 @@ object Map!(K: Hash + Equal, V) {
}
}
impl Equal for Map!(K, V) {
impl Equal for Map {
# Returns `True` if `self` and the given `Map` are identical to each
# other.
#
......@@ -547,7 +547,7 @@ impl Equal for Map!(K, V) {
}
}
impl Index!(K, V) for Map!(K, V) {
impl Index!(K, V) for Map {
# Returns the value for the given key, if any.
#
# # Examples
......@@ -572,7 +572,7 @@ impl Index!(K, V) for Map!(K, V) {
}
}
impl SetIndex!(K, V) for Map!(K, V) {
impl SetIndex!(K, V) for Map {
# Inserts the key and value in this `Map`, returning the inserted value.
#
# # Examples
......@@ -588,7 +588,7 @@ impl SetIndex!(K, V) for Map!(K, V) {
}
}
impl Length for Map!(K, V) {
impl Length for Map {
# Returns the number of key-value pairs in this map.
#
# # Examples
......
......@@ -575,7 +575,7 @@ object ArrayMirror!(T) {
}
}
impl Format for ArrayMirror!(T) {
impl Format for ArrayMirror {
def format!(F: Formatter)(formatter: F) -> F where T: Mirrored {
formatter.push('Array')
......@@ -600,7 +600,7 @@ impl Format for ArrayMirror!(T) {
}
}
impl Mirror for ArrayMirror!(T) {
impl Mirror for ArrayMirror {
def subject -> Array!(T) {
@subject
}
......@@ -641,7 +641,7 @@ object MapMirror!(K: Hash + Equal, V) {
}
}
impl Format for MapMirror!(K, V) {
impl Format for MapMirror {
def format!(F: Formatter)(formatter: F) -> F where K: Mirrored, V: Mirrored {
let mut index = 0
......@@ -676,7 +676,7 @@ impl Format for MapMirror!(K, V) {
}
}
impl Mirror for MapMirror!(K, V) {
impl Mirror for MapMirror {
def subject -> Map!(K, V) {
@subject
}
......@@ -692,7 +692,7 @@ object SetMirror!(V: Hash + Equal) {
}
}
impl Format for SetMirror!(V) {
impl Format for SetMirror {
def format!(F: Formatter)(formatter: F) -> F where V: Mirrored {
let mut index = 0
......@@ -721,7 +721,7 @@ impl Format for SetMirror!(V) {
}
}
impl Mirror for SetMirror!(V) {
impl Mirror for SetMirror {
def subject -> Set!(V) {
@subject
}
......@@ -737,7 +737,7 @@ object PairMirror!(A, B) {
}
}
impl Format for PairMirror!(A, B) {
impl Format for PairMirror {
def format!(F: Formatter)(formatter: F) -> F where A: Mirrored, B: Mirrored {
formatter.push('Pair { ')
......@@ -756,7 +756,7 @@ impl Format for PairMirror!(A, B) {
}
}
impl Mirror for PairMirror!(A, B) {
impl Mirror for PairMirror {
def subject -> Pair!(A, B) {
@subject
}
......@@ -772,7 +772,7 @@ object TripleMirror!(A, B, C) {
}
}
impl Format for TripleMirror!(A, B, C) {
impl Format for TripleMirror {
def format!(F: Formatter)(
formatter: F
) -> F where A: Mirrored, B: Mirrored, C: Mirrored {
......@@ -799,7 +799,7 @@ impl Format for TripleMirror!(A, B, C) {
}
}
impl Mirror for TripleMirror!(A, B, C) {
impl Mirror for TripleMirror {
def subject -> Triple!(A, B, C) {
@subject
}
......@@ -926,7 +926,7 @@ impl Mirrored for Object {
}
}
impl Mirrored for Array!(T) {
impl Mirrored for Array {
def mirror -> ArrayMirror!(T) {
ArrayMirror.new(self)
}
......@@ -938,13 +938,13 @@ impl Mirrored for ByteArray {
}
}
impl Mirrored for Map!(K, V) {
impl Mirrored for Map {
def mirror -> MapMirror!(K, V) {
MapMirror.new(self)
}
}
impl Mirrored for Set!(V) {
impl Mirrored for Set {
def mirror -> SetMirror!(V) {
SetMirror.new(self)
}
......@@ -980,13 +980,13 @@ impl Mirrored for Block {
}
}
impl Mirrored for Pair!(A, B) {
impl Mirrored for Pair {
def mirror -> PairMirror!(A, B) {
PairMirror.new(self)
}
}
impl Mirrored for Triple!(A, B, C) {
impl Mirrored for Triple {
def mirror -> TripleMirror!(A, B, C) {
TripleMirror.new(self)
}
......
......@@ -50,13 +50,13 @@ object Pair!(A, B) {
}
}
impl Equal for Pair!(A, B) {
impl Equal for Pair {
def ==(other: Self) -> Boolean where A: Equal, B: Equal {
(@first == other.first).and { @second == other.second }
}
}
impl Hash for Pair!(A, B) {
impl Hash for Pair {
def hash(hasher: Hasher) -> Nil where A: Hash, B: Hash {
@first.hash(hasher)
@second.hash(hasher)
......@@ -128,7 +128,7 @@ object Triple!(A, B, C) {
}
}
impl Equal for Triple!(A, B, C) {
impl Equal for Triple {
def ==(other: Self) -> Boolean where A: Equal, B: Equal, C: Equal {
(@first == other.first)
.and { @second == other.second }
......@@ -136,7 +136,7 @@ impl Equal for Triple!(A, B, C) {
}
}
impl Hash for Triple!(A, B, C) {
impl Hash for Triple {
def hash(hasher: Hasher) -> Nil where A: Hash, B: Hash, C: Hash {
@first.hash(hasher)
@second.hash(hasher)
......
......@@ -50,7 +50,7 @@ object Range!(T: Successor + SmallerOrEqual) {
}
}
impl Equal for Range!(T) {
impl Equal for Range {
# Returns `True` if `self` and `other` are identical.
#
# # Examples
......@@ -67,7 +67,7 @@ impl Equal for Range!(T) {
}
}
impl ToArray!(T) for Range!(T) {
impl ToArray!(T) for Range {
# Converts `self` to an `Array`.
#
# # Examples
......
......@@ -123,7 +123,7 @@ object Set!(V: Hash + Equal) {
}
}
impl Equal for Set!(V) {
impl Equal for Set {
# Returns `True` if `self` and the given `Set` are identical to each
# other.
#
......@@ -155,7 +155,7 @@ impl Equal for Set!(V) {
}
}
impl Length for Set!(V) {
impl Length for Set {
# Returns the number of values in this `Set`.
#
# # Examples
......
......@@ -417,15 +417,6 @@ test.group('Parsing implementation blocks') do (g) {
assert.true(node.body.children.empty?)
}
g.test('Reopening an object with type arguments') {
let node = parse_as(input: 'impl A!(B, C) {}', type: ReopenObject)
let params = node.name.type_arguments
assert.equal(params.length, 2)
assert.equal((params[0] as Constant).name, 'B')
assert.equal((params[1] as Constant).name, 'C')
}
g.test('Implementing a trait') {
let node = parse_as(input: 'impl A for B {}', type: ImplementTrait)
......@@ -437,18 +428,18 @@ test.group('Parsing implementation blocks') do (g) {
}
g.test('Implementing a trait with type arguments') {
let node = parse_as(input: 'impl A!(C) for B!(D) {}', type: ImplementTrait)
let node = parse_as(input: 'impl A!(C) for B {}', type: ImplementTrait)
assert.equal(node.trait_name.name, 'A')
assert.equal((node.trait_name.type_arguments[0] as Constant).name, 'C')
assert.equal(node.object_name.name, 'B')
assert.equal((node.object_name.type_arguments[0] as Constant).name, 'D')
assert.true(node.object_name.type_arguments.empty?)
}
g.test('Implementing a trait with trait bounds') {
let node =
parse_as(input: 'impl A for B!(C) where C: D, E: F {}', type: ImplementTrait)
parse_as(input: 'impl A for B where C: D, E: F {}', type: ImplementTrait)
let bounds = node.trait_bounds
......@@ -459,6 +450,18 @@ test.group('Parsing implementation blocks') do (g) {
assert.equal(bounds[1].required_traits[0].name, 'F')
}
g.test('Reopening an object while specifying type parameters') {
assert.panic {
parse('impl Foo!(A) {}')
}
}
g.test('Specifying object type parameters when implementing a trait') {
assert.panic {
parse('impl Foo for Bar!(A) {}')
}
}
g.test('Reopening an object without closing the body') {
assert.panic {
parse('impl Foo {')
......
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