Skip to content

Commit

Permalink
Showing 6 changed files with 108 additions and 22 deletions.
7 changes: 0 additions & 7 deletions spec/compiler/crystal/tools/playground_spec.cr
Original file line number Diff line number Diff line change
@@ -59,13 +59,6 @@ describe Playground::Agent do
x, y = 3, 4
agent.i(1, ["x", "y"]) { {x, y} }.should eq({3, 4})
agent.last_message.should eq(%({"tag":32,"type":"value","line":1,"value":"{3, 4}","value_type":"Tuple(Int32, Int32)","data":{"x":"3","y":"4"}}))

agent.i(1) { nil.as(Void?) }
agent.last_message.should eq(%({"tag":32,"type":"value","line":1,"value":"nil","value_type":"(Void | Nil)"}))
agent.i(1) { a_sample_void.as(Void?) }
agent.last_message.should eq(%({"tag":32,"type":"value","line":1,"value":"nil","value_type":"(Void | Nil)"}))
agent.i(1) { a_sample_void }
agent.last_message.should eq(%({"tag":32,"type":"value","line":1,"value":"nil","value_type":"Nil"}))
end
end

50 changes: 49 additions & 1 deletion spec/compiler/type_inference/def_overload_spec.cr
Original file line number Diff line number Diff line change
@@ -833,7 +833,7 @@ describe "Type inference: def overload" do
" - A::String.foo(a : A::String, b : Bool)"
end

it "overloads on metaclass" do
it "overloads on metaclass (#2916)" do
assert_type(%(
def foo(x : String.class)
1
@@ -846,4 +846,52 @@ describe "Type inference: def overload" do
{foo(String), foo(typeof("" || nil))}
)) { tuple_of([int32, char]) }
end

it "overloads on metaclass (2) (#2916)" do
assert_type(%(
def foo(x : String.class)
1
end
def foo(x : ::String.class)
'a'
end
foo(String)
)) { char }
end

it "overloads on metaclass (3) (#2916)" do
assert_type(%(
class Foo
end
class Bar < Foo
end
def foo(x : Foo.class)
1
end
def foo(x : Bar.class)
'a'
end
{foo(Bar), foo(Foo)}
)) { tuple_of([char, int32]) }
end

it "overloads union against non-union (#2904)" do
assert_type(%(
def foo(x : Int32?)
true
end
def foo(x : Int32)
'a'
end
{foo(1), foo(nil)}
)) { tuple_of([char, bool]) }
end
end
6 changes: 6 additions & 0 deletions spec/compiler/type_inference/union_spec.cr
Original file line number Diff line number Diff line change
@@ -149,4 +149,10 @@ describe "Type inference: union" do
Union(Foo, Bar)
)) { types["Foo"].virtual_type!.metaclass }
end

it "treats void as nil in union" do
assert_type(%(
nil.as(Void?)
)) { nil_type }
end
end
49 changes: 47 additions & 2 deletions src/compiler/crystal/semantic/restrictions.cr
Original file line number Diff line number Diff line change
@@ -1,6 +1,36 @@
require "../syntax/ast"
require "../types"

# Here is the logic for deciding two things:
#
# 1. Whether a method should come before another one
# when considering overloads.
# This is what `restriction_of?` is for.
# 2. What's the resulting type of filtering a type
# by a restriction.
# This is what `restrict` is for.
#
# If `a.restriction_of?(b)` is true, it means that
# `a` should come before `b` when considering restrictions.
# This applies almost always to AST nodes, which are
# sometimes resolved to see if a type inherits another
# one (and so it should be considered before that type),
# but can apply to types when arguments have a fixed
# type (mostly for primitive methods, though we should
# get rid of this to simplify things).
# A similar logic applies to a `Def`, where this logic
# is applied for each of the arguments, though here
# the number of arguments, splat index and other factors
# are considered.
# If `a.restriction_of?(b) == true` and `b.restriction_of?(a) == true`,
# for `a` and `b` being `Def`s, then it means `a` and `b` are equivalent,
# and so when adding `b` to a types methods it will replace `a`.
#
# The method `restrict` is different in that the return
# value is not a boolean, but a type, and computing it
# might be a bit more expensive. For example when restricting
# `Int32 | String` against `Int32`, the result is `Int32`.

module Crystal
class ASTNode
def restriction_of?(other : Underscore, owner)
@@ -178,6 +208,7 @@ module Crystal
end

def restriction_of?(other : Union, owner)
# `true` if this type is a restriction of any type in the union
other.types.any? { |o| self.restriction_of?(o, owner) }
end

@@ -188,7 +219,11 @@ module Crystal

class Union
def restriction_of?(other : Path, owner)
types.any? &.restriction_of?(other, owner)
# For a union to be considered before a path,
# all types in the union must be considered before
# that path.
# For example when using all subtypes of a parent type.
types.all? &.restriction_of?(other, owner)
end
end

@@ -207,7 +242,13 @@ module Crystal

class Metaclass
def restriction_of?(other : Metaclass, owner)
self == other
self_type = TypeLookup.lookup?(owner, self)
other_type = TypeLookup.lookup?(owner, other)
if self_type && other_type
self_type.restriction_of?(other_type, owner)
else
self == other
end
end
end

@@ -768,6 +809,10 @@ module Crystal
restricted = instance_type.restrict(other.instance_type.base_type, context)
restricted ? self : nil
end

def restriction_of?(other : VirtualMetaclassType, owner)
restriction_of?(other.base_type.metaclass, owner)
end
end

class GenericClassInstanceMetaclassType
6 changes: 6 additions & 0 deletions src/compiler/crystal/semantic/type_merge.cr
Original file line number Diff line number Diff line change
@@ -89,6 +89,12 @@ module Crystal
add_type types, type.remove_alias
end

# When Void participates in a union, it becomes Nil
# (users shouldn't deal with real Void values)
def add_type(types, type : VoidType)
add_type(types, nil_type)
end

def add_type(types, type : Type)
types << type unless types.includes? type
end
12 changes: 0 additions & 12 deletions src/compiler/crystal/tools/playground/agent.cr
Original file line number Diff line number Diff line change
@@ -45,18 +45,6 @@ class Crystal::Playground::Agent
to_value(value) rescue "(error)"
end

def to_value(value : Void)
"(void)"
end

def to_value(value : Void?)
if value
"(void)"
else
nil.inspect
end
end

def to_value(value)
value.inspect
end

0 comments on commit 48ecf33

Please sign in to comment.