Skip to content

Commit

Permalink
Allow applying a CallConvention attribute to a lib declaration. Fixes #…
Browse files Browse the repository at this point in the history
Ary Borenszweig committed Nov 26, 2016

Verified

This commit was signed with the committer’s verified signature. The key has expired.
nomadium Miguel Landaeta
1 parent 2a273e6 commit afe8bb6
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
@@ -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
@@ -485,47 +485,6 @@ describe "Semantic: proc" do
)) { int32 }
end

it "specifies a call convention" do

This comment has been minimized.

Copy link
@Sija

Sija Nov 27, 2016

Contributor

Why remove tests for fun when they still can define their own CallConvention?

This comment has been minimized.

Copy link
@lbguilherme

lbguilherme Nov 27, 2016

Contributor

I think they just got moved. See spec/compiler/semantic/lib_spec.cr

This comment has been minimized.

Copy link
@Sija

Sija Nov 27, 2016

Contributor

@lbguilherme you're right, my eyes are playing tricks on me ;)

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"
63 changes: 43 additions & 20 deletions src/compiler/crystal/semantic/top_level_visitor.cr
Original file line number Diff line number Diff line change
@@ -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)

@@ -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
@@ -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
@@ -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)
@@ -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

1 change: 1 addition & 0 deletions src/compiler/crystal/types.cr
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit afe8bb6

Please sign in to comment.