Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions spec/compiler/semantic/reference_storage_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ require "../../spec_helper"
describe "Semantic: ReferenceStorage" do
it "errors if T is a struct type" do
assert_error <<-CRYSTAL, "Can't instantiate ReferenceStorage(T) with T = Foo (T must be a reference type)"
@[Primitive(:reference_storage_type)]
struct ReferenceStorage(T) < Value
end

struct Foo
@x = 1
end
Expand All @@ -13,12 +17,20 @@ describe "Semantic: ReferenceStorage" do

it "errors if T is a value type" do
assert_error <<-CRYSTAL, "Can't instantiate ReferenceStorage(T) with T = Int32 (T must be a reference type)"
@[Primitive(:reference_storage_type)]
struct ReferenceStorage(T) < Value
end

ReferenceStorage(Int32)
CRYSTAL
end

it "errors if T is a union type" do
assert_error <<-CRYSTAL, "Can't instantiate ReferenceStorage(T) with T = (Bar | Foo) (T must be a reference type)"
@[Primitive(:reference_storage_type)]
struct ReferenceStorage(T) < Value
end

class Foo
end

Expand All @@ -31,10 +43,30 @@ describe "Semantic: ReferenceStorage" do

it "errors if T is a nilable type" do
assert_error <<-CRYSTAL, "Can't instantiate ReferenceStorage(T) with T = (Foo | Nil) (T must be a reference type)"
@[Primitive(:reference_storage_type)]
struct ReferenceStorage(T) < Value
end

class Foo
end

ReferenceStorage(Foo?)
CRYSTAL
end

it "allows a different name" do
assert_type(<<-CRYSTAL) { types["Foo"].metaclass }
@[Primitive(:reference_storage_type)]
struct MyRef(U) < Value
def u
U
end
end

class Foo
end

MyRef(Foo).new.u
CRYSTAL
end
end
6 changes: 1 addition & 5 deletions src/compiler/crystal/program.cr
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,6 @@ module Crystal
types["Struct"] = struct_t = @struct_t = NonGenericClassType.new self, self, "Struct", value
abstract_value_type(struct_t)

types["ReferenceStorage"] = @reference_storage = reference_storage = GenericReferenceStorageType.new self, self, "ReferenceStorage", value, ["T"]
reference_storage.declare_instance_var("@type_id", int32)
reference_storage.can_be_stored = false

types["Enumerable"] = @enumerable = GenericModuleType.new self, self, "Enumerable", ["T"]
types["Indexable"] = @indexable = GenericModuleType.new self, self, "Indexable", ["T"]

Expand Down Expand Up @@ -497,7 +493,7 @@ module Crystal

{% for name in %w(object no_return value number reference void nil bool char int int8 int16 int32 int64 int128
uint8 uint16 uint32 uint64 uint128 float float32 float64 string symbol pointer enumerable indexable
array static_array reference_storage exception tuple named_tuple proc union enum range regex crystal
array static_array exception tuple named_tuple proc union enum range regex crystal
packed_annotation thread_local_annotation no_inline_annotation
always_inline_annotation naked_annotation returns_twice_annotation
raises_annotation primitive_annotation call_convention_annotation
Expand Down
10 changes: 10 additions & 0 deletions src/compiler/crystal/semantic/semantic_visitor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,16 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor
when @program.experimental_annotation
# ditto DeprecatedAnnotation
ExperimentalAnnotation.from(ann)
when @program.primitive_annotation
# there isn't a PrimitiveAnnotation type yet, so we validate right here
if ann.args.size != 1
ann.raise "expected Primitive annotation to have one argument"
end

arg = ann.args.first
unless arg.is_a?(SymbolLiteral)
arg.raise "expected Primitive argument to be a symbol literal"
end
Comment on lines +525 to +534
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Is this an integral part of this patch? It looks like a tangential issue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this would probably prevent us from "exploiting" @[Primitive] in other places in a backward-compatible manner

end
end

Expand Down
67 changes: 47 additions & 20 deletions src/compiler/crystal/semantic/top_level_visitor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,29 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor

@last_doc : String?

# special types recognized for `@[Primitive]`
private enum PrimitiveType
ReferenceStorageType
end

def visit(node : ClassDef)
check_outside_exp node, "declare class"

scope, name, type = lookup_type_def(node)

annotations = read_annotations

special_type = nil
process_annotations(annotations) do |annotation_type, ann|
case annotation_type
when @program.primitive_annotation
arg = ann.args.first.as(SymbolLiteral)
unless special_type = PrimitiveType.parse?(arg.value)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick(style): I found it hard to recognize that this line assigns to special_type. I was looking for an assignment and didn't see this at first because it starts with an unless.
I'd suggest to split this into two lines to improve readability.

Suggested change
unless special_type = PrimitiveType.parse?(arg.value)
special_type = PrimitiveType.parse?(arg.value)
unless special_type

A trailing unless would also be possible.

arg.raise "BUG: Unknown primitive type #{arg.value.inspect}"
end
end
end

created_new_type = false

if type
Expand All @@ -70,14 +86,30 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor
end
else
created_new_type = true
if type_vars = node.type_vars
type = GenericClassType.new @program, scope, name, nil, type_vars, false
type.splat_index = node.splat_index
else
type = NonGenericClassType.new @program, scope, name, nil, false
case special_type
in Nil
if type_vars = node.type_vars
type = GenericClassType.new @program, scope, name, nil, type_vars, false
type.splat_index = node.splat_index
else
type = NonGenericClassType.new @program, scope, name, nil, false
end
type.abstract = node.abstract?
type.struct = node.struct?
in .reference_storage_type?
unless type_vars = node.type_vars
node.raise "BUG: Expected reference_storage_type to be a generic struct"
end
unless type_vars.size == 1
node.raise "BUG: Expected reference_storage_type to have a single generic type parameter"
end
if node.splat_index
node.raise "BUG: Expected reference_storage_type to have no splat parameter"
end
type = GenericReferenceStorageType.new @program, scope, name, @program.value, type_vars
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chore: Can we also validate node.struct? == true and node.abstract? == false?
abstract class ReferenceStorage(T) should be invalid.

type.declare_instance_var("@type_id", @program.int32)
type.can_be_stored = false
end
type.abstract = node.abstract?
type.struct = node.struct?
end

type.private = true if node.visibility.private?
Expand Down Expand Up @@ -133,6 +165,10 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor
if superclass.struct? && !superclass.abstract?
node.raise "can't extend non-abstract struct #{superclass}"
end

if type.is_a?(GenericReferenceStorageType) && superclass != @program.value
node.raise "BUG: Expected reference_storage_type to inherit from Value"
end
end

if created_new_type
Expand Down Expand Up @@ -375,7 +411,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor

process_def_annotations(node, annotations) do |annotation_type, ann|
if annotation_type == @program.primitive_annotation
process_primitive_annotation(node, ann)
process_def_primitive_annotation(node, ann)
end

node.add_annotation(annotation_type, ann)
Expand Down Expand Up @@ -460,17 +496,8 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor
false
end

private def process_primitive_annotation(node, ann)
if ann.args.size != 1
ann.raise "expected Primitive annotation to have one argument"
end

arg = ann.args.first
unless arg.is_a?(SymbolLiteral)
arg.raise "expected Primitive argument to be a symbol literal"
end

value = arg.value
private def process_def_primitive_annotation(node, ann)
value = ann.args.first.as(SymbolLiteral).value

primitive = Primitive.new(value)
primitive.location = node.location
Expand Down Expand Up @@ -924,7 +951,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor
if annotation_type == @program.call_convention_annotation
call_convention = parse_call_convention(ann, call_convention)
elsif annotation_type == @program.primitive_annotation
process_primitive_annotation(external, ann)
process_def_primitive_annotation(external, ann)
else
ann.raise "funs can only be annotated with: NoInline, AlwaysInline, Naked, ReturnsTwice, Raises, CallConvention"
end
Expand Down
15 changes: 8 additions & 7 deletions src/compiler/crystal/types.cr
Original file line number Diff line number Diff line change
Expand Up @@ -550,12 +550,12 @@ module Crystal
def allows_instance_vars?
case self
when program.object, program.value, program.struct,
program.reference, program.class_type, program.reference_storage,
program.reference, program.class_type,
program.number, program.int, program.float,
program.tuple, program.named_tuple,
program.pointer, program.static_array,
program.union, program.enum, program.proc,
PrimitiveType
PrimitiveType, GenericReferenceStorageType
false
else
true
Expand Down Expand Up @@ -2647,24 +2647,25 @@ module Crystal
@struct = true

def new_generic_instance(program, generic_type, type_vars)
t = type_vars["T"].type
type_param = self.type_vars.first
t = type_vars[type_param].type

unless t.is_a?(TypeParameter) || (t.class? && !t.struct?)
raise TypeException.new "Can't instantiate ReferenceStorage(T) with T = #{t} (T must be a reference type)"
raise TypeException.new "Can't instantiate ReferenceStorage(#{type_param}) with #{type_param} = #{t} (#{type_param} must be a reference type)"
end

ReferenceStorageType.new program, t
ReferenceStorageType.new program, t, self, type_param
end
end

class ReferenceStorageType < GenericClassInstanceType
getter reference_type : Type

def initialize(program, @reference_type)
def initialize(program, @reference_type, generic_type, type_param)
t_var = Var.new("T", @reference_type)
t_var.bind_to t_var

super(program, program.reference_storage, program.struct, {"T" => t_var} of String => ASTNode)
super(program, generic_type, program.value, {type_param => t_var} of String => ASTNode)
end
end

Expand Down
3 changes: 2 additions & 1 deletion src/reference_storage.cr
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
# WARNING: `ReferenceStorage` is unsuitable when instances of `T` require more
# than `instance_sizeof(T)` bytes, such as `String` and `Log::Metadata`.
@[Experimental("This type's API is still under development. Join the discussion about custom reference allocation at [#13481](https://github.com/crystal-lang/crystal/issues/13481).")]
struct ReferenceStorage(T)
@[Primitive(:reference_storage_type)]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Use camel case for primitive types, equivalent to type names.

Suggested change
@[Primitive(:reference_storage_type)]
@[Primitive(:ReferenceStorageType)]

This is a minor thing. Either spelling should work because it's backed by Enum.parse. But I think this makes a bit more sense for type names.

struct ReferenceStorage(T) < Value
private def initialize
end

Expand Down