Skip to content

Commit

Permalink
Fix custom array/hash-like literals in nested modules (crystal-lang#5685
Browse files Browse the repository at this point in the history
)
  • Loading branch information
asterite authored and RX14 committed Feb 7, 2018
1 parent 151c0da commit ab8ed5c
Show file tree
Hide file tree
Showing 10 changed files with 137 additions and 28 deletions.
46 changes: 46 additions & 0 deletions spec/compiler/codegen/array_literal_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,50 @@ describe "Code gen: array literal spec" do
custom.value
)).to_i.should eq(6)
end

it "creates custom non-generic array in module" do
run(%(
module Moo
class Custom
def initialize
@value = 0
end
def <<(element)
@value += element
end
def value
@value
end
end
end
custom = Moo::Custom {1, 2, 3}
custom.value
)).to_i.should eq(6)
end

it "creates custom generic array in module (#5684)" do
run(%(
module Moo
class Custom(T)
def initialize
@value = 0
end
def <<(element : T)
@value += element
end
def value
@value
end
end
end
custom = Moo::Custom {1, 2, 3}
custom.value
)).to_i.should eq(6)
end
end
58 changes: 58 additions & 0 deletions spec/compiler/codegen/hash_literal_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -152,4 +152,62 @@ describe "Code gen: hash literal spec" do
b["a"].call
)).to_i.should eq(1)
end

it "creates custom non-generic hash in module" do
run(%(
module Moo
class Custom
def initialize
@keys = 0
@values = 0
end
def []=(key, value)
@keys += key
@values += value
end
def keys
@keys
end
def values
@values
end
end
end
custom = Moo::Custom {1 => 10, 2 => 20}
custom.keys * custom.values
)).to_i.should eq(90)
end

it "creates custom generic hash in module (#5684)" do
run(%(
module Moo
class Custom(K, V)
def initialize
@keys = 0
@values = 0
end
def []=(key, value)
@keys += key
@values += value
end
def keys
@keys
end
def values
@values
end
end
end
custom = Moo::Custom {1 => 10, 2 => 20}
custom.keys * custom.values
)).to_i.should eq(90)
end
end
23 changes: 7 additions & 16 deletions src/compiler/crystal/semantic/main_visitor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2795,19 +2795,16 @@ module Crystal

case type
when GenericClassType
type_name = type.name.split "::"

path = Path.global(type_name).at(node.location)
generic_type = TypeNode.new(type).at(node.location)
type_of = TypeOf.new(node.elements).at(node.location)
generic = Generic.new(path, type_of).at(node.location)

generic = Generic.new(generic_type, type_of).at(node.location)

node.name = generic
when GenericClassInstanceType
# Nothing
else
type_name = type.to_s.split "::"
path = Path.global(type_name).at(node.location)
node.name = path
node.name = TypeNode.new(name.type).at(node.location)
end

expand_named(node)
Expand All @@ -2823,22 +2820,16 @@ module Crystal

case type
when GenericClassType
type_name = type.name.split "::"

path = Path.global(type_name).at(node.location)
generic_type = TypeNode.new(type).at(node.location)
type_of_keys = TypeOf.new(node.entries.map { |x| x.key.as(ASTNode) }).at(node.location)
type_of_values = TypeOf.new(node.entries.map { |x| x.value.as(ASTNode) }).at(node.location)
generic = Generic.new(path, [type_of_keys, type_of_values] of ASTNode).at(node.location)
generic = Generic.new(generic_type, [type_of_keys, type_of_values] of ASTNode).at(node.location)

node.name = generic
when GenericClassInstanceType
# Nothing
else
type_name = type.to_s.split "::"

path = Path.global(type_name).at(node.location)

node.name = path
node.name = TypeNode.new(name.type).at(node.location)
end

expand_named(node)
Expand Down
19 changes: 14 additions & 5 deletions src/compiler/crystal/semantic/restrictions.cr
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ module Crystal

def restrict(other : Generic, context)
# Special case: consider `Union(X, Y, ...)` the same as `X | Y | ...`
generic_type = context.defining_type.lookup_path other.name
generic_type = get_generic_type(other, context)
if generic_type.is_a?(GenericUnionType)
return restrict(Union.new(other.type_vars), context)
end
Expand Down Expand Up @@ -545,7 +545,7 @@ module Crystal
end

def restrict(other : Generic, context)
generic_type = context.defining_type.lookup_path other.name
generic_type = get_generic_type(other, context)
generic_type = generic_type.remove_alias if generic_type.is_a? AliasType
return super unless generic_type == self.generic_type

Expand Down Expand Up @@ -683,7 +683,7 @@ module Crystal
end

def restrict(other : Generic, context)
generic_type = context.defining_type.lookup_path other.name
generic_type = get_generic_type(other, context)
return super unless generic_type == self.generic_type

generic_type = generic_type.as(TupleType)
Expand Down Expand Up @@ -743,7 +743,7 @@ module Crystal
end

def restrict(other : Generic, context)
generic_type = context.defining_type.lookup_path other.name
generic_type = get_generic_type(other, context)
return super unless generic_type == self.generic_type

other_named_args = other.named_args
Expand Down Expand Up @@ -1023,7 +1023,7 @@ module Crystal
end

def restrict(other : Generic, context)
generic_type = context.defining_type.lookup_path other.name
generic_type = get_generic_type(other, context)
return super unless generic_type.is_a?(ProcType)

# Consider the case of a splat in the type vars
Expand Down Expand Up @@ -1091,3 +1091,12 @@ module Crystal
end
end
end

private def get_generic_type(node, context)
name = node.name
if name.is_a?(Crystal::Path)
context.defining_type.lookup_path name
else
name.type
end
end
3 changes: 2 additions & 1 deletion src/compiler/crystal/semantic/type_guess_visitor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,8 @@ module Crystal
end

# If it's Pointer(T).malloc or Pointer(T).null, guess it to Pointer(T)
if obj.is_a?(Generic) && obj.name.single?("Pointer") &&
if obj.is_a?(Generic) &&
(name = obj.name).is_a?(Path) && name.single?("Pointer") &&
(node.name == "malloc" || node.name == "null")
type = lookup_type?(obj)
return type if type.is_a?(PointerInstanceType)
Expand Down
4 changes: 3 additions & 1 deletion src/compiler/crystal/syntax/ast.cr
Original file line number Diff line number Diff line change
Expand Up @@ -1351,7 +1351,9 @@ module Crystal
end

class Generic < ASTNode
property name : Path
# Usually a Path, but can also be a TypeNode in the case of a
# custom array/hash-like literal.
property name : ASTNode
property type_vars : Array(ASTNode)
property named_args : Array(NamedArgument)?

Expand Down
6 changes: 4 additions & 2 deletions src/compiler/crystal/syntax/to_s.cr
Original file line number Diff line number Diff line change
Expand Up @@ -834,8 +834,10 @@ module Crystal
end

def visit(node : Generic)
if @inside_lib && node.name.names.size == 1
case node.name.names.first
name = node.name

if @inside_lib && (name.is_a?(Path) && name.names.size == 1)
case name.names.first
when "Pointer"
node.type_vars.first.accept self
@str << "*"
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/crystal/syntax/transformer.cr
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ module Crystal
end

def transform(node : Generic)
node.name = node.name.transform(self).as(Path)
node.name = node.name.transform(self)
transform_many node.type_vars
node
end
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/crystal/tools/doc/type.cr
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ class Crystal::Doc::Type
end

def node_to_html(node : Generic, io, links = true)
match = lookup_path(node.name)
match = lookup_path(node.name.as(Path))
if match
if match.must_be_included?
if links
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/crystal/tools/formatter.cr
Original file line number Diff line number Diff line change
Expand Up @@ -1052,7 +1052,7 @@ module Crystal
end

def visit(node : Generic)
name = node.name
name = node.name.as(Path)
first_name = name.global? && name.names.size == 1 && name.names.first

if name.global? && @token.type == :"::"
Expand Down

0 comments on commit ab8ed5c

Please sign in to comment.