Skip to content

Commit

Permalink
Allow applying a CallConvention attribute to a lib declaration. Fixes c…
Browse files Browse the repository at this point in the history
  • Loading branch information
Ary Borenszweig authored and firejox committed Dec 11, 2016
1 parent 2a273e6 commit c2e1b54
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 61 deletions.
52 changes: 52 additions & 0 deletions spec/compiler/semantic/lib_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -875,4 +875,56 @@ describe "Semantic: lib" do
),
"fun redefinition with different signature"
end

it "specifies a call convention" do
result = semantic(%(
lib LibFoo
@[CallConvention("X86_StdCall")]
fun foo : Int32
end
))
foo = result.program.types["LibFoo"].lookup_first_def("foo", nil).as(External)
foo.call_convention.should eq(LLVM::CallConvention::X86_StdCall)
end

it "specifies a call convention to a lib" do
result = semantic(%(
@[CallConvention("X86_StdCall")]
lib LibFoo
fun foo : Int32
end
))
foo = result.program.types["LibFoo"].lookup_first_def("foo", nil).as(External)
foo.call_convention.should eq(LLVM::CallConvention::X86_StdCall)
end

it "errors if wrong number of arguments for CallConvention" do
assert_error %(
lib LibFoo
@[CallConvention("X86_StdCall", "bar")]
fun foo : Int32
end
),
"wrong number of arguments for attribute CallConvention (given 2, expected 1)"
end

it "errors if CallConvention argument is not a string" do
assert_error %(
lib LibFoo
@[CallConvention(1)]
fun foo : Int32
end
),
"argument to CallConvention must be a string"
end

it "errors if CallConvention argument is not a valid string" do
assert_error %(
lib LibFoo
@[CallConvention("foo")]
fun foo : Int32
end
),
"invalid call convention. Valid values are #{LLVM::CallConvention.values.join ", "}"
end
end
41 changes: 0 additions & 41 deletions spec/compiler/semantic/proc_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -485,47 +485,6 @@ describe "Semantic: proc" do
)) { int32 }
end

it "specifies a call convention" do
result = semantic(%(
lib LibFoo
@[CallConvention("X86_StdCall")]
fun foo : Int32
end
))
foo = result.program.types["LibFoo"].lookup_first_def("foo", nil).as(External)
foo.call_convention.should eq(LLVM::CallConvention::X86_StdCall)
end

it "errors if wrong number of arguments for CallConvention" do
assert_error %(
lib LibFoo
@[CallConvention("X86_StdCall", "bar")]
fun foo : Int32
end
),
"wrong number of arguments for attribute CallConvention (given 2, expected 1)"
end

it "errors if CallConvention argument is not a string" do
assert_error %(
lib LibFoo
@[CallConvention(1)]
fun foo : Int32
end
),
"argument to CallConvention must be a string"
end

it "errors if CallConvention argument is not a valid string" do
assert_error %(
lib LibFoo
@[CallConvention("foo")]
fun foo : Int32
end
),
"invalid call convention. Valid values are #{LLVM::CallConvention.values.join ", "}"
end

it "types proc literal with a type that was never instantiated" do
assert_type(%(
require "prelude"
Expand Down
63 changes: 43 additions & 20 deletions src/compiler/crystal/semantic/top_level_visitor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor
def visit(node : LibDef)
check_outside_exp node, "declare lib"

link_attributes = process_link_attributes
link_attributes, call_convention = process_lib_attributes

scope = current_type_scope(node)

Expand All @@ -371,6 +371,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor

type.private = true if node.visibility.private?
type.add_link_attributes(link_attributes)
type.call_convention = call_convention if call_convention

pushing_type(type) do
@in_lib = true
Expand Down Expand Up @@ -679,6 +680,12 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor
node.doc ||= attributes_doc()
check_ditto node

# Copy call convention from lib, if any
scope = current_type
if !call_convention && scope.is_a?(LibType)
call_convention = scope.call_convention
end

# We fill the arguments and return type in TypeDeclarationVisitor
external = External.new(node.name, ([] of Arg), node.body, node.real_name).at(node)
external.doc = node.doc
Expand Down Expand Up @@ -750,12 +757,24 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor
false
end

def process_link_attributes
def process_lib_attributes
attributes = @attributes
return unless attributes
return {nil, nil} unless attributes

@attributes = nil
attributes.map { |attr| LinkAttribute.from(attr) }
link_attributes = [] of LinkAttribute
call_convention = nil
attributes.each do |attr|
case attr.name
when "Link"
link_attributes << LinkAttribute.from(attr)
when "CallConvention"
call_convention = parse_call_convention(attr, call_convention)
else
attr.raise "illegal attribute for lib, valid attributes are: Link, CallConvention"
end
end
{link_attributes, call_convention}
end

def include_in(current_type, node, kind)
Expand Down Expand Up @@ -813,28 +832,32 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor
attributes.reject! do |attr|
next false unless attr.name == "CallConvention"

if call_convention
attr.raise "call convention already specified"
end
call_convention = parse_call_convention(attr, call_convention)
true
end

if attr.args.size != 1
attr.wrong_number_of_arguments "attribute CallConvention", attr.args.size, 1
end
call_convention
end

call_convention_node = attr.args.first
unless call_convention_node.is_a?(StringLiteral)
call_convention_node.raise "argument to CallConvention must be a string"
end
def parse_call_convention(attr, call_convention)
if call_convention
attr.raise "call convention already specified"
end

value = call_convention_node.value
call_convention = LLVM::CallConvention.parse?(value)
unless call_convention
call_convention_node.raise "invalid call convention. Valid values are #{LLVM::CallConvention.values.join ", "}"
end
if attr.args.size != 1
attr.wrong_number_of_arguments "attribute CallConvention", attr.args.size, 1
end

true
call_convention_node = attr.args.first
unless call_convention_node.is_a?(StringLiteral)
call_convention_node.raise "argument to CallConvention must be a string"
end

value = call_convention_node.value
call_convention = LLVM::CallConvention.parse?(value)
unless call_convention
call_convention_node.raise "invalid call convention. Valid values are #{LLVM::CallConvention.values.join ", "}"
end
call_convention
end

Expand Down
1 change: 1 addition & 0 deletions src/compiler/crystal/types.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2167,6 +2167,7 @@ module Crystal
class LibType < ModuleType
getter link_attributes : Array(LinkAttribute)?
property? used = false
property call_convention : LLVM::CallConvention?

def add_link_attributes(link_attributes)
if link_attributes
Expand Down

0 comments on commit c2e1b54

Please sign in to comment.