Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: crystal-lang/crystal
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 204c94c39702
Choose a base ref
...
head repository: crystal-lang/crystal
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 917971dedc08
Choose a head ref
  • 3 commits
  • 8 files changed
  • 2 contributors

Commits on Mar 23, 2017

  1. Fix self restriction with including generic module

    Ref: #3847
    
    Now, we can get a compile error with such a code:
    
        module Foo(T)
          def foo(x : T)
            x
          end
        end
    
        abstract struct Bar
          include Foo(self)
        end
    
        struct Baz1 < Bar
        end
    
        struct Baz2 < Bar
        end
    
        Baz1.new.foo Baz2.new # => no overload matches 'Baz1#foo' with type Baz2
    
    This commit adds `lazy_self` parameter to `lookup_type`. When `lazy_self`
    is `true`, `lookup_type` keeps `self` in generics type. It is used to
    look up type for `include` and `extend`.
    makenowjust authored and Brian J. Cardiff committed Mar 23, 2017
    Copy the full SHA
    6645b8f View commit details
  2. Add TODO for fixing self restriction

    Because old compiler wants this definition and CI uses old compiler...
    makenowjust authored and Brian J. Cardiff committed Mar 23, 2017
    Copy the full SHA
    8ace6d4 View commit details
  3. Merge pull request #3972 from MakeNowJust/fix/crystal/self-restriction

    Fix self restriction with including generic module
    bcardiff authored Mar 23, 2017
    Copy the full SHA
    917971d View commit details
16 changes: 16 additions & 0 deletions spec/compiler/semantic/macro_spec.cr
Original file line number Diff line number Diff line change
@@ -775,6 +775,22 @@ describe "Semantic: macro" do
)) { int32.metaclass }
end

it "finds generic type argument of included module with self" do
assert_type(%(
module Bar(T)
def t
{{ T }}
end
end
class Foo(U)
include Bar(self)
end
Foo(Int32).new.t
)) { generic_class("Foo", int32).metaclass }
end

it "finds free type vars" do
assert_type(%(
module Foo(T)
172 changes: 170 additions & 2 deletions spec/compiler/semantic/module_spec.cr
Original file line number Diff line number Diff line change
@@ -226,15 +226,183 @@ describe "Semantic: module" do
end
end
class Baz(X)
class Bar(U)
include Foo(self)
end
Bar(Int32).new.foo
") { generic_class("Bar", int32).metaclass }
end

it "includes generic module with self, and inherits it" do
assert_type("
module Foo(T)
def foo
T
end
end
class Bar(U)
include Foo(self)
end
class Baz < Bar(Int32)
end
Baz.new.foo
") { types["Baz"].metaclass }
end

it "includes generic module with self (check argument type, success)" do
assert_type("
module Foo(T)
def foo(x : T)
x
end
end
class Bar(U)
include Foo(self)
end
Bar(Int32).new.foo Bar(Int32).new
") { generic_class("Bar", int32) }
end

it "includes generic module with self (check argument superclass type, success)" do
assert_type("
module Foo(T)
def foo(x : T)
x
end
end
class Bar(U)
include Foo(self)
end
class Baz < Bar(Int32)
end
Bar(Int32).new.foo Baz.new
") { types["Baz"] }
end

it "includes generic module with self (check argument type, error)" do
assert_error "
module Foo(T)
def foo(x : T)
x
end
end
class Bar(U)
include Foo(self)
end
class Baz1 < Bar(Int32)
end
class Baz2 < Bar(Int32)
end
Baz1.new.foo Baz2.new
", "no overload matches"
end

it "includes generic module with self (check argument superclass type, error)" do
assert_error "
module Foo(T)
def foo(x : T)
x
end
end
class Bar(U)
include Foo(self)
end
class Baz < Bar(Int32)
end
Baz.new.foo Bar(Int32).new
", "no overload matches"
end

it "includes generic module with self (check return type, success)" do
assert_type("
module Foo(T)
def foo : T
Bar(Int32).new
end
end
class Bar(U)
include Foo(self)
end
Bar(Int32).new.foo
") { generic_class("Bar", int32).metaclass }
") { generic_class("Bar", int32) }
end

it "includes generic module with self (check return subclass type, success)" do
assert_type("
module Foo(T)
def foo : T
Baz.new
end
end
class Bar(U)
include Foo(self)
end
class Baz < Bar(Int32)
end
Bar(Int32).new.foo
") { types["Baz"] }
end

it "includes generic module with self (check return type, error)" do
assert_error "
module Foo(T)
def foo : T
Bar(Int32).new
end
end
class Bar(U)
include Foo(self)
end
class Baz < Bar(Int32)
end
Baz.new.foo
", "type must be Baz, not Bar(Int32)"
end

it "includes generic module with self (check return subclass type, error)" do
assert_error "
module Foo(T)
def foo : T
Baz2.new
end
end
class Bar(U)
include Foo(self)
end
class Baz1 < Bar(Int32)
end
class Baz2 < Bar(Int32)
end
Baz1.new.foo
", "type must be Baz1, not Baz2"
end

it "includes module but can't access metaclass methods" do
3 changes: 3 additions & 0 deletions src/compiler/crystal/macros/interpreter.cr
Original file line number Diff line number Diff line change
@@ -418,6 +418,9 @@ module Crystal
end

TypeNode.new(matched_type)
when Self
target = @scope == @program.class_type ? @scope : @scope.instance_type
TypeNode.new(target)
when ASTNode
matched_type
else
17 changes: 11 additions & 6 deletions src/compiler/crystal/semantic/main_visitor.cr
Original file line number Diff line number Diff line change
@@ -177,6 +177,8 @@ module Crystal
# It's different if from a virtual type we do `v.class.new`
# because the class could be any in the hierarchy.
node.type = check_type_in_type_args(type.remove_alias_if_simple).devirtualize
when Self
node.type = check_type_in_type_args(the_self(node).remove_alias_if_simple)
when ASTNode
type.accept self unless type.type?
node.syntax_replacement = type
@@ -310,12 +312,7 @@ module Crystal
end

def visit(node : Self)
the_self = (@scope || current_type)
if the_self.is_a?(Program)
node.raise "there's no self in this scope"
end

node.type = the_self.instance_type
node.type = the_self(node).instance_type
end

def visit(node : Var)
@@ -3124,6 +3121,14 @@ module Crystal
end
end

def the_self(node)
the_self = (@scope || current_type)
if the_self.is_a?(Program)
node.raise "there's no self in this scope"
end
the_self
end

def visit(node : When | Unless | Until | MacroLiteral | OpAssign)
raise "BUG: #{node.class_desc} node '#{node}' (#{node.location}) should have been eliminated in normalize"
end
4 changes: 2 additions & 2 deletions src/compiler/crystal/semantic/semantic_visitor.cr
Original file line number Diff line number Diff line change
@@ -220,8 +220,8 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor
end
end

def lookup_type(node : ASTNode, free_vars = nil)
current_type.lookup_type(node, free_vars: free_vars, allow_typeof: false)
def lookup_type(node : ASTNode, free_vars = nil, lazy_self = false)
current_type.lookup_type(node, free_vars: free_vars, allow_typeof: false, lazy_self: lazy_self)
end

def check_outside_exp(node, op)
2 changes: 1 addition & 1 deletion src/compiler/crystal/semantic/top_level_visitor.cr
Original file line number Diff line number Diff line change
@@ -811,7 +811,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor
def include_in(current_type, node, kind)
node_name = node.name

type = lookup_type(node_name)
type = lookup_type(node_name, lazy_self: true)
case type
when GenericModuleType
node.raise "wrong number of type vars for #{type} (given 0, expected #{type.type_vars.size})"
75 changes: 42 additions & 33 deletions src/compiler/crystal/semantic/type_lookup.cr
Original file line number Diff line number Diff line change
@@ -38,28 +38,28 @@ class Crystal::Type
# ```
#
# If `self` is `Foo` and `Bar(Baz)` is given, the result will be `Foo::Bar(Baz)`.
def lookup_type(node : ASTNode, self_type = self.instance_type, allow_typeof = true, free_vars : Hash(String, TypeVar)? = nil) : Type
TypeLookup.new(self, self_type, true, allow_typeof, free_vars).lookup(node).not_nil!
def lookup_type(node : ASTNode, self_type = self.instance_type, allow_typeof = true, lazy_self = false, free_vars : Hash(String, TypeVar)? = nil) : Type
TypeLookup.new(self, self_type, true, allow_typeof, lazy_self, free_vars).lookup(node).not_nil!
end

# Similar to `lookup_type`, but returns `nil` if a type can't be found.
def lookup_type?(node : ASTNode, self_type = self.instance_type, allow_typeof = true, free_vars : Hash(String, TypeVar)? = nil) : Type?
TypeLookup.new(self, self_type, false, allow_typeof, free_vars).lookup(node)
def lookup_type?(node : ASTNode, self_type = self.instance_type, allow_typeof = true, lazy_self = false, free_vars : Hash(String, TypeVar)? = nil) : Type?
TypeLookup.new(self, self_type, false, allow_typeof, lazy_self, free_vars).lookup(node)
end

# Similar to `lookup_type`, but the result might also be an ASTNode, for example when
# looking `N` relative to a StaticArray.
def lookup_type_var(node : Path, free_vars : Hash(String, TypeVar)? = nil) : Type | ASTNode
TypeLookup.new(self, self.instance_type, true, false, free_vars).lookup_type_var(node).not_nil!
TypeLookup.new(self, self.instance_type, true, false, false, free_vars).lookup_type_var(node).not_nil!
end

# Similar to `lookup_type_var`, but might return `nil`.
def lookup_type_var?(node : Path, free_vars : Hash(String, TypeVar)? = nil, raise = false) : Type | ASTNode | Nil
TypeLookup.new(self, self.instance_type, raise, false, free_vars).lookup_type_var?(node)
TypeLookup.new(self, self.instance_type, raise, false, false, free_vars).lookup_type_var?(node)
end

private struct TypeLookup
def initialize(@root : Type, @self_type : Type, @raise : Bool, @allow_typeof : Bool, @free_vars : Hash(String, TypeVar)? = nil)
def initialize(@root : Type, @self_type : Type, @raise : Bool, @allow_typeof : Bool, @lazy_self : Bool, @free_vars : Hash(String, TypeVar)? = nil)
@in_generic_args = 0

# If we are looking types inside a non-instantiated generic type,
@@ -88,6 +88,8 @@ class Crystal::Type
end
when Type
return type_var
when Self
return lookup(type_var)
end

if @raise
@@ -215,8 +217,14 @@ class Crystal::Type
type_vars = Array(TypeVar).new(node.type_vars.size + 1)
node.type_vars.each do |type_var|
case type_var
when Self
if @lazy_self
type_vars << type_var
next
end
when NumberLiteral
type_vars << type_var
next
when Splat
type = in_generic_args { lookup(type_var.exp) }
return if !@raise && !type
@@ -234,37 +242,38 @@ class Crystal::Type

type_var.raise "can only splat tuple type, not #{splat_type}"
end
else
# Check the case of T resolving to a number
if type_var.is_a?(Path) && type_var.names.size == 1
type = @root.lookup_path(type_var)
case type
when Const
interpreter = MathInterpreter.new(@root)
begin
num = interpreter.interpret(type.value)
type_vars << NumberLiteral.new(num)
rescue ex : Crystal::Exception
type_var.raise "expanding constant value for a number value", inner: ex
end
next
when ASTNode
type_vars << type
next
next
end

# Check the case of T resolving to a number
if type_var.is_a?(Path) && type_var.names.size == 1
type = @root.lookup_path(type_var)
case type
when Const
interpreter = MathInterpreter.new(@root)
begin
num = interpreter.interpret(type.value)
type_vars << NumberLiteral.new(num)
rescue ex : Crystal::Exception
type_var.raise "expanding constant value for a number value", inner: ex
end
next
# when ASTNode
# type_vars << type
# next
end
end

type = in_generic_args { lookup(type_var) }
return if !@raise && !type
type = type.not_nil!

case instance_type
when GenericUnionType, PointerType, StaticArrayType, TupleType, ProcType
check_type_allowed_in_generics(type_var, type, "can't use #{type} as a generic type argument")
end
type = in_generic_args { lookup(type_var) }
return if !@raise && !type
type = type.not_nil!

type_vars << type.virtual_type
case instance_type
when GenericUnionType, PointerType, StaticArrayType, TupleType, ProcType
check_type_allowed_in_generics(type_var, type, "can't use #{type} as a generic type argument")
end

type_vars << type.virtual_type
end

begin
2 changes: 2 additions & 0 deletions src/enum.cr
Original file line number Diff line number Diff line change
@@ -237,6 +237,8 @@ struct Enum
value <=> other.value
end

# TODO: Remove `#==` after release next version. It is no longer needed.

# :nodoc:
def ==(other)
false