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: 6dfadb5a5d58
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: 5d8c8bf68d70
Choose a head ref
  • 4 commits
  • 8 files changed
  • 1 contributor

Commits on Jun 9, 2016

  1. Macros: interpret range literal begin/end before using it as a call a…

    …rgument
    Ary Borenszweig committed Jun 9, 2016
    Copy the full SHA
    51641d3 View commit details
  2. Allow matching procs with splats in type restrictions

    Ary Borenszweig committed Jun 9, 2016
    Copy the full SHA
    b6a5551 View commit details
  3. Some fixes to splats in restrictions

    Ary Borenszweig committed Jun 9, 2016
    Copy the full SHA
    b46f2bb View commit details
  4. Fixed #2749: give error when nesting macro expressions

    Ary Borenszweig committed Jun 9, 2016
    Copy the full SHA
    5d8c8bf View commit details
8 changes: 8 additions & 0 deletions spec/compiler/macro/macro_methods_spec.cr
Original file line number Diff line number Diff line change
@@ -254,6 +254,10 @@ describe "macro methods" do
assert_macro "", %({{"hello"[1...-2]}}), [] of ASTNode, %("el")
end

it "executes string [Range] inclusive (computed)" do
assert_macro "", %({{"hello"[[1].size..-2]}}), [] of ASTNode, %("ell")
end

it "executes string chomp" do
assert_macro "", %({{"hello\n".chomp}}), [] of ASTNode, %("hello")
end
@@ -529,6 +533,10 @@ describe "macro methods" do
assert_macro "", %({{ [1, 2, 3, 4][1...-1] }}), [] of ASTNode, %([2, 3])
end

it "executes [] with computed range" do
assert_macro "", %({{ [1, 2, 3, 4][[1].size...-1] }}), [] of ASTNode, %([2, 3])
end

it "executes [] with two numbers" do
assert_macro "", %({{ [1, 2, 3, 4, 5][1, 3] }}), [] of ASTNode, %([2, 3, 4])
end
7 changes: 7 additions & 0 deletions spec/compiler/parser/parser_spec.cr
Original file line number Diff line number Diff line change
@@ -409,6 +409,10 @@ describe "Parser" do
it_parses "class Foo(T, *U); end", ClassDef.new("Foo".path, type_vars: ["T", "U"], splat_index: 1)
assert_syntax_error "class Foo(*T, *U); end", "splat type argument already specified"

it_parses "x : Foo(A, *B, C)", TypeDeclaration.new("x".var, Generic.new("Foo".path, ["A".path, "B".path.splat, "C".path] of ASTNode))
it_parses "x : *T -> R", TypeDeclaration.new("x".var, ProcNotation.new(["T".path.splat] of ASTNode, "R".path))
it_parses "def foo(x : *T -> R); end", Def.new("foo", args: [Arg.new("x", restriction: ProcNotation.new(["T".path.splat] of ASTNode, "R".path))])

it_parses "struct Foo; end", ClassDef.new("Foo".path, struct: true)

it_parses "Foo(T)", Generic.new("Foo".path, ["T".path] of ASTNode)
@@ -1158,6 +1162,9 @@ describe "Parser" do

it_parses "Foo.foo(count: 3).bar { }", Call.new(Call.new("Foo".path, "foo", named_args: [NamedArgument.new("count", 3.int32)]), "bar", block: Block.new)

assert_syntax_error "{{ {{ 1 }} }}", "can't nest macro expressions"
assert_syntax_error "{{ {% begin %} }}", "can't nest macro expressions"

assert_syntax_error "return do\nend", "unexpected token: do"

%w(def macro class struct module fun alias abstract include extend lib).each do |keyword|
48 changes: 48 additions & 0 deletions spec/compiler/type_inference/proc_spec.cr
Original file line number Diff line number Diff line change
@@ -851,4 +851,52 @@ describe "Type inference: proc" do
->{}.foo
)) { int32 }
end

it "accesses T inside variadic generic" do
assert_type(%(
def foo(proc : Proc(*T, R))
{T, R}
end
foo(->(x : Int32, y : Float64) { 'a' })
)) { tuple_of([tuple_of([int32, float64]).metaclass, char.metaclass]) }
end

it "accesses T inside variadic generic (2)" do
assert_type(%(
def foo(proc : Proc(*T, R))
{T, R}
end
foo(->(x : Int32) { 'a' })
)) { tuple_of([tuple_of([int32]).metaclass, char.metaclass]) }
end

it "accesses T inside variadic generic, in proc notation" do
assert_type(%(
def foo(proc : *T -> R)
{T, R}
end
foo(->(x : Int32, y : Float64) { 'a' })
)) { tuple_of([tuple_of([int32, float64]).metaclass, char.metaclass]) }
end

it "declares an instance variable with splat in proc notation" do
assert_type(%(
class Foo
@x : *{Int32, Char} -> String
def initialize
@x = ->(x : Int32, y : Char) { "a" }
end
def x
@x
end
end
Foo.new.x
)) { proc_of([int32, char, string]) }
end
end
14 changes: 14 additions & 0 deletions spec/compiler/type_inference/splat_spec.cr
Original file line number Diff line number Diff line change
@@ -457,6 +457,20 @@ describe "Type inference: splat" do
)) { no_return.metaclass }
end

it "matches splat in geneic type" do
assert_type(%(
class Foo(*T)
end
def method(x : Foo(A, *B, C))
{A, B, C}
end
foo = Foo(Int32, Char, String, Bool).new
method(foo)
)) { tuple_of([int32.metaclass, tuple_of([char, string]).metaclass, bool.metaclass]) }
end

describe Splat do
it "without splat" do
a_def = Def.new("foo", args: [Arg.new("x"), Arg.new("y")])
21 changes: 15 additions & 6 deletions src/compiler/crystal/macros/methods.cr
Original file line number Diff line number Diff line change
@@ -416,6 +416,9 @@ module Crystal
case arg
when RangeLiteral
from, to = arg.from, arg.to
from = interpreter.accept(from)
to = interpreter.accept(to)

unless from.is_a?(NumberLiteral)
raise "range from in StringLiteral#[] must be a number, not #{from.class_desc}: #{from}"
end
@@ -742,31 +745,34 @@ module Crystal

block_arg = block.args.first?

interpret_map(method, args) do |num|
interpret_map(method, args, interpreter) do |num|
interpreter.define_var(block_arg.name, NumberLiteral.new(num)) if block_arg
interpreter.accept block.body
end
when "to_a"
interpret_map(method, args) do |num|
interpret_map(method, args, interpreter) do |num|
NumberLiteral.new(num)
end
else
super
end
end

def interpret_map(method, args)
def interpret_map(method, args, interpreter)
interpret_argless_method(method, args) do
ArrayLiteral.map(interpret_to_range) do |num|
ArrayLiteral.map(interpret_to_range(interpreter)) do |num|
yield num
end
end
end

def interpret_to_range
def interpret_to_range(interpreter)
from = self.from
to = self.to

from = interpreter.accept(from)
to = interpreter.accept(to)

unless from.is_a?(NumberLiteral)
raise "range begin must be a NumberLiteral, not #{from.class_desc}"
end
@@ -1394,7 +1400,7 @@ private def intepret_array_or_tuple_method(object, klass, method, args, block, i
index = arg.to_number.to_i
value = object.elements[index]? || Crystal::NilLiteral.new
when Crystal::RangeLiteral
range = arg.interpret_to_range
range = arg.interpret_to_range(interpreter)
begin
klass.new(object.elements[range])
rescue ex
@@ -1406,6 +1412,9 @@ private def intepret_array_or_tuple_method(object, klass, method, args, block, i
when 2
from, to = args

from = interpreter.accept(from)
to = interpreter.accept(to)

unless from.is_a?(Crystal::NumberLiteral)
from.raise "expected first argument to RangeLiteral#[] to be a number, not #{from.class_desc}"
end
111 changes: 105 additions & 6 deletions src/compiler/crystal/semantic/restrictions.cr
Original file line number Diff line number Diff line change
@@ -418,6 +418,48 @@ module Crystal
return nil
end

# Consider the case of a splat in the type vars
splat_index = other.type_vars.index &.is_a?(Splat)
if splat_index
types = Array(Type).new(type_vars.size)
i = 0
type_vars.each_value do |var|
return nil unless var.is_a?(Var)

var_type = var.type
if i == self.splat_index
types.concat(var_type.as(TupleInstanceType).tuple_types)
else
types << var_type
end
i += 1
end

i = 0
other.type_vars.each do |type_var|
if type_var.is_a?(Splat)
count = types.size - (other.type_vars.size - 1)
return nil unless count >= 0

arg_types = types[i, count]
arg_types_tuple = context.type_lookup.program.tuple_of(arg_types)

restricted = arg_types_tuple.restrict(type_var.exp, context)
return nil unless restricted == arg_types_tuple

i += count
else
arg_type = types[i]
restricted = arg_type.restrict(type_var, context)
return unless restricted == arg_type

i += 1
end
end

return self
end

if generic_class.type_vars.size != other.type_vars.size
other.wrong_number_of "type vars", generic_class, other.type_vars.size, generic_class.type_vars.size
end
@@ -731,12 +773,37 @@ module Crystal
inputs_size = inputs ? inputs.size : 0
output = other.output

return nil if arg_types.size != inputs_size
# Consider the case of a splat in the type vars
if inputs && (splat_index = inputs.index &.is_a?(Splat))
i = 0
inputs.each do |input|
if input.is_a?(Splat)
count = arg_types.size - (inputs.size - 1)
return nil unless count >= 0

input_arg_types = arg_types[i, count]
input_arg_types_tuple = context.type_lookup.program.tuple_of(input_arg_types)

restricted = input_arg_types_tuple.restrict(input.exp, context)
return nil unless restricted == input_arg_types_tuple

i += count
else
arg_type = arg_types[i]
restricted = arg_type.restrict(input, context)
return unless restricted == arg_type

i += 1
end
end
else
return nil if arg_types.size != inputs_size

if inputs
inputs.zip(arg_types) do |input, my_input|
restricted = my_input.restrict(input, context)
return nil unless restricted == my_input
if inputs
inputs.zip(arg_types) do |input, my_input|
restricted = my_input.restrict(input, context)
return nil unless restricted == my_input
end
end
end

@@ -763,7 +830,39 @@ module Crystal
generic_class = context.type_lookup.lookup_type other.name
return super unless generic_class.is_a?(ProcType)

return nil unless other.type_vars.size == arg_types.size + 1
# Consider the case of a splat in the type vars
splat_index = other.type_vars.index &.is_a?(Splat)
if splat_index
proc_types = arg_types + [return_type]

i = 0
other.type_vars.each do |type_var|
if type_var.is_a?(Splat)
count = proc_types.size - (other.type_vars.size - 1)
return nil unless count >= 0

arg_types = proc_types[i, count]
arg_types_tuple = context.type_lookup.program.tuple_of(arg_types)

restricted = arg_types_tuple.restrict(type_var.exp, context)
return nil unless restricted == arg_types_tuple

i += count
else
arg_type = proc_types[i]
restricted = arg_type.restrict(type_var, context)
return unless restricted == arg_type

i += 1
end
end

return self
end

unless other.type_vars.size == arg_types.size + 1
return nil
end

other.type_vars.each_with_index do |other_type_var, i|
proc_type = arg_types[i]? || return_type
24 changes: 20 additions & 4 deletions src/compiler/crystal/semantic/type_lookup.cr
Original file line number Diff line number Diff line change
@@ -171,12 +171,28 @@ module Crystal
types = [] of Type
if inputs = node.inputs
inputs.each do |input|
input.accept self
return false if !@raise && !@type
if input.is_a?(Splat)
input.exp.accept self
return false if !@raise && !@type

a_type = type
if a_type.is_a?(TupleInstanceType)
types.concat(a_type.tuple_types)
else
if @raise
input.exp.raise "can only splat tuple type, not #{a_type}"
else
return false
end
end
else
input.accept self
return false if !@raise && !@type

Crystal.check_type_allowed_in_generics(input, type, "can't use #{type} as proc argument")
Crystal.check_type_allowed_in_generics(input, type, "can't use #{type} as proc argument")

types << type.virtual_type
types << type.virtual_type
end
end
end

Loading