Skip to content

Commit

Permalink
Showing 11 changed files with 99 additions and 13 deletions.
23 changes: 23 additions & 0 deletions spec/compiler/codegen/class_var_spec.cr
Original file line number Diff line number Diff line change
@@ -497,4 +497,27 @@ describe "Codegen: class var" do
Bar.var
)).to_i.should eq(2)
end

it "declares var as uninitialized and initializes it unsafely" do
run(%(
class Foo
@@x = uninitialized Int32
@@x = Foo.bar
def self.bar
if 1 == 2
@@x
else
10
end
end
def self.x
@@x
end
end
Foo.x
)).to_i.should eq(10)
end
end
16 changes: 16 additions & 0 deletions spec/compiler/codegen/global_spec.cr
Original file line number Diff line number Diff line change
@@ -58,4 +58,20 @@ describe "Code gen: global" do
$one.value
)).to_i.should eq(42)
end

it "declares var as uninitialized and initializes it unsafely" do
run(%(
def bar
if 1 == 2
$x
else
10
end
end
$x = uninitialized Int32
$x = bar
$x
)).to_i.should eq(10)
end
end
2 changes: 2 additions & 0 deletions spec/compiler/parser/parser_spec.cr
Original file line number Diff line number Diff line change
@@ -926,6 +926,8 @@ describe "Parser" do

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)
it_parses "@@a = uninitialized Foo", UninitializedVar.new("@@a".class_var, "Foo".path)
it_parses "$a = uninitialized Foo", UninitializedVar.new(Global.new("$a"), "Foo".path)

it_parses "()", NilLiteral.new
it_parses "(1; 2; 3)", [1.int32, 2.int32, 3.int32] of ASTNode
14 changes: 14 additions & 0 deletions spec/compiler/type_inference/class_var_spec.cr
Original file line number Diff line number Diff line change
@@ -417,4 +417,18 @@ describe "Type inference: class var" do
),
"class variable '@@x' of Bar is already defined as Int32 in Moo"
end

it "declares uninitialized (#2935)" do
assert_type(%(
class Foo
@@x = uninitialized Int32
def self.x
@@x
end
end
Foo.x
)) { int32 }
end
end
12 changes: 12 additions & 0 deletions spec/compiler/type_inference/global_spec.cr
Original file line number Diff line number Diff line change
@@ -694,4 +694,16 @@ describe "Global inference" do
),
"can't use Int as the type of a global variable yet, use a more specific type"
end

it "declares uninitialized (#2935)" do
assert_type(%(
$x = uninitialized Int32
def foo
$x
end
foo
)) { int32 }
end
end
3 changes: 3 additions & 0 deletions src/compiler/crystal/semantic/ast.cr
Original file line number Diff line number Diff line change
@@ -910,6 +910,9 @@ module Crystal
# The (optional) initial value of a class variable
property initializer : ClassVarInitializer?

# Is this variable "unsafe" (no need to check if it was initialized)?
property uninitialized = false

def kind
case name[0]
when '@'
Original file line number Diff line number Diff line change
@@ -59,9 +59,9 @@ module Crystal
had_class_var = false
end

self.class_var_and_const_being_typed.push class_var
self.class_var_and_const_being_typed.push class_var unless class_var.uninitialized
node.accept main_visitor
self.class_var_and_const_being_typed.pop
self.class_var_and_const_being_typed.pop unless class_var.uninitialized

unless had_class_var
main_visitor.undefined_class_variable(class_var, owner)
2 changes: 2 additions & 0 deletions src/compiler/crystal/semantic/main_visitor.cr
Original file line number Diff line number Diff line change
@@ -376,6 +376,8 @@ module Crystal
end

def first_time_accessing_meta_type_var?(var)
return false if var.uninitialized

if var.freeze_type
deps = var.dependencies?
# If no dependencies it's the case of a global for a regex literal.
6 changes: 5 additions & 1 deletion src/compiler/crystal/semantic/type_declaration_processor.cr
Original file line number Diff line number Diff line change
@@ -13,7 +13,8 @@ module Crystal
struct TypeDeclarationProcessor
record TypeDeclarationWithLocation,
type : TypeVar,
location : Location
location : Location,
uninitialized : Bool

# This captures an initialize info: it's related Def,
# and which instance variables are assigned. Useful
@@ -188,6 +189,9 @@ module Crystal
var = declare_meta_type_var(vars, owner, name, info.type.as(Type))
var.location = info.location

# Check if var is uninitialized
var.uninitialized = true if info.uninitialized

# If the variable is gueseed to be nilable because it is not initialized
# in all of the initialize methods, and the explicit type is not nilable,
# give an error right now
25 changes: 16 additions & 9 deletions src/compiler/crystal/semantic/type_declaration_visitor.cr
Original file line number Diff line number Diff line change
@@ -31,7 +31,7 @@ module Crystal
super(mod)

# The type of global variables. The last one wins.
@globals = {} of String => Type
@globals = {} of String => TypeDeclarationWithLocation

# The type of class variables. The last one wins.
# This is type => variables.
@@ -108,9 +108,9 @@ module Crystal
when InstanceVar
declare_instance_var(node, var)
when ClassVar
declare_class_var(node, var)
declare_class_var(node, var, false)
when Global
declare_global_var(node, var)
declare_global_var(node, var, false)
end

false
@@ -149,29 +149,29 @@ module Crystal
var_type = lookup_type(node.declared_type)
var_type = check_declare_var_type(node, var_type, "an instance variable")
owner_vars = @instance_vars[owner] ||= {} of String => TypeDeclarationWithLocation
type_decl = TypeDeclarationWithLocation.new(var_type.virtual_type, node.location.not_nil!)
type_decl = TypeDeclarationWithLocation.new(var_type.virtual_type, node.location.not_nil!, false)
owner_vars[var.name] = type_decl
end

def declare_instance_var_on_generic(owner, node, var)
# For generic types we must delay the type resolution
owner_vars = @instance_vars[owner] ||= {} of String => TypeDeclarationWithLocation
type_decl = TypeDeclarationWithLocation.new(node.declared_type, node.location.not_nil!)
type_decl = TypeDeclarationWithLocation.new(node.declared_type, node.location.not_nil!, false)
owner_vars[var.name] = type_decl
end

def declare_class_var(node, var)
def declare_class_var(node, var, uninitialized)
owner = class_var_owner(node)
var_type = lookup_type(node.declared_type)
var_type = check_declare_var_type(node, var_type, "a class variable")
owner_vars = @class_vars[owner] ||= {} of String => TypeDeclarationWithLocation
owner_vars[var.name] = TypeDeclarationWithLocation.new(var_type.virtual_type, node.location.not_nil!)
owner_vars[var.name] = TypeDeclarationWithLocation.new(var_type.virtual_type, node.location.not_nil!, uninitialized)
end

def declare_global_var(node, var)
def declare_global_var(node, var, uninitialized)
var_type = lookup_type(node.declared_type)
var_type = check_declare_var_type(node, var_type, "a global variable")
@globals[var.name] = var_type.virtual_type
@globals[var.name] = TypeDeclarationWithLocation.new(var_type.virtual_type, node.location.not_nil!, uninitialized)
end

def visit(node : Def)
@@ -207,6 +207,13 @@ module Crystal
end

def visit(node : UninitializedVar)
var = node.var
case var
when ClassVar
declare_class_var(node, var, true)
when Global
declare_global_var(node, var, true)
end
false
end

5 changes: 4 additions & 1 deletion src/compiler/crystal/syntax/parser.cr
Original file line number Diff line number Diff line change
@@ -350,7 +350,10 @@ module Crystal

push_def if needs_new_scope

if @token.keyword?(:uninitialized) && (atomic.is_a?(Var) || atomic.is_a?(InstanceVar))
if @token.keyword?(:uninitialized) && (
atomic.is_a?(Var) || atomic.is_a?(InstanceVar) ||
atomic.is_a?(ClassVar) || atomic.is_a?(Global)
)
push_var atomic
next_token_skip_space
type = parse_single_type

0 comments on commit 78990a0

Please sign in to comment.