Skip to content

Commit

Permalink
Parse and type casts (foo as Bar)
Browse files Browse the repository at this point in the history
  • Loading branch information
asterite committed Dec 27, 2013
1 parent 0929b0b commit 09207b4
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 1 deletion.
2 changes: 1 addition & 1 deletion spec/compiler/lexer/lexer_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ describe "Lexer" do
it_lexes "\t", :SPACE
it_lexes "\n", :NEWLINE
it_lexes "\n\n\n", :NEWLINE
it_lexes_keywords [:def, :if, :else, :elsif, :end, :true, :false, :class, :module, :include, :while, :nil, :do, :yield, :return, :unless, :next, :break, :begin, :lib, :fun, :type, :struct, :union, :enum, :macro, :ptr, :out, :require, :case, :when, :then, :of, :abstract, :rescue, :ensure, :is_a?, :alias, :pointerof, :ifdef]
it_lexes_keywords [:def, :if, :else, :elsif, :end, :true, :false, :class, :module, :include, :while, :nil, :do, :yield, :return, :unless, :next, :break, :begin, :lib, :fun, :type, :struct, :union, :enum, :macro, :ptr, :out, :require, :case, :when, :then, :of, :abstract, :rescue, :ensure, :is_a?, :alias, :pointerof, :ifdef, :as]
it_lexes_idents ["ident", "something", "with_underscores", "with_1", "foo?", "bar!", "foo$123"]
it_lexes_idents ["def?", "if?", "else?", "elsif?", "end?", "true?", "false?", "class?", "while?", "nil?", "do?", "yield?", "return?", "unless?", "next?", "break?", "begin?"]
it_lexes_idents ["def!", "if!", "else!", "elsif!", "end!", "true!", "false!", "class!", "while!", "nil!", "do!", "yield!", "return!", "unless!", "next!", "break!", "begin!"]
Expand Down
4 changes: 4 additions & 0 deletions spec/compiler/parser/parser_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,10 @@ describe "Parser" do

it_parses "def foo\n1\nend\nif 1\nend", [Def.new("foo", ([] of Arg), 1.int32), If.new(1.int32)] of ASTNode

it_parses "1 as Bar", Cast.new(1.int32, "Bar".ident)
it_parses "foo as Bar", Cast.new("foo".call, "Bar".ident)
it_parses "foo.bar as Bar", Cast.new(Call.new("foo".call, "bar"), "Bar".ident)

it "keeps instance variables declared in def" do
node = Parser.parse("def foo; @x = 1; @y = 2; @x = 3; @z; end")
assert_type node, Def
Expand Down
48 changes: 48 additions & 0 deletions spec/compiler/type_inference/cast_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env bin/crystal --run
require "../../spec_helper"

describe "Type inference: cast" do
it "casts to same type is ok" do
assert_type("
1 as Int32
") { int32 }
end

it "casts to incompatible type gives error" do
assert_error "1 as Float64",
"can't cast Int32 to Float64"
end

it "casts to compatible type and use it" do
assert_type("
class Foo
end
class Bar < Foo
def coco
1
end
end
a = Foo.new || Bar.new
b = a as Bar
b.coco
") { int32 }
end

it "casts pointer of one type to another type" do
assert_type("
a = 1
p = pointerof(a)
p as Float64*
") { pointer_of(float64) }
end

it "casts pointer to another type" do
assert_type("
a = 1
p = pointerof(a)
p as String
") { types["String"] }
end
end
35 changes: 35 additions & 0 deletions src/compiler/crystal/ast.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2796,6 +2796,41 @@ module Crystal
end
end

class Cast < ASTNode
@obj :: ASTNode+
@to :: ASTNode+

def initialize(@obj, @to)
end

def obj=(@obj)
end

def obj
@obj
end

def to=(@to)
end

def to
@to
end

def accept_children(visitor)
@obj.accept visitor
@to.accept visitor
end

def ==(other : self)
@obj == other.obj && @to == other.to
end

def clone_without_location
Cast.new(@obj.clone, @to.clone)
end
end

# Ficticious node to represent primitives
class Primitive < ASTNode
@name :: Symbol
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/crystal/lexer.cr
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,8 @@ module Crystal
if next_char == 'i' && next_char == 'a' && next_char == 's'
return check_ident_or_keyword(:alias, start, start_column)
end
when 's'
return check_ident_or_keyword(:as, start, start_column)
end
scan_ident(start, start_column)
when 'b'
Expand Down
12 changes: 12 additions & 0 deletions src/compiler/crystal/parser.cr
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,14 @@ module Crystal
case @token.type
when :SPACE
next_token
when :IDENT
if @token.keyword?(:as)
next_token_skip_space
to = parse_single_type
atomic = Cast.new(atomic, to)
else
break
end
when :"."
next_token_skip_space_or_newline
check AtomicWithMethodCheck
Expand Down Expand Up @@ -1902,6 +1910,10 @@ module Crystal
end

def parse_call_args_space_consumed(check_plus_and_minus = true, allow_curly = false)
if @token.keyword?(:as)
return nil
end

case @token.type
when :"&"
return nil if @buffer.value.whitespace?
Expand Down
6 changes: 6 additions & 0 deletions src/compiler/crystal/transformer.cr
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,12 @@ module Crystal
node
end

def transform(node : Cast)
node.obj = node.obj.transform(self)
node.to = node.to.transform(self)
node
end

def transform(node : DeclareVar)
node.var = node.var.transform(self)
node.declared_type = node.declared_type.transform(self)
Expand Down
17 changes: 17 additions & 0 deletions src/compiler/crystal/type_inference.cr
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,23 @@ module Crystal
end
end

def end_visit(node : Cast)
obj_type = node.obj.type
to_type = node.to.type.instance_type

if obj_type.pointer?
resulting_type = to_type
else
resulting_type = obj_type.filter_by(to_type)
end

if resulting_type
node.type = resulting_type
else
node.raise "can't cast #{obj_type} to #{to_type}"
end
end

def end_visit(node : RespondsTo)
node.type = mod.bool
obj = node.obj
Expand Down

0 comments on commit 09207b4

Please sign in to comment.