Skip to content
This repository was archived by the owner on Feb 15, 2026. It is now read-only.
Merged
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
156 changes: 154 additions & 2 deletions spec/bindgen/parser/type_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ describe Bindgen::Parser::Type do
type.reference?.should be_false
type.pointer.should eq(1)
type.base_name.should eq("int")
type.full_name.should eq("int*")
type.full_name.should eq("int *")
Copy link
Owner

Choose a reason for hiding this comment

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

Where's the extra space coming from?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Pointers/references coming from the Clang parser have the extra space, so Parser::Type.parse now canonicalizes the typename given to it with Cpp::Typename, instead of using the typename verbatim. (This probably fixed a bug somewhere that considered T* and T * to be unequal types.)

end

it "recognizes 'int &'" do
Expand All @@ -71,7 +71,7 @@ describe Bindgen::Parser::Type do
type.reference?.should be_true
type.pointer.should eq(1)
type.base_name.should eq("int")
type.full_name.should eq("int&")
type.full_name.should eq("int &")
end

it "recognizes 'const int *'" do
Expand Down Expand Up @@ -101,9 +101,161 @@ describe Bindgen::Parser::Type do
type.full_name.should eq("const int **")
end

it "recognizes `Container<>`" do
type = parse("Container<>")
type.base_name.should eq("Container<>")
type.full_name.should eq("Container<>")
template = type.template
template.should_not be_nil
template.try(&.base_name).should eq("Container")
template.try(&.full_name).should eq("Container<>")
template.try(&.arguments.empty?).should be_true
end

it "recognizes `Container<int>`" do
type = parse("Container<int>")
type.base_name.should eq("Container<int>")
type.full_name.should eq("Container<int>")
template = type.template
template.should_not be_nil
template.try(&.base_name).should eq("Container")
template.try(&.full_name).should eq("Container<int>")
template.try(&.arguments.size).should eq(1)
template.try(&.arguments[0]).should eq(parse("int"))
end

it "recognizes `Container<const int *>`" do
type = parse("Container<const int *>")
type.base_name.should eq("Container<const int *>")
type.full_name.should eq("Container<const int *>")
template = type.template
template.should_not be_nil
template.try(&.base_name).should eq("Container")
template.try(&.full_name).should eq("Container<const int *>")
template.try(&.arguments.size).should eq(1)
template.try(&.arguments[0]).should eq(parse("const int *"))
end

it "recognizes `Container<int, bool>`" do
type = parse("Container<int, bool>")
type.base_name.should eq("Container<int, bool>")
type.full_name.should eq("Container<int, bool>")
template = type.template
template.should_not be_nil
template.try(&.base_name).should eq("Container")
template.try(&.full_name).should eq("Container<int, bool>")
template.try(&.arguments.size).should eq(2)
template.try(&.arguments[0]).should eq(parse("int"))
template.try(&.arguments[1]).should eq(parse("bool"))
end

it "recognizes `Container<Container<int> >`" do
type = parse("Container<Container<int> >")
type.base_name.should eq("Container<Container<int> >")
type.full_name.should eq("Container<Container<int> >")
template = type.template
template.should_not be_nil
template.try(&.base_name).should eq("Container")
template.try(&.full_name).should eq("Container<Container<int> >")
template.try(&.arguments.size).should eq(1)
template.try(&.arguments[0]).should eq(parse("Container<int>"))
end

it "recognizes `Container<Container<int>>`" do
type = parse("Container<Container<int>>")
type.base_name.should eq("Container<Container<int>>")
type.full_name.should eq("Container<Container<int>>")
template = type.template
template.should_not be_nil
template.try(&.base_name).should eq("Container")
template.try(&.full_name).should eq("Container<Container<int> >")
template.try(&.arguments.size).should eq(1)
template.try(&.arguments[0]).should eq(parse("Container<int>"))
end

it "recognizes `Container<const Container<int> &>`" do
type = parse("Container<const Container<int> &>")
type.base_name.should eq("Container<const Container<int> &>")
type.full_name.should eq("Container<const Container<int> &>")
template = type.template
template.should_not be_nil
template.try(&.base_name).should eq("Container")
template.try(&.full_name).should eq("Container<const Container<int> &>")
template.try(&.arguments.size).should eq(1)
template.try(&.arguments[0]).should eq(parse("const Container<int> &"))
end

it "supports pointer depth offset" do
parse("int", 1).pointer.should eq(1)
parse("int *", 1).pointer.should eq(2)

# don't add offset to template argument types
type = parse("Container<int>", 1)
type.pointer.should eq(1)
type.template.not_nil!.arguments[0].pointer.should eq(0)
end
end

describe "#substitute" do
it "replaces `T` with another type" do
parse("T").substitute("T", parse("int")).should eq(parse("int"))
parse("U").substitute("T", parse("int")).should eq(parse("U"))
end

it "combines const-ness" do
parse("const T").substitute("T", parse("int")).should eq(parse("const int"))
parse("T").substitute("T", parse("const int")).should eq(parse("const int"))
parse("const T").substitute("T", parse("const int")).should eq(parse("const int"))
end

it "combines references" do
parse("T").substitute("T", parse("int &")).should eq(parse("int &"))
parse("T &").substitute("T", parse("int")).should eq(parse("int &"))
parse("T &").substitute("T", parse("int &")).should eq(parse("int &"))
end

it "combines pointers" do
parse("T").substitute("T", parse("int *")).should eq(parse("int *"))
parse("T *").substitute("T", parse("int")).should eq(parse("int *"))
parse("T").substitute("T", parse("int **")).should eq(parse("int **"))
parse("T *").substitute("T", parse("int *")).should eq(parse("int **"))
parse("T **").substitute("T", parse("int")).should eq(parse("int **"))
end

it "combines mixtures of references and pointers" do
parse("T &").substitute("T", parse("int *")).should eq(parse("int *&"))
parse("T *").substitute("T", parse("int &")).should eq(parse("int *"))

parse("T").substitute("T", parse("int *&")).should eq(parse("int *&"))
parse("T *").substitute("T", parse("int *&")).should eq(parse("int **"))
parse("T &").substitute("T", parse("int *&")).should eq(parse("int *&"))

parse("T *&").substitute("T", parse("int")).should eq(parse("int *&"))
parse("T *&").substitute("T", parse("int *")).should eq(parse("int **&"))
parse("T *&").substitute("T", parse("int &")).should eq(parse("int *&"))
parse("T *&").substitute("T", parse("int *&")).should eq(parse("int **&"))
end

it "replaces `T` in template arguments" do
parse("Ref<T>").substitute("T", parse("int")).should eq(parse("Ref<int>"))
parse("Ref<Ref<T>, T>").substitute("T", parse("int")).should eq(parse("Ref<Ref<int>, int>"))

parse("Ref<T>").substitute("Ref", parse("int")).should eq(parse("Ref<T>"))
end

it "replaces `T` with another template type" do
parse("T").substitute("T", parse("U<int>")).should eq(parse("U<int>"))
parse("U<T, T>").substitute("T", parse("U<int>")).should eq(parse("U<U<int>, U<int> >"))
end

it "accepts multiple simultaneous replacements" do
substs = {"T" => parse("int"), "U" => parse("char")}
parse("T").substitute(substs).should eq(parse("int"))
parse("U").substitute(substs).should eq(parse("char"))
parse("T<T, U>").substitute(substs).should eq(parse("T<int, char>"))

substs = {"X" => parse("Y"), "Y" => parse("X")}
parse("T<X, Y>").substitute(substs).should eq(parse("T<Y, X>"))
end
end
end
22 changes: 22 additions & 0 deletions src/bindgen/parser/argument.cr
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,28 @@ module Bindgen
)
end

# Performs type substitution on the type part of this argument using the
# given *replacements*.
def substitute_type(replacements : Hash(String, Type)) : Argument
Argument.new(
name: @name,
type: substitute(replacements),
has_default: @has_default,
value: @value,
)
end

# Substitutes all uses of *name* on the type part of this argument with
# the given *type*.
def substitute_type(name : String, with type : Type) : Argument
Argument.new(
name: @name,
type: substitute(name, type),
has_default: @has_default,
value: @value,
)
end

# Checks if the type-part of this equals the type-part of *other*.
def type_equals?(other : Type)
{% for i in %i[base_name full_name const reference move builtin void pointer template] %}
Expand Down
60 changes: 51 additions & 9 deletions src/bindgen/parser/method.cr
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ module Bindgen
@name, @class_name, @return_type, @arguments, @first_default_argument = nil,
@access = AccessSpecifier::Public, @type = Type::MemberMethod,
@const = false, @virtual = false, @pure = false, @extern_c = false,
@builtin = false, @origin = nil, @crystal_name = nil
@builtin = false, @origin = nil, @crystal_name = nil, @binding_name = nil
)
end

Expand Down Expand Up @@ -429,7 +429,7 @@ module Bindgen
return unless @name == "operator++" || @name == "operator--"
return unless @arguments.size == 1 && @arguments[0].full_name == "int"

fixed = Method.new(
Method.new(
type: @type,
name: @name,
access: @access,
Expand All @@ -441,9 +441,8 @@ module Bindgen
arguments: [] of Argument,
return_type: @return_type,
crystal_name: crystal_name,
binding_name: binding_method_name,
)
fixed.binding_name = binding_method_name
fixed
end

# Mangled name for the C++ wrapper method name
Expand All @@ -469,7 +468,7 @@ module Bindgen
end

case self
when .constructor? then "_CONSTRUCT"
when .constructor? then "#{@name}_CONSTRUCT"
when .aggregate_constructor? then "_AGGREGATE"
when .copy_constructor? then "_COPY"
when .member_getter? then "#{@name}_GETTER"
Expand All @@ -478,7 +477,7 @@ module Bindgen
when .static_method? then "#{@name}_STATIC"
when .static_getter? then "#{@name}_STATIC_GETTER"
when .static_setter? then "#{@name}_STATIC_SETTER"
when .destructor? then "_DESTROY"
when .destructor? then "#{@name}_DESTROY"
else @name
end
end
Expand Down Expand Up @@ -630,11 +629,13 @@ module Bindgen
args << l.merge(r)
end

result = Method.new(
Method.new(
type: @type,
access: @access,
name: @name,
class_name: @class_name,
crystal_name: @crystal_name,
binding_name: @binding_name,
arguments: args,
first_default_argument: @first_default_argument,
return_type: @return_type,
Expand All @@ -643,9 +644,50 @@ module Bindgen
pure: @pure && other.pure?,
builtin: @builtin,
)
end

# Performs type substitution on the argument and return types of this
# method using the given *replacements*.
def substitute_type(replacements : Hash(String, Parser::Type)) : Method
args = arguments.map(&.substitute_type(replacements))
ret = @return_type.substitute(replacements)

result.crystal_name = @crystal_name
result
Method.new(
type: @type,
access: @access,
name: @name,
class_name: @class_name,
crystal_name: @crystal_name,
binding_name: @binding_name,
arguments: args,
first_default_argument: @first_default_argument,
return_type: ret,
const: @const,
virtual: @virtual,
pure: @pure,
)
end

# Substitutes all uses of *name* on the argument and return types of this
# method with the given *type*.
def substitute_type(name : String, with type : Type) : Method
args = arguments.map(&.substitute_type(name, type))
ret = @return_type.substitute(name, type)

Method.new(
type: @type,
access: @access,
name: @name,
class_name: @class_name,
crystal_name: @crystal_name,
binding_name: @binding_name,
arguments: args,
first_default_argument: @first_default_argument,
return_type: ret,
const: @const,
virtual: @virtual,
pure: @pure,
)
end
end
end
Expand Down
Loading