Skip to content

Commit

Permalink
Allow annotating class and global variables. Fixes #1838
Browse files Browse the repository at this point in the history
  • Loading branch information
Ary Borenszweig committed Jan 20, 2016
1 parent a78fd01 commit d8ef2ce
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 36 deletions.
2 changes: 2 additions & 0 deletions spec/compiler/parser/parser_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,8 @@ describe "Parser" do
it_parses "@a :: Foo | Int32", TypeDeclaration.new("@a".instance_var, Union.new(["Foo".path, "Int32".path] of ASTNode))
it_parses "a : Foo", TypeDeclaration.new("a".var, "Foo".path)
it_parses "@a : Foo", TypeDeclaration.new("@a".instance_var, "Foo".path)
it_parses "@@a : Foo", TypeDeclaration.new("@@a".class_var, "Foo".path)
it_parses "$x : Foo", TypeDeclaration.new(Global.new("$x"), "Foo".path)

it_parses "a = uninitialized Foo; a", [UninitializedVar.new("a".var, "Foo".path), "a".var]
it_parses "@a = uninitialized Foo", UninitializedVar.new("@a".instance_var, "Foo".path)
Expand Down
13 changes: 0 additions & 13 deletions spec/compiler/type_inference/class_var_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,6 @@ describe "Type inference: class var" do
") { int32 }
end

it "types class var as nil if assigned for the first time inside method" do
assert_type("
class Foo
def self.foo
@@foo = 1
@@foo
end
end
Foo.foo
") { |mod| union_of(mod.nil, int32) }
end

it "types class var of program" do
assert_type("
@@foo = 1
Expand Down
46 changes: 46 additions & 0 deletions spec/compiler/type_inference/type_declaration_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,52 @@ describe "Type inference: type declaration" do
"can't declare variable of generic non-instantiated type Foo"
end

it "declares global variable" do
assert_error %(
$x : Int32
$x = true
),
"type must be Int32, not Bool"
end

it "declares global variable and reads it (nilable)" do
assert_error %(
$x : Int32
$x
),
"type must be Int32, not Nil"
end

it "declares class variable" do
assert_error %(
class Foo
@@x : Int32
def self.x=(x)
@@x = x
end
end
Foo.x = true
),
"type must be Int32, not Bool"
end

it "declares class variable (2)" do
assert_error %(
class Foo
@@x : Int32
def self.x
@@x
end
end
Foo.x
),
"type must be Int32, not Nil"
end

# TODO: remove these after 0.11

it "declares as uninitialized" do
Expand Down
26 changes: 21 additions & 5 deletions src/compiler/crystal/semantic/type_visitor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,24 @@ module Crystal
else
node.raise "can only declare instance variables of a non-generic class, not a #{type.type_desc} (#{type})"
end
when ClassVar
class_var = lookup_class_var(var, bind_to_nil_if_non_existent: false)

node.declared_type.accept self
var_type = check_declare_var_type node

class_var.freeze_type = var_type
when Global
global_var = mod.global_vars[var.name]?
unless global_var
global_var = Var.new(var.name)
mod.global_vars[var.name] = global_var
end

node.declared_type.accept self
var_type = check_declare_var_type node

global_var.freeze_type = var_type
end

node.type = @mod.nil
Expand Down Expand Up @@ -282,9 +300,9 @@ module Crystal
var = mod.global_vars[node.name]?
unless var
var = Var.new(node.name)
var.bind_to mod.nil_var
mod.global_vars[node.name] = var
end
var.bind_to mod.nil_var unless var.dependencies?
node.bind_to var
var
end
Expand Down Expand Up @@ -371,10 +389,8 @@ module Crystal

class_var_owner = scope as ClassVarContainer

bind_to_nil = bind_to_nil_if_non_existent && !class_var_owner.has_class_var?(node.name)

var = class_var_owner.lookup_class_var node.name
var.bind_to mod.nil_var if bind_to_nil
var.bind_to mod.nil_var if bind_to_nil_if_non_existent && !var.dependencies?

node.owner = class_var_owner
node.var = var
Expand Down Expand Up @@ -541,7 +557,7 @@ module Crystal
end

def type_assign(target : ClassVar, value, node)
var = lookup_class_var target, !!@typed_def
var = lookup_class_var target, bind_to_nil_if_non_existent: false
check_valid_attributes var, ValidClassVarAttributes, "class variable"

value.accept self
Expand Down
42 changes: 25 additions & 17 deletions src/compiler/crystal/syntax/parser.cr
Original file line number Diff line number Diff line change
Expand Up @@ -854,8 +854,7 @@ module Crystal
when :SYMBOL
node_and_next_token SymbolLiteral.new(@token.value.to_s)
when :GLOBAL
@wants_regex = false
node_and_next_token Global.new(@token.value.to_s)
new_node_check_type_declaration Global
when :"$~", :"$?"
location = @token.location
var = Var.new(@token.to_s).at(location)
Expand Down Expand Up @@ -1016,30 +1015,39 @@ module Crystal
when :CONST
parse_ident_or_literal
when :INSTANCE_VAR
name = @token.value.to_s
add_instance_var name
ivar = InstanceVar.new(name).at(@token.location)
ivar.end_location = token_end_location
@wants_regex = false
next_token_skip_space

if (@token.type == :"::") || (@no_type_declaration == 0 && @token.type == :":")
next_token_skip_space
ivar_type = parse_single_type
TypeDeclaration.new(ivar, ivar_type).at(ivar.location)
else
ivar
new_node_check_type_declaration(InstanceVar) do |name|
add_instance_var(name)
end
when :CLASS_VAR
@wants_regex = false
node_and_next_token ClassVar.new(@token.value.to_s)
new_node_check_type_declaration ClassVar
when :UNDERSCORE
node_and_next_token Underscore.new
else
unexpected_token_in_atomic
end
end

def new_node_check_type_declaration(klass)
new_node_check_type_declaration(klass) { }
end

def new_node_check_type_declaration(klass)
name = @token.value.to_s
yield name
var = klass.new(name).at(@token.location)
var.end_location = token_end_location
@wants_regex = false
next_token_skip_space

if (@token.type == :"::") || (@no_type_declaration == 0 && @token.type == :":")
next_token_skip_space
var_type = parse_single_type
TypeDeclaration.new(var, var_type).at(var.location)
else
var
end
end

def parse_ident_or_literal
ident = parse_ident
skip_space
Expand Down
2 changes: 1 addition & 1 deletion src/concurrent/scheduler.cr
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class Scheduler
if runnable = @@runnables.pop?
runnable.resume
else
@@loop_fiber.resume
@@loop_fiber.not_nil!.resume
end
nil
end
Expand Down

0 comments on commit d8ef2ce

Please sign in to comment.