Class: RubyBindgen::Generators::Rice

Inherits:
Generator
  • Object
show all
Defined in:
lib/ruby-bindgen/generators/rice/rice.rb,
lib/ruby-bindgen/generators/rice/rice.rb,
lib/ruby-bindgen/generators/rice/type_index.rb,
lib/ruby-bindgen/generators/rice/type_speller.rb,
lib/ruby-bindgen/generators/rice/function_pointer.rb,
lib/ruby-bindgen/generators/rice/signature_builder.rb,
lib/ruby-bindgen/generators/rice/template_resolver.rb,
lib/ruby-bindgen/generators/rice/iterator_collector.rb,
lib/ruby-bindgen/generators/rice/reference_qualifier.rb

Defined Under Namespace

Classes: FunctionPointer, IteratorCollector, ReferenceQualifier, SignatureBuilder, TemplateResolver, TypeIndex, TypeSpeller

Constant Summary collapse

CURSOR_LITERALS =
[:cursor_integer_literal, :cursor_floating_literal,
:cursor_imaginary_literal, :cursor_string_literal,
:cursor_character_literal, :cursor_cxx_bool_literal_expr,
:cursor_cxx_null_ptr_literal_expr, :cursor_fixed_point_literal,
:cursor_unary_operator]
CURSOR_CLASSES =
[:cursor_class_decl, :cursor_class_template, :cursor_struct]
OPERATOR_MAPPINGS =

Fundamental types that should use ArgBuffer/ReturnBuffer when passed/returned as pointers Mapping of C++ operators to Ruby method names per Rice documentation Keys use cursor spelling form (e.g., ‘operator()’) so they share the same key namespace as user rename_methods. Values can be:

- String: direct mapping
- Proc: called with cursor to determine mapping (for arity-dependent operators)
RubyBindgen::NameMapper.new([
  # Assignment operators - not overridable in Ruby
  ['operator=', 'assign'],
  ['operator+=', 'assign_plus'],
  ['operator-=', 'assign_minus'],
  ['operator*=', 'assign_multiply'],
  ['operator/=', 'assign_divide'],
  ['operator%=', 'assign_modulus'],

  # Bitwise assignment operators - not overridable in Ruby
  ['operator&=', 'assign_and'],
  ['operator|=', 'assign_or'],
  ['operator^=', 'assign_xor'],
  ['operator<<=', 'assign_left_shift'],
  ['operator>>=', 'assign_right_shift'],

  # Logical operators - && and || not overridable in Ruby
  ['operator&&', 'logical_and'],
  ['operator||', 'logical_or'],

  # Function call operator
  ['operator()', 'call'],

  # Member access through pointer (arrow operator)
  ['operator->', 'arrow'],

  # Increment/decrement - arity-dependent (prefix=0 args, postfix=1 arg)
  ['operator++', ->(cursor) { cursor.type.args_size == 0 ? 'increment' : 'increment_post' }],
  ['operator--', ->(cursor) { cursor.type.args_size == 0 ? 'decrement' : 'decrement_post' }],

  # Dereference vs multiply - arity-dependent (unary=0 args, binary=1 arg)
  ['operator*', ->(cursor) { cursor.type.args_size == 0 ? 'dereference' : '*' }],

  # Unary plus/minus vs binary - arity-dependent
  # Ruby uses +@ and -@ for unary operators, + and - for binary
  # Member: unary=0 args, binary=1 arg
  # Non-member: unary=1 arg, binary=2 args (but we check member arity here)
  ['operator+', ->(cursor) { cursor.type.args_size == 0 ? '+@' : '+' }],
  ['operator-', ->(cursor) { cursor.type.args_size == 0 ? '-@' : '-' }],

  # Pass-through operators - Ruby supports these directly
  ['operator/', '/'],
  ['operator%', '%'],
  ['operator&', '&'],
  ['operator|', '|'],
  ['operator^', '^'],
  ['operator~', '~'],
  ['operator<<', '<<'],
  ['operator>>', '>>'],
  ['operator==', '=='],
  ['operator!=', '!='],
  ['operator<', '<'],
  ['operator>', '>'],
  ['operator<=', '<='],
  ['operator>=', '>='],
  ['operator!', '!'],
  ['operator[]', '[]'],
]).freeze
CONVERSION_TYPE_MAPPINGS =

Mapping of C++ type names to Ruby conversion method suffixes

RubyBindgen::NameMapper.new([
  # Standard integer types
  ['int', 'i'],
  ['long', 'l'],
  ['long long', 'i64'],
  ['short', 'i16'],
  ['unsigned int', 'u'],
  ['unsigned long', 'ul'],
  ['unsigned long long', 'u64'],
  ['unsigned short', 'u16'],
  # Fixed-width integer types
  ['int8_t', 'i8'],
  ['int16_t', 'i16'],
  ['int32_t', 'i32'],
  ['int64_t', 'i64'],
  ['uint8_t', 'u8'],
  ['uint16_t', 'u16'],
  ['uint32_t', 'u32'],
  ['uint64_t', 'u64'],
  # Size type (platform-independent)
  ['size_t', 'size'],
  # Floating point types
  ['float', 'f32'],
  ['double', 'f'],
  ['long double', 'ld'],
  # Other types
  ['bool', 'bool'],
  ['std::string', 's'],
  ['std::__cxx11::basic_string<char>', 's'],
  ['std::basic_string<char>', 's'],
  ['basic_string<char>', 's'],
  ['char *', 's'],
  ['const char *', 's'],
]).freeze
RICE_NATIVE_TYPES =
std

types that Rice converts to native Ruby types (no Rice wrapper exists).

string→String, string_view→String, complex→Complex, monostate→NilClass, tuple→Array. Checked by declaration spelling (not qualified_name) to avoid inline namespace issues (e.g., std::__cxx11::basic_string on libstdc++).

Set.new(%w[basic_string basic_string_view complex monostate tuple]).freeze
FUNDAMENTAL_TYPES =
[
  :type_void, :type_bool,
  :type_char_u, :type_uchar, :type_char16, :type_char32, :type_char_s,
  :type_schar, :type_wchar,
  :type_short, :type_ushort,
  :type_int, :type_uint,
  :type_long, :type_ulong,
  :type_longlong, :type_ulonglong,
  :type_int128, :type_uint128,
  :type_float, :type_double, :type_longdouble,
  :type_float128, :type_float16,
  :type_nullptr
].freeze
ITERATOR_METHODS =
["begin", "end", "cbegin", "cend", "rbegin", "rend", "crbegin", "crend"].freeze

Instance Attribute Summary

Attributes inherited from Generator

#config, #inputter, #outputter

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Generator

#project, #render_template, #translation_unit_file?

Constructor Details

#initialize(inputter, outputter, config) ⇒ Rice

Create the main generator plus the extracted helpers that own naming, qualification, template resolution, and signature construction.

Raises:

  • (ArgumentError)


161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 161

def initialize(inputter, outputter, config)
  super(inputter, outputter, config)
  @include_header = config[:include]
  @init_names = Hash.new
  @namespaces = Set.new
  @classes = Hash.new  # Maps cruby_name -> C++ type for Data_Type<T> declarations
  @reference_qualifier = ReferenceQualifier.new
  @type_index = TypeIndex.new
  @type_speller = TypeSpeller.new(type_index: @type_index)
  @auto_generated_bases = Set.new
  @symbols = RubyBindgen::Symbols.new(config[:symbols] || {})
  @export_macros = config[:export_macros] || []
  @version_check = config[:version_check]
  raise ArgumentError, "version_check is required when symbols.versions is non-empty" if @symbols.has_versions? && !@version_check

  # Build naming tables: merge operator defaults with user config
  symbols_config = config[:symbols] || {}
  rename_types = RubyBindgen::NameMapper.from_config(symbols_config[:rename_types] || [])
  user_rename_methods = RubyBindgen::NameMapper.from_config(symbols_config[:rename_methods] || [])
  rename_methods = OPERATOR_MAPPINGS.merge(user_rename_methods)
  @namer = RubyBindgen::Namer.new(rename_types, rename_methods, CONVERSION_TYPE_MAPPINGS)
  @template_resolver = TemplateResolver.new(reference_qualifier: @reference_qualifier,
                                            type_speller: @type_speller,
                                            namer: @namer)
  @signature_builder = SignatureBuilder.new(type_speller: @type_speller,
                                            reference_qualifier: @reference_qualifier,
                                            cursor_literals: CURSOR_LITERALS,
                                            fundamental_types: FUNDAMENTAL_TYPES)
  # Non-member operators grouped by target class cruby_name
  @non_member_operators = Hash.new { |h, k| h[k] = [] }
  @iterator_collector = IteratorCollector.new
end

Class Method Details

.template_dirObject

Directory containing the ERB templates used by the Rice generator.



155
156
157
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 155

def self.template_dir
  __dir__
end

Instance Method Details

#add_indentation(content, indentation) ⇒ Object

Add left padding to non-blank lines while preserving existing blank lines.



1545
1546
1547
1548
1549
1550
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1545

def add_indentation(content, indentation)
  content.lines.map do |line|
    # Don't add indentation to blank lines
    line.strip.empty? ? line : " " * indentation + line
  end.join
end

#auto_generate_base_class(base_ref, base_spelling, under, recursive: true) ⇒ Object

Auto-generate a base class definition when no typedef exists for it. When recursive: true, also generates any base classes of the base class.



1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1387

def auto_generate_base_class(base_ref, base_spelling, under, recursive: true)
  base_template_ref = base_ref.find_first_by_kind(false, :cursor_template_ref)
  return "" unless base_template_ref

  base_declaration = base_ref.type.declaration
  unless base_declaration.kind == :cursor_no_decl_found
    specialized_template = base_declaration.specialized_template
    return "" if specialized_template.kind == :cursor_class_template_partial_specialization
  end

  base_template = base_template_ref.referenced
  return "" if base_template.location.in_system_header?
  return "" if base_template.kind == :cursor_class_template_partial_specialization
  # Skip base templates from included files — their own output handles registration
  return "" unless translation_unit_file?(base_template)
  base_template_arguments_text = @template_resolver.template_argument_list_text(base_spelling)
  return "" unless base_template_arguments_text

  base_template_arguments = @template_resolver.template_argument_texts(base_template_arguments_text)
  return "" if base_template_arguments.empty?

  result = ""
  base_base_spelling = nil

  # Check if this base class has its own base that needs auto-generation
  if recursive
    base_base_ref = base_template.find_first_by_kind(false, :cursor_cxx_base_specifier)
    if base_base_ref
      base_base_template_ref = base_base_ref.find_first_by_kind(false, :cursor_template_ref)
      if base_base_template_ref
        # Build substitution map from template params to actual values
        template_params = @template_resolver.template_parameters(base_template).map(&:spelling)
        template_arg_values = base_template_arguments
        subs = template_params.each_with_index.to_h { |param, i| [param, template_arg_values[i]] }

        # Substitute template parameters with actual values
        base_base_spelling = @template_resolver.resolve_base_specifier_spelling(base_base_ref, substitutions: subs)

        if base_base_spelling && !@type_index.typedef_for(base_base_spelling) && !@auto_generated_bases.include?(base_base_spelling)
          result = auto_generate_base_class(base_base_ref, base_base_spelling, under)
        end
      end
    end
  end

  @auto_generated_bases << base_spelling
  ruby_name = @template_resolver.ruby_name_from_template(base_spelling, base_template_arguments)
  cruby_name = "rb_c#{ruby_name}"

  @classes[cruby_name] = base_spelling
  result + render_template("auto_generated_base_class",
                  :cruby_name => cruby_name, :ruby_name => ruby_name,
                  :base_spelling => base_spelling, :base_base_spelling => base_base_spelling,
                  :base_template => base_template, :template_arguments => base_template_arguments_text,
                  :under => under)
end

#auto_generate_template_base_for_class(base_specifier, base_spelling, under) ⇒ Object

Auto-generate a template base class for a non-template derived class. For example: class PlaneWarper : public WarperBase<PlaneProjector>



1446
1447
1448
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1446

def auto_generate_template_base_for_class(base_specifier, base_spelling, under)
  auto_generate_base_class(base_specifier, base_spelling, under, recursive: false)
end

#auto_instantiate_parameter_templates(cursor, under) ⇒ Object

Scan a class’s methods for class template parameters that need auto-instantiation.



662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 662

def auto_instantiate_parameter_templates(cursor, under)
  result = []

  cursor.each(false) do |child, _|
    next unless [:cursor_cxx_method, :cursor_constructor].include?(child.kind)
    next if child.private? || child.protected?

    child.num_arguments.times do |i|
      param = child.argument(i)
      type = param.type.intrinsic_type

      # Skip if not a template instantiation or is from a system header (std::, etc.)
      next unless type.num_template_arguments > 0
      next if type.canonical.declaration.location.in_system_header?

      # Find the class template declaration semantically so alias parameters
      # like `using AliasContainer = Container<Item>` still auto-instantiate
      # the underlying `Container<Item>` specialization.
      decl, instantiated_type, instantiated_type_source = parameter_template_instantiation(type, param)
      next unless decl && decl.kind == :cursor_class_template
      next unless decl.location.file == cursor.location.file

      # Auto-instantiate if no typedef exists
      instantiated_type = @type_speller.qualify_class_static_members(instantiated_type, cursor)
      next if @type_index.typedef_for(instantiated_type)

      code = auto_instantiate_template(decl, instantiated_type, instantiated_type_source, under)
      result << code unless code.empty?
    end
  end

  merge_children({ nil => result })
end

#auto_instantiate_template(cursor_template, instantiated_type, type, under) ⇒ Object

Auto-instantiate a class template used as a parameter type without a typedef.



1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1364

def auto_instantiate_template(cursor_template, instantiated_type, type, under)
  return "" unless cursor_template
  match = instantiated_type.match(/\<(.*)\>/)
  return "" unless match

  # Skip if template arguments reference skipped symbols
  return "" if type_references_skipped_symbol?(type)

  ruby_class_name = instantiated_type.gsub(/::|<|>|,|\s+/, ' ').split.map(&:capitalize).join
  ruby_class_name = @namer.apply_rename_types(ruby_class_name)
  cruby_name = "rb_c#{ruby_class_name}"
  return "" if @classes.key?(cruby_name)

  @classes[cruby_name] = instantiated_type
  render_template("class_template_specialization",
                  :cursor => cursor_template, :cursor_template => cursor_template,
                  :template_specialization => instantiated_type, :template_arguments => match[1],
                  :cruby_name => cruby_name, :base_ref => nil, :base => nil,
                  :base_spelling => nil, :under => under)
end

#binary_return_stmt(cursor, op_symbol) ⇒ Object

Pick the right return statement shape for a binary operator based on what the operator returns: void, a reference to self, or a value.



1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1218

def binary_return_stmt(cursor, op_symbol)
  if cursor.result_type.kind == :type_void
    "self #{op_symbol} other;"
  elsif cursor.result_type.kind == :type_lvalue_ref &&
        cursor.result_type.non_reference_type == cursor.type.arg_type(0).non_reference_type
    # Returns reference to self (e.g., FileStorage& operator<<)
    "self #{op_symbol} other;\n  return self;"
  else
    # Returns a value (e.g., bool, ptrdiff_t)
    "return self #{op_symbol} other;"
  end
end

#callback_signature_unsupported?(type) ⇒ Boolean

Returns:

  • (Boolean)


366
367
368
369
370
371
372
373
374
375
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 366

def callback_signature_unsupported?(type)
  return false if type.nil? || type.kind == :type_invalid
  return false unless [:type_function_proto, :type_function_no_proto].include?(type.kind)

  return true if type.result_type.reference?

  type.arg_types.any? do |arg_type|
    arg_type.reference? || unsupported_rice_callback_type?(arg_type)
  end
end

#comparable_std_type?(decl) ⇒ Boolean

Returns:

  • (Boolean)


331
332
333
334
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 331

def comparable_std_type?(decl)
  ["basic_string", "string", "monostate"].include?(decl.spelling) ||
    ["std::string", "std::monostate"].include?(decl.qualified_name)
end

#create_project_filesObject

Render the optional project-level wrapper files that call every generated per-header init function.



1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1519

def create_project_files
  return unless @project

  # Create master hpp/cpp files to include all the files we generated
  basename = "#{project}-rb"
  rice_header = "#{basename}.hpp"
  rice_cpp = "#{basename}.cpp"
  init_function = "Init_#{project}"

  content = render_template("project.hpp",
                            :init_name => init_function, :init_names => @init_names)
  self.outputter.write(rice_header, content)

  content = render_template("project.cpp",
                            :project_header => rice_header, :init_name => init_function, :init_names => @init_names)
  self.outputter.write(rice_cpp, content)
end

#create_rice_include_headerObject

Generate default Rice include header if user didn’t specify one. If the file already exists on disk, preserve it so user customizations are not lost.



415
416
417
418
419
420
421
422
423
424
425
426
427
428
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 415

def create_rice_include_header
  return if @include_header  # User specified their own header

  header_path = rice_include_header
  output_path = self.outputter.output_path(header_path)
  if File.exist?(output_path)
    STDOUT << "  Preserving: " << header_path << "\n"
    return
  end

  STDOUT << "  Writing: " << header_path << "\n"
  content = render_template("rice_include.hpp")
  self.outputter.write(header_path, content)
end

#figure_method(cursor) ⇒ Object

Map a cursor kind such as ‘:cursor_class_decl` to the corresponding visitor method symbol, for example `:visit_class_decl`.



1539
1540
1541
1542
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1539

def figure_method(cursor)
  name = cursor.kind.to_s.delete_prefix("cursor_")
  "visit_#{name.underscore}".to_sym
end

#find_under(cursor) ⇒ Object

Find the nearest enclosing module (namespace, class, or struct), skipping inline namespaces which don’t map to Ruby modules.



794
795
796
797
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 794

def find_under(cursor)
  cursor.ancestors_by_kind(:cursor_class_decl, :cursor_struct, :cursor_namespace)
        .find { |a| a.kind != :cursor_namespace || !a.inline_namespace? }
end

#generateObject

Parse the configured inputs with libclang and stream the resulting translation units back through this visitor.



196
197
198
199
200
201
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 196

def generate
  clang_args = @config[:clang_args] || []
  parser = RubyBindgen::Parser.new(@inputter, clang_args, libclang: @config[:libclang])
  ::FFI::Clang::Cursor.namer = @namer
  parser.generate(self)
end

#get_base_spelling(cursor) ⇒ Object

Get base class spelling from a cursor’s base specifier. Handles both template and non-template base classes. Returns nil if no base class exists.



733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 733

def get_base_spelling(cursor)
  base_specifier = cursor.find_first_by_kind(false, :cursor_cxx_base_specifier)
  return nil unless base_specifier

  base_declaration = base_specifier.type&.declaration
  return nil unless base_declaration && base_declaration.kind != :cursor_no_decl_found

  specialized_template = base_declaration.specialized_template
  base_cursor = specialized_template.kind == :cursor_invalid_file ? base_declaration : specialized_template

  # Skip system-header base classes (e.g., std::shared_ptr)
  return nil if base_cursor.location.in_system_header?

  @template_resolver.resolve_base_specifier_spelling(base_specifier)
end

#has_equality_operator?(decl) ⇒ Boolean

Returns:

  • (Boolean)


336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 336

def has_equality_operator?(decl)
  return true if decl.find_by_kind(false, :cursor_cxx_method).any? do |method|
    method.spelling == "operator==" && method.type.args_size == 1
  end

  @translation_unit_cursor.find_by_kind(true, :cursor_function, :cursor_function_template).any? do |function|
    next false unless function.spelling == "operator=="
    next false unless function.type.args_size == 2

    arg_declarations = 2.times.map do |index|
      function.type.arg_type(index).intrinsic_type.canonical.declaration
    end

    arg_declarations.all? do |arg_decl|
      arg_decl.kind != :cursor_no_decl_found &&
        (arg_decl == decl || arg_decl.qualified_name == decl.qualified_name)
    end
  end
end

#has_export_macro?(cursor) ⇒ Boolean

Check if cursor has one of the required export macros in its source text Used to filter out non-exported functions (e.g., only include CV_EXPORTS functions)

Returns:

  • (Boolean)


801
802
803
804
805
806
807
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 801

def has_export_macro?(cursor)
  return true if @export_macros.empty?

  source_text = cursor.extent.text
  return false if source_text.nil?
  @export_macros.any? { |macro| source_text.include?(macro) }
end

#has_skipped_param_type?(cursor) ⇒ Boolean

Check if any parameter type of a callable references a skipped symbol.

Returns:

  • (Boolean)


235
236
237
238
239
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 235

def has_skipped_param_type?(cursor)
  (0...cursor.type.args_size).any? do |i|
    type_references_skipped_symbol?(cursor.type.arg_type(i))
  end
end

#has_skipped_return_type?(cursor) ⇒ Boolean

Check if the return type of a callable references a skipped symbol.

Returns:

  • (Boolean)


252
253
254
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 252

def has_skipped_return_type?(cursor)
  type_references_skipped_symbol?(cursor.type.result_type)
end

#has_unsupported_rice_param_type?(cursor) ⇒ Boolean

Skip rvalue-reference parameters only when the underlying type is genuinely move-only. Copyable types such as std::vector<T> or T&& assignment operators compile and should remain available.

Returns:

  • (Boolean)


244
245
246
247
248
249
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 244

def has_unsupported_rice_param_type?(cursor)
  (0...cursor.type.args_size).any? do |i|
    type = cursor.type.arg_type(i)
    unsupported_rice_rvalue_ref_type?(type)
  end
end

#implicit_default_constructor_available?(cursor) ⇒ Boolean

Returns:

  • (Boolean)


377
378
379
380
381
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 377

def implicit_default_constructor_available?(cursor)
  cursor.find_by_kind(false, :cursor_field_decl).none? do |field|
    field.type.reference?
  end
end

#ipp_path_for_cursor(cursor) ⇒ Object

Compute the .ipp path for a template defined in a different file.



407
408
409
410
411
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 407

def ipp_path_for_cursor(cursor)
  template_file = cursor.file_location.file
  relative = Pathname.new(template_file).relative_path_from(Pathname.new(@inputter.base_path)).to_s
  File.join(File.dirname(relative), "#{File.basename(relative, '.*')}-rb.ipp")
end

#merge_children(versions, indentation: 0, chain: false, terminate: false, strip: false) ⇒ Object

Merge previously rendered child content into final output text, with optional method chaining, termination, indentation, and version guards.



1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1685

def merge_children(versions, indentation: 0, chain: false, terminate: false, strip: false)
  lines = versions.keys.sort_by { |key| key.to_s }.each_with_object([]) do |version, result|
    next unless versions[version]&.any?
    result << "#if #{@version_check} >= #{version}" if version
    versions[version].each do |line|
      line = line.rstrip if strip
      line = ".#{line}" if chain
      result << line
    end
    result << "#endif\n" if version
  end

  if lines.empty?
    return terminate ? ";" : ""
  end

  result = if chain
             lines.join("\n")
           else
             lines.map { |l| l.chomp }.reject(&:empty?).join("\n\n")
           end
  if terminate
    result += ";"
  end

  result = add_indentation(result, indentation) if indentation > 0
  result = "\n" + result if chain || terminate
  result
end

#move_only_std_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


282
283
284
285
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 282

def move_only_std_type?(type)
  canonical_spelling = type.canonical.spelling
  canonical_spelling.start_with?("std::unique_ptr<")
end

#nested_template_builder_requires_outer_context?(cursor) ⇒ Boolean

Returns:

  • (Boolean)


1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1566

def nested_template_builder_requires_outer_context?(cursor)
  parent = cursor.semantic_parent
  while parent
    break if [:cursor_invalid_file, :cursor_no_decl_found, :cursor_translation_unit].include?(parent.kind)
    return true if [:cursor_class_template,
                     :cursor_class_template_partial_specialization,
                     :cursor_function_template].include?(parent.kind)
    parent = parent.semantic_parent
  end

  false
end

#ostream_insertion?(cursor) ⇒ Boolean

‘std::ostream& operator<<(std::ostream&, T&)` overloads get translated to a Ruby `inspect` method on T’s class.

Returns:

  • (Boolean)


1173
1174
1175
1176
1177
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1173

def ostream_insertion?(cursor)
  return false unless cursor.spelling.include?("<<")
  arg0_decl = cursor.type.arg_type(0).non_reference_type.declaration
  arg0_decl.location.in_system_header? && arg0_decl.spelling.end_with?("ostream")
end

#parameter_template_instantiation(type, param) ⇒ Object

Resolve the actual class template being instantiated for a parameter type. This prefers semantic specialization data so aliases such as ‘using AliasContainer = Container<Item>` still resolve to `Container`.



699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 699

def parameter_template_instantiation(type, param)
  declaration = type.declaration
  specialized_template = declaration.specialized_template
  unless specialized_template.kind == :cursor_invalid_file
    return [specialized_template, @type_speller.type_spelling(type.unqualified_type), type]
  end

  canonical_type = type.canonical
  canonical_declaration = canonical_type.declaration
  specialized_template = canonical_declaration.specialized_template
  unless specialized_template.kind == :cursor_invalid_file
    return [specialized_template, @type_speller.type_spelling(canonical_type.unqualified_type), canonical_type]
  end

  template_ref = param.find_first_by_kind(true, :cursor_template_ref)
  return [nil, nil, type] unless template_ref

  [template_ref.referenced, @type_speller.type_spelling(type.unqualified_type), type]
end

#render_binary_operator(cursor, class_cursor) ⇒ Object

e.g. ‘operator+(const Mat& a, const Mat& b)`.



1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1205

def render_binary_operator(cursor, class_cursor)
  op_symbol = cursor.spelling.sub(/^operator\s*/, '')
  line = render_template("non_member_operator_binary",
                         :ruby_name => cursor.ruby_name,
                         :arg0_type => @type_speller.type_spelling(cursor.type.arg_type(0)),
                         :arg1_type => @type_speller.type_spelling(cursor.type.arg_type(1)),
                         :result_type => @type_speller.type_spelling(cursor.result_type),
                         :return_stmt => binary_return_stmt(cursor, op_symbol)).strip
  [class_cursor, line]
end

#render_children(cursor, indentation: 0, chain: false, terminate: false, strip: false, exclude_kinds: Set.new, only_kinds: nil) ⇒ Object

Convenience wrapper around ‘visit_children` and `merge_children`.



1716
1717
1718
1719
1720
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1716

def render_children(cursor, indentation: 0, chain: false, terminate: false, strip: false,
                    exclude_kinds: Set.new, only_kinds: nil)
  versions = visit_children(cursor, exclude_kinds: exclude_kinds, only_kinds: only_kinds)
  merge_children(versions, indentation: indentation, chain: chain, terminate: terminate, strip: strip)
end

#render_class_templates(cursor, indentation: 0, strip: false) ⇒ Object

Returns [content, has_builders] where has_builders indicates if any builder templates were generated



1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1580

def render_class_templates(cursor, indentation: 0, strip: false)
  results = Array.new
  cursor.find_by_kind(true, :cursor_class_template) do |class_template_cursor|
    if class_template_cursor.private? || class_template_cursor.protected?
      next :continue
    end

    # Nested class templates inside class templates need the outer
    # template parameters and dependent parent qualification to form a
    # valid builder signature. Until that context is threaded through the
    # builder templates, skip emitting them rather than generating
    # invalid C++ such as Allocator::rebind<U>.
    if nested_template_builder_requires_outer_context?(class_template_cursor)
      next :continue
    end

    # Skip forward declarations
    if class_template_cursor.declaration? && !class_template_cursor.definition?
      next :continue
    end
    if class_template_cursor.location.in_system_header?
      next :continue
    end

    # Check if class template is from the main file.
    # Note: from_main_file? doesn't work when -include is used, so manually check.
    unless translation_unit_file?(class_template_cursor)
      next :continue
    end

    # Skip if explicitly listed in symbols
    if skip_symbol?(class_template_cursor)
      next :continue
    end

    builder = visit_class_template_builder(class_template_cursor)
    results << builder if builder
  end
  content = merge_children({ nil => results }, indentation: indentation, strip: strip)
  has_builders = !results.empty? && !content.strip.empty?
  [content, has_builders]
end

#render_cursor(cursor, template, local_variables = {}) ⇒ Object

Render an ERB template with the current cursor injected into the locals.



1553
1554
1555
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1553

def render_cursor(cursor, template, local_variables = {})
  render_template(template, local_variables.merge(:cursor => cursor))
end

#render_non_member_operatorsObject

Render the queued non-member operators grouped by their target class. Includes ostream-based ‘inspect`, unary operators, and binary operators. Each branch produces a [target_cursor, rendered_line] pair; we group the lines by the target class’s cruby_name and emit one chained ‘class.define(…)…` block per class.



1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1135

def render_non_member_operators
  # cpp_type carries the qualified class name for cross-file Data_Type<T>() refs.
  grouped = Hash.new { |h, k| h[k] = { lines: [], cpp_type: nil } }

  @non_member_operators.each_value do |operators|
    operators.each do |op|
      cursor = op[:cursor]
      class_cursor = op[:class_cursor]

      target_cursor, line =
        if ostream_insertion?(cursor)
          render_ostream_inspect(cursor)
        elsif cursor.type.args_size == 1
          render_unary_operator(cursor, class_cursor)
        else
          render_binary_operator(cursor, class_cursor)
        end

      target_class = target_cursor.cruby_name
      grouped[target_class][:cpp_type] ||= @type_speller.qualified_class_name(target_cursor)
      grouped[target_class][:lines] << line
    end
  end

  result = []
  grouped.each do |cruby_name, info|
    next if info[:lines].empty?
    # Local-defined classes use the variable; cross-file refs use Data_Type<T>()
    class_ref = @classes.key?(cruby_name) ? cruby_name : "Data_Type<#{info[:cpp_type]}>()"
    # Indent 4 spaces (2 for function body + 2 for method chain)
    content = merge_children({ nil => info[:lines] }, indentation: 4, chain: true, terminate: true, strip: true)
    result << "#{class_ref}#{content}"
  end
  result.join("\n  \n  ")
end

#render_ostream_inspect(cursor) ⇒ Object



1179
1180
1181
1182
1183
1184
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1179

def render_ostream_inspect(cursor)
  target_cursor = cursor.type.arg_type(1).non_reference_type.declaration
  arg_type = @type_speller.type_spelling(cursor.type.arg_type(1))
  line = render_template("non_member_operator_inspect", :arg_type => arg_type).strip
  [target_cursor, line]
end

#render_unary_operator(cursor, class_cursor) ⇒ Object

e.g. ‘operator~(const Mat& m)`, `operator-(const Mat& m)`.



1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1187

def render_unary_operator(cursor, class_cursor)
  op_symbol = cursor.spelling.sub(/^operator\s*/, '')
  # Ruby uses +@ and -@ for unary plus/minus; ~ and ! stay as-is.
  ruby_name = case op_symbol
              when '+' then '+@'
              when '-' then '-@'
              else op_symbol
              end

  line = render_template("non_member_operator_unary",
                         :ruby_name => ruby_name,
                         :arg0_type => @type_speller.type_spelling(cursor.type.arg_type(0)),
                         :result_type => @type_speller.type_spelling(cursor.result_type),
                         :op_symbol => op_symbol).strip
  [class_cursor, line]
end

#rice_equality_supported?(type) ⇒ Boolean

Returns:

  • (Boolean)


308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 308

def rice_equality_supported?(type)
  type = type.non_reference_type if type.reference?
  type = type.canonical

  return true if FUNDAMENTAL_TYPES.include?(type.kind) || type.kind == :type_enum
  return true if [:type_pointer, :type_member_pointer].include?(type.kind)

  decl = type.declaration
  return true if decl.kind == :cursor_no_decl_found
  return true if comparable_std_type?(decl)

  if variant_like_type?(decl)
    return (0...type.num_template_arguments).all? do |i|
      arg_type = type.template_argument_type(i)
      next true if arg_type.kind == :type_invalid

      rice_equality_supported?(arg_type)
    end
  end

  has_equality_operator?(decl)
end

#rice_include_headerObject

Returns the path to the Rice include header (user-specified or auto-generated)



402
403
404
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 402

def rice_include_header
  @include_header || "#{@project || 'rice'}_include.hpp"
end

#skip_callable?(cursor) ⇒ Boolean

Common skip checks for functions and methods

Returns:

  • (Boolean)


812
813
814
815
816
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 812

def skip_callable?(cursor)
  skip_symbol?(cursor) ||
    cursor.availability == :deprecated ||
    cursor.type.variadic?
end

#skip_namespace_forward_declaration?(cursor) ⇒ Boolean

Returns:

  • (Boolean)


870
871
872
873
874
875
876
877
878
879
880
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 870

def skip_namespace_forward_declaration?(cursor)
  return false unless cursor.kind == :cursor_class_decl
  return false unless cursor.opaque_declaration?
  return false if [:cursor_class_decl, :cursor_struct].include?(cursor.semantic_parent.kind)

  definition = cursor.definition
  return false if [:cursor_invalid_file, :cursor_no_decl_found].include?(definition.kind)
  return false if definition.opaque_declaration?

  translation_unit_file?(definition)
end

#skip_symbol?(cursor) ⇒ Boolean

Check if a cursor should be skipped based on symbols config. Adds Rice-specific template-argument matching on top of basic lookup.

Returns:

  • (Boolean)


258
259
260
261
262
263
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 258

def skip_symbol?(cursor)
  return true if @symbols.skip?(cursor)

  # Check if any template argument type references a skipped symbol
  type_references_skipped_symbol?(cursor.type)
end

#template_cursor_definition(cursor) ⇒ Object



1557
1558
1559
1560
1561
1562
1563
1564
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1557

def template_cursor_definition(cursor)
  return nil unless cursor

  definition = cursor.definition
  return cursor if definition.kind == :cursor_invalid_file || definition.kind == :cursor_no_decl_found

  definition
end

#template_specialization_target(cursor) ⇒ Object

Resolve a typedef or type alias to the class template specialization it names. Handles both direct specializations:

typedef Point_<int> Point2i;

and aliases through an existing typedef:

typedef Point2i Point;


1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1263

def template_specialization_target(cursor)
  type = cursor.underlying_type

  loop do
    declaration = type.declaration
    return nil if declaration.kind == :cursor_no_decl_found

    template_cursor = declaration.specialized_template
    return [template_cursor, type] unless template_cursor.kind == :cursor_invalid_file

    return nil unless declaration.kind == :cursor_typedef_decl || declaration.kind == :cursor_type_alias_decl

    type = declaration.underlying_type
  end
end

#type_references_skipped_symbol?(type) ⇒ Boolean

Check if a type references a skipped symbol by examining its declaration. Unwraps pointers/references and checks template arguments recursively.

Returns:

  • (Boolean)


205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 205

def type_references_skipped_symbol?(type)
  type = type.intrinsic_type

  # Check the type's own declaration (try both non-canonical and canonical
  # since dependent types like SkippedClass<T> may not resolve canonically)
  [type, type.canonical].each do |t|
    decl = t.declaration
    next if decl.kind == :cursor_no_decl_found
    return true if @symbols.skip?(decl)
  end

  # For dependent/unexposed types where no declaration is found (e.g., SkippedClass<T>
  # inside a class template), fall back to checking the type spelling
  if type.declaration.kind == :cursor_no_decl_found && type.canonical.declaration.kind == :cursor_no_decl_found
    return true if @symbols.skip_spelling?(type.spelling)
  end

  # Check template arguments recursively
  if type.num_template_arguments > 0
    type.num_template_arguments.times do |i|
      arg_type = type.template_argument_type(i)
      next if arg_type.kind == :type_invalid
      return true if type_references_skipped_symbol?(arg_type)
    end
  end

  false
end

#unsupported_rice_attribute_type?(type) ⇒ Boolean

Rice’s std::function adapter is not reliable once callback signatures involve references or nested callbacks which themselves do. Skip those attrs instead of emitting uncompilable wrappers.

Returns:

  • (Boolean)


268
269
270
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 268

def unsupported_rice_attribute_type?(type)
  type.reference?
end

#unsupported_rice_callback_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


356
357
358
359
360
361
362
363
364
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 356

def unsupported_rice_callback_type?(type)
  type = type.non_reference_type if type.reference?
  canonical = type.canonical
  decl = canonical.declaration
  return false if decl.kind == :cursor_no_decl_found
  return false unless decl.qualified_name == "std::function"

  callback_signature_unsupported?(canonical.template_argument_type(0))
end

#unsupported_rice_rvalue_ref_type?(type) ⇒ Boolean

Preserve rvalue-reference bindings for copyable types. The only cases we still need to suppress here are move-only sinks such as unique_ptr&& where Rice has no usable from_ruby conversion.

Returns:

  • (Boolean)


275
276
277
278
279
280
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 275

def unsupported_rice_rvalue_ref_type?(type)
  return false unless type.kind == :type_rvalue_ref

  pointee = type.non_reference_type
  move_only_std_type?(pointee) || !pointee.copyable?
end

#unsupported_rice_vector_element_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 287

def unsupported_rice_vector_element_type?(type)
  type = type.non_reference_type if type.reference?
  canonical = type.canonical
  decl = canonical.declaration
  return false if decl.kind == :cursor_no_decl_found
  return false unless vector_like_type?(decl)

  element_type = canonical.template_argument_type(0)
  return false if element_type.nil? || element_type.kind == :type_invalid

  !rice_equality_supported?(element_type)
end

#variant_like_type?(decl) ⇒ Boolean

Returns:

  • (Boolean)


304
305
306
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 304

def variant_like_type?(decl)
  decl.spelling == "variant" || decl.qualified_name == "std::variant"
end

#vector_like_type?(decl) ⇒ Boolean

Returns:

  • (Boolean)


300
301
302
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 300

def vector_like_type?(decl)
  decl.spelling == "vector" || decl.qualified_name == "std::vector"
end

#visit_children(cursor, exclude_kinds: Set.new, only_kinds: nil) ⇒ Object

Visit eligible child cursors and bucket their rendered output by version guard so later merging can emit ‘#if VERSION >= …` blocks cleanly.



1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1625

def visit_children(cursor, exclude_kinds: Set.new, only_kinds: nil)
  versions = Hash.new { |h, k| h[k] = [] }
  cursor.each(false) do |child_cursor, parent_cursor|
    if child_cursor.location.in_system_header?
      next :continue
    end

    # This sometimes does not work - for example OpenCV defines the macros
    # CV__DNN_INLINE_NS_BEGIN/CV__DNN_INLINE_NS_END in a separate header file
    # which causes from_main_file? to be false. So manually check.
    # unless child_cursor.location.from_main_file?
    unless translation_unit_file?(child_cursor)
      next :continue
    end

    # For some reason child.cursor.public? filters out way too much
    if child_cursor.private? || child_cursor.protected?
      next :continue
    end

    if child_cursor.deleted?
      next :continue
    end

    child_kind = child_cursor.kind

    unless child_cursor.declaration? || child_kind == :cursor_macro_definition
      next :continue
    end

    if child_cursor.forward_declaration?
      next :continue
    end

    if exclude_kinds.include?(child_kind)
      next :continue
    end

    if only_kinds && !only_kinds.include?(child_kind)
      next :continue
    end

    visit_method = "visit_#{child_kind.to_s.delete_prefix("cursor_").underscore}".to_sym
    if self.respond_to?(visit_method)
      content = self.send(visit_method, child_cursor)
      version = @symbols.version(child_cursor)
      case content
        when Array
          versions[version] += content
        when String
          versions[version] << content
      end
    end
    next :continue
  end
  versions
end

#visit_class_decl(cursor) ⇒ Object Also known as: visit_struct

Render a class or struct, including child members, anonymous enum constants, embedded types, and any auto-generated template bases needed before the class itself can be registered.



540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 540

def visit_class_decl(cursor)
  return if skip_namespace_forward_declaration?(cursor)

  # Skip explicitly listed symbols
  return if skip_symbol?(cursor)

  # Skip anonymous types with no definer (no typedef, field, or variable name).
  # These are unnameable in C++ and cannot be meaningfully bound.
  return if cursor.anonymous? && !cursor.anonymous_definer

  result = Hash.new { |h, k| h[k] = [] }

  # Determine containing module
  under = find_under(cursor)

  # Is there a base class?
  base = nil
  auto_generated_base = ""
  base_specifier = cursor.find_first_by_kind(false, :cursor_cxx_base_specifier)
  if base_specifier
    # Use canonical spelling for fully qualified type name with namespaces
    base = base_specifier.type.canonical.spelling

    # Check if base is a template instantiation that needs to be auto-generated
    if base.include?('<') && !@auto_generated_bases.include?(base)
      auto_generated_base = auto_generate_template_base_for_class(base_specifier, base, under)
    end
  end

  # Visit children
  versions = visit_children(cursor,
                            exclude_kinds: Set[:cursor_class_decl, :cursor_struct, :cursor_enum_decl, :cursor_typedef_decl])

  # Are there any constructors? If not, C++ will define one implicitly
  # (but not for incomplete/opaque types which can't be instantiated)
  constructors = cursor.find_by_kind(false, :cursor_constructor)
  if !cursor.abstract? &&
     !cursor.opaque_declaration? &&
     constructors.none? &&
     implicit_default_constructor_available?(cursor)
    versions[nil].unshift(self.render_template("constructor",
                                               :cursor => cursor,
                                               :signature => @signature_builder.constructor_signature(cursor),
                                               :args => []))
  end

  # Add anonymous enum constants to the class chain (with per-constant versioning)
  cursor.find_by_kind(false, :cursor_enum_decl) do |child_cursor|
    next if child_cursor.private? || child_cursor.protected?
    next unless child_cursor.anonymous?
    constant_versions = visit_children(child_cursor)
    constant_versions.each do |version, lines|
      versions[version].concat(lines.map(&:strip))
    end
  end

  children_content = merge_children(versions, indentation: 2, chain: true, terminate: true, strip: true)

  # Collect forward-declared (incomplete) inner classes
  # They must be registered with Rice before the parent class methods use them
  incomplete_classes = []
  cursor.find_by_kind(false, :cursor_class_decl, :cursor_struct) do |child_cursor|
    next if child_cursor.private? || child_cursor.protected?
    next unless child_cursor.opaque_declaration?
    definition = child_cursor.definition
    if definition &&
       ![:cursor_invalid_file, :cursor_no_decl_found].include?(definition.kind) &&
       !definition.opaque_declaration? &&
       translation_unit_file?(definition)
      next
    end
    incomplete_classes << visit_incomplete_class(child_cursor, cursor)
  end
  incomplete_classes_content = merge_children({ nil => incomplete_classes })

  # Auto-instantiate any class templates used as parameter types
  auto_instantiated = auto_instantiate_parameter_templates(cursor, under)
  result[nil] << auto_instantiated unless auto_instantiated.empty?

  # Render class
  cpp_type = @type_speller.qualified_class_name(cursor)
  raw_class_name = cursor.type.spelling.split("::").last
  ruby_class_name = @namer.apply_rename_types(raw_class_name, raw_class_name.camelize)
  has_incomplete_classes = !incomplete_classes_content.to_s.empty?
  @classes[cursor.cruby_name] = cpp_type
  result[nil] << self.render_cursor(cursor, "class", :under => under, :base => base,
                               :auto_generated_base => auto_generated_base,
                               :incomplete_classes => incomplete_classes_content,
                               :children => children_content,
                               :cpp_type => cpp_type,
                               :ruby_class_name => ruby_class_name,
                               :has_incomplete_classes => has_incomplete_classes)

  # Alias each_const to each if the class only has const iterators
  if @iterator_collector.each_const_only?(cursor.cruby_name)
    result[nil] << render_template("iterator_alias", :cruby_name => cursor.cruby_name)
  end

  # Define any complete embedded classes and structs
  cursor.find_by_kind(false, :cursor_class_decl, :cursor_struct) do |child_cursor|
    next if child_cursor.private? || child_cursor.protected?
    next if child_cursor.forward_declaration?
    next if child_cursor.opaque_declaration?
    content = visit_class_decl(child_cursor)
    next unless content
    version = @symbols.version(child_cursor)
    result[version] << content
  end

  # Define any named embedded enums (anonymous enums are chained above)
  cursor.find_by_kind(false, :cursor_enum_decl) do |child_cursor|
    next if child_cursor.private? || child_cursor.protected?
    next if child_cursor.anonymous?
    version = @symbols.version(child_cursor)
    result[version] << visit_enum_decl(child_cursor)
  end

  merge_children(result)
end

#visit_class_template_builder(cursor) ⇒ Object

Render the reusable ‘_instantiate` helper for a class template so typedefs and alias specializations can bind that template later.



751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 751

def visit_class_template_builder(cursor)
  children_content = render_children(cursor,
                                    only_kinds: [:cursor_cxx_method, :cursor_constructor, :cursor_field_decl, :cursor_variable,
                                                 :cursor_enum_decl, :cursor_conversion_function],
                                    indentation: 4, chain: true,
                                    terminate: true, strip: true)

  base_spelling = get_base_spelling(cursor)

  template_parameter_kinds = [:cursor_template_type_parameter,
                              :cursor_non_type_template_parameter,
                              :cursor_template_template_parameter]

  raw_template_parameters = cursor.find_by_kind(false, *template_parameter_kinds)

  # Filter out unnamed template parameters (e.g., `typename = void` default params)
  # — they have empty spelling and produce invalid C++ like `<T, >`
  template_parameters = raw_template_parameters.reject { |p| p.spelling.empty? }
  return if template_parameters.empty? && raw_template_parameters.any?
  template_signature = template_parameters.map { |template_parameter| @template_resolver.template_parameter_signature(template_parameter) }
                                         .join(", ")

  # Build fully qualified type using template params (e.g., Tests::Matrix<T, Rows, Columns>)
  param_names = template_parameters.map { |template_parameter| @template_resolver.template_parameter_argument(template_parameter) }
                                  .join(", ")
  fully_qualified_type = "#{cursor.qualified_name}<#{param_names}>"

  # Determine containing module
  under = find_under(cursor)

  # Render class
  result = Array.new
  result << self.render_cursor(cursor, "class_template", :under => under,
                               :template_signature => template_signature,
                               :fully_qualified_type => fully_qualified_type,
                               :base_spelling => base_spelling,
                               :children => children_content)

  merge_children({ nil => result })
end

#visit_constructor(cursor) ⇒ Object

Render a public, callable constructor into the Rice chain for its class.



505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 505

def visit_constructor(cursor)
  # Do not process class constructors defined outside of the class definition
  return if cursor.lexical_parent != cursor.semantic_parent

  # Do not process deleted constructors
  return if cursor.deleted?

  # Skip deprecated constructors (they may not be exported from library)
  return if cursor.availability == :deprecated

  # Skip explicitly listed constructors
  return if skip_symbol?(cursor)

  # Do not process move constructors
  return if cursor.move_constructor?

  # Do not construct abstract classes
  return if cursor.semantic_parent.abstract?

  # Skip constructors that take skipped types as parameters
  return if has_skipped_param_type?(cursor)
  return if has_unsupported_rice_param_type?(cursor)

  signature = @signature_builder.constructor_signature(cursor)
  args = @signature_builder.arguments(cursor)

  return unless signature

  self.render_cursor(cursor, "constructor",
                     :signature => signature, :args => args)
end

#visit_conversion_function(cursor) ⇒ Object

Render a conversion operator such as ‘operator bool()` or `operator T*()`. Template-parameter conversions that cannot produce stable Ruby names are skipped.



910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 910

def visit_conversion_function(cursor)
  # For now only deal with member functions
  return unless CURSOR_CLASSES.include?(cursor.lexical_parent.kind)

  return if skip_callable?(cursor)

  return unless cursor.type.args_size == 0
  return if has_skipped_return_type?(cursor)

  result_type = cursor.type.result_type

  # Skip "safe bool idiom" conversion operators from pre-C++11.
  # These are typedefs to member function pointers used before explicit operator bool().
  # Pattern: typedef void (Class::*bool_type)() const; operator bool_type() const;
  if result_type.declaration.kind == :cursor_typedef_decl
    canonical = result_type.canonical
    if canonical.kind == :type_member_pointer
      # Check if the pointee is a function type
      pointee = canonical.pointee
      if pointee.kind == :type_function_proto || pointee.kind == :type_function_no_proto
        return
      end
    end
  end

  result_type_spelling = @type_speller.type_spelling(result_type)
  is_const = cursor.const?

  # For class templates, the result type may contain "type-parameter-X_Y" which
  # generates invalid Ruby method names. Use generic names instead.
  if cursor.semantic_parent.kind == :cursor_class_template &&
     result_type.canonical.spelling.include?("type-parameter-")
    # Determine if this is a pointer conversion
    if result_type.kind == :type_pointer
      ruby_name = is_const ? "to_const_ptr" : "to_ptr"
    else
      # Skip other template parameter conversions for now
      return
    end
  else
    ruby_name = cursor.ruby_name
  end

  qualified_parent = @type_speller.qualified_display_name(cursor.semantic_parent)
  self.render_cursor(cursor, "conversion_function",
                     :ruby_name => ruby_name, :result_type => result_type_spelling,
                     :is_const => is_const,
                     :qualified_parent => qualified_parent)
end

#visit_cxx_iterator_method(cursor) ⇒ Object

Generates Rice define_iterator calls for C++ iterator methods. In C++, cbegin/crbegin can be called on non-const objects but return const iterators while const begin/rbegin require a const receiver. Ruby has no such distinction, so the collector skips cbegin/crbegin to avoid emitting duplicate each_const / each_reverse_const methods.



887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 887

def visit_cxx_iterator_method(cursor)
  iterator_name = @iterator_collector.record(cursor)
  return unless iterator_name

  signature = @signature_builder.method_signature(cursor)
  return unless signature

  begin_method = cursor.spelling
  end_method = begin_method.sub("begin", "end")
  is_template = cursor.semantic_parent.kind == :cursor_class_template
  qualified_parent = @type_speller.qualified_display_name(cursor.semantic_parent)

  self.render_cursor(cursor, "cxx_iterator_method",
                     :name => iterator_name,
                     :begin_method => begin_method,
                     :end_method => end_method,
                     :signature => signature,
                     :is_template => is_template,
                     :qualified_parent => qualified_parent)
end

#visit_cxx_method(cursor) ⇒ Object

Render a class method, including special handling for iterator adapters and mutable ‘operator[]` setter support.



820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 820

def visit_cxx_method(cursor)
  # Do not process method definitions outside of classes (because we already processed them)
  return if cursor.lexical_parent != cursor.semantic_parent
  return if skip_callable?(cursor)
  return if has_skipped_param_type?(cursor)
  return if has_unsupported_rice_param_type?(cursor)
  return if has_skipped_return_type?(cursor)

  # Is this an iterator?
  if ITERATOR_METHODS.include?(cursor.spelling)
    return visit_cxx_iterator_method(cursor)
  end

  signature = @signature_builder.method_signature(cursor)

  result = Array.new

  name = cursor.ruby_name
  args = @signature_builder.arguments(cursor)

  # Check if return type should use ReturnBuffer
  return_buffer = @signature_builder.buffer_type?(cursor.result_type)

  is_template = cursor.semantic_parent.kind == :cursor_class_template
  qualified_parent = @type_speller.qualified_display_name(cursor.semantic_parent)
  result << self.render_cursor(cursor, "cxx_method",
                               :name => name,
                               :is_template => is_template,
                               :signature => signature,
                               :args => args,
                               :return_buffer => return_buffer,
                               :qualified_parent => qualified_parent)

  # Special handling for implementing #[](index, value)
  if cursor.spelling == "operator[]" && cursor.result_type.kind == :type_lvalue_ref &&
     !cursor.result_type.non_reference_type.const_qualified? && !cursor.const?
    index_param = cursor.find_by_kind(false, :cursor_parm_decl).first
    index_type = @type_speller.type_spelling(cursor.type.arg_type(0))
    index_name = index_param&.spelling.to_s.empty? ? "index" : index_param.spelling
    value_type = @type_speller.type_spelling(cursor.result_type)
    result << self.render_cursor(cursor, "operator[]",
                                 :name => name,
                                 :index_type => index_type,
                                 :index_name => index_name,
                                 :qualified_parent => qualified_parent,
                                 :value_type => value_type)
  end
  result
end

#visit_endObject

Emit the shared include header and optional project wrapper once all translation units have been processed.



396
397
398
399
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 396

def visit_end
  create_rice_include_header
  create_project_files
end

#visit_enum_constant_decl(cursor) ⇒ Object

Render one enum constant, preserving whether it came from an anonymous enum and whether that enum lives inside a class scope.



982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 982

def visit_enum_constant_decl(cursor)
  return if skip_symbol?(cursor)
  enum_parent = cursor.semantic_parent
  enum_scope = enum_parent.semantic_parent
  anonymous_parent = enum_parent.anonymous?
  anonymous_class_scope = anonymous_parent &&
    [:cursor_class_decl, :cursor_struct, :cursor_class_template].include?(enum_scope.kind)
  qualified_name = "#{@type_speller.qualified_display_name(enum_scope)}::#{cursor.spelling}"

  self.render_cursor(cursor, "enum_constant_decl",
                     :anonymous_parent => anonymous_parent,
                     :anonymous_class_scope => anonymous_class_scope,
                     :owner_cruby_name => enum_scope.cruby_name,
                     :qualified_name => qualified_name,
                     :value_name => cursor.qualified_display_name)
end

#visit_enum_decl(cursor) ⇒ Object

Render a named enum as a Rice enum, or flatten anonymous enum constants into the surrounding class/namespace output.



962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 962

def visit_enum_decl(cursor)
  return if CURSOR_CLASSES.include?(cursor.semantic_parent.kind) && !cursor.public?
  return if !cursor.anonymous? && skip_symbol?(cursor)

  if cursor.anonymous? && CURSOR_CLASSES.include?(cursor.semantic_parent.kind)
    # Anonymous enum inside a class — return constants as chainable strings
    versions = visit_children(cursor)
    return versions.values.flatten.map(&:strip)
  elsif cursor.anonymous?
    # Anonymous enum at namespace/TU level — return standalone constant definitions
    return render_children(cursor, strip: true)
  end

  under = find_under(cursor)
  children = render_children(cursor, indentation: 2, chain: true, terminate: true, strip: true)
  self.render_cursor(cursor, "enum_decl", :under => under, :children => children)
end

#visit_field_decl(cursor) ⇒ Object

Render a public field as a Rice attribute on its containing class.



1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1082

def visit_field_decl(cursor)
  return unless cursor.public?
  return if skip_symbol?(cursor)
  return if cursor.availability == :deprecated
  return if unsupported_rice_attribute_type?(cursor.type)

  qualified_parent = @type_speller.qualified_display_name(cursor.semantic_parent)
  self.render_cursor(cursor, "field_decl",
                     :qualified_parent => qualified_parent)
end

#visit_function(cursor) ⇒ Object

Render a free function or non-member operator that survives the symbol, export-macro, and parameter/return-type filters.



1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1001

def visit_function(cursor)
  # Skip `= delete`d free functions. clang_CXXMethod_isDeleted only
  # applies to class methods, so we use clang_getCursorAvailability,
  # which reports :not_available for deleted free functions.
  return if cursor.availability == :not_available
  # Can't return arrays in C++
  return if cursor.type.result_type.is_a?(::FFI::Clang::Types::Array)
  return if skip_callable?(cursor)
  return if has_skipped_param_type?(cursor)
  return if has_unsupported_rice_param_type?(cursor)
  return if has_skipped_return_type?(cursor)
  return unless has_export_macro?(cursor)

  if cursor.spelling.start_with?('operator') && !cursor.spelling.match?(/^operator\w/)
    return self.visit_operator_non_member(cursor)
  end

  name = cursor.ruby_name
  args = @signature_builder.arguments(cursor)

  signature = @signature_builder.method_signature(cursor)

  # Check if return type should use ReturnBuffer
  return_buffer = @signature_builder.buffer_type?(cursor.type.result_type)

  under = cursor.ancestors_by_kind(:cursor_namespace)
               .find { |a| !a.inline_namespace? }
  self.render_cursor(cursor, "function",
                     :under => under,
                     :name => name,
                     :signature => signature,
                     :args => args,
                     :return_buffer => return_buffer)
end

#visit_incomplete_class(cursor, parent_cursor) ⇒ Object

Visit a forward-declared (incomplete) inner class. These need to be registered with Rice so that types like Ptr<Impl> work. Must be registered BEFORE the parent class methods that use them (smart pointer issue).



722
723
724
725
726
727
728
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 722

def visit_incomplete_class(cursor, parent_cursor)
  # Skip if already defined
  return "" if @classes.key?(cursor.cruby_name)

  @classes[cursor.cruby_name] = cursor.qualified_name
  render_cursor(cursor, "incomplete_class", :under => parent_cursor)
end

#visit_macro_definition(cursor) ⇒ Object

Render simple object-like macros as Ruby constants when the macro body is exactly one literal token.



1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1038

def visit_macro_definition(cursor)
  tokens = cursor.translation_unit.tokenize(cursor.extent)
  return unless tokens.size == 2
  return unless tokens.tokens[0].kind == :identifier
  return unless tokens.tokens[1].kind == :literal
  return if skip_symbol?(cursor)

  self.render_cursor(cursor, "constant",
                     :name => tokens.tokens[0].spelling.upcase_first,
                     :qualified_name => tokens.tokens[0].spelling)
end

#visit_namespace(cursor) ⇒ Object

Render a namespace as a Ruby module, except for inline namespaces which are flattened into their enclosing namespace.



1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1052

def visit_namespace(cursor)
  # Skip anonymous namespaces - they're internal implementation details
  return if cursor.anonymous?

  # Inline namespaces (e.g., std::__1, abseil's lts_*) should not create
  # Ruby modules — their members belong to the enclosing namespace.
  # Recurse into children without registering a new module.
  if cursor.inline_namespace?
    return self.render_children(cursor)
  end

  result = Array.new

  # Don't redefine a namespace twice. It doesn't matter to Ruby, but C++ wrapper
  # will break with a redefinition error:
  #   Module rb_mNamespace = define_module("namespace");
  #   Module rb_mNamespace = define_module("namespace");
  qualified_display_name = cursor.qualified_display_name
  unless @namespaces.include?(qualified_display_name)
    @namespaces << qualified_display_name
    under = find_under(cursor)
    result << self.render_cursor(cursor, "namespace", :under => under)
  end

  result << self.render_children(cursor)

  result.map { |s| s.chomp }.reject(&:empty?).join("\n\n")
end

#visit_operator_non_member(cursor) ⇒ Object

Record a free operator for later rendering onto the target class. These are grouped and emitted after normal members so cross-file ‘Data_Type<T>()` references can be handled in one pass.



1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1096

def visit_operator_non_member(cursor)
  # This is a stand-alone operator, such as:
  #
  #   MatExpr operator + (const Mat& a, const Mat& b);  # binary (2 args)
  #   std::ostream& operator << (std::ostream& out, const Complex<_Tp>& c)
  #   MatExpr operator ~(const Mat& m);  # unary (1 arg)
  #   MatExpr operator -(const Mat& m);  # unary negation (1 arg)
  return if cursor.type.args_size < 1 || cursor.type.args_size > 2

  arg0_type = cursor.type.arg_type(0).non_reference_type
  class_cursor = arg0_type.declaration

  # Skip when the first argument is a fundamental type (e.g., double) or a
  # typedef to one (e.g., ptrdiff_t -> long long).  There is no Rice wrapper
  # for these types so Data_Type<T>() would be invalid.
  return if class_cursor.kind == :cursor_no_decl_found
  return if FUNDAMENTAL_TYPES.include?(arg0_type.canonical.kind)
  # Rice already provides bitwise operators (&, |, ^, ~, <<, >>) for enums automatically
  return if class_cursor.kind == :cursor_enum_decl

  # Skip types that Rice converts to native Ruby types (no Rice wrapper exists).
  # Note: std::vector, std::pair, etc. ARE wrapped by Rice.
  canonical_decl = arg0_type.canonical.declaration
  return if canonical_decl.kind != :cursor_no_decl_found &&
            canonical_decl.location.in_system_header? &&
            RICE_NATIVE_TYPES.include?(canonical_decl.spelling)

  # Use the class cursor directly - operators should be attached to the actual class
  # (e.g., rb_cCvMat for cv::Mat), not to typedefs (e.g., rb_cMatND which is typedef for Mat)
  # Collect non-member operators to render grouped by class later
  @non_member_operators[class_cursor.cruby_name] << { cursor: cursor, class_cursor: class_cursor }
  nil
end

#visit_parse_error(_path, relative_path, error) ⇒ Object



389
390
391
392
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 389

def visit_parse_error(_path, relative_path, error)
  warn "Warning: skipping #{relative_path} because it could not be parsed"
  warn error.message
end

#visit_startObject

Reset any per-run caches before parsing begins.



384
385
386
387
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 384

def visit_start
  # Clear caches from previous runs
  @type_speller.clear
end

#visit_template_specialization(cursor, cursor_template, underlying_type) ⇒ Object

Render one typedef/alias specialization of a class template and ensure any inherited template bases are available first.



1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1281

def visit_template_specialization(cursor, cursor_template, underlying_type)
  under = find_under(cursor)
  # Get template arguments including any default values that were omitted in the typedef
  template_argument_values = @template_resolver.full_template_arguments(cursor, underlying_type, cursor_template)
  template_arguments = template_argument_values.join(", ")

  result = ""

  # Is there a base class?
  base_ref = cursor_template.find_first_by_kind(false, :cursor_cxx_base_specifier)
  base_spelling = nil
  if base_ref
    # Base class children can be a :cursor_type_ref or :cursor_template_ref
    type_ref = base_ref.find_first_by_kind(false, :cursor_type_ref, :cursor_template_ref)
    base = type_ref.definition

    # Resolve the base class to its instantiated form (e.g., PtrStep<unsigned char>)
    base_spelling = @template_resolver.resolve_base_instantiation(cursor, underlying_type)

    # Ensure the base class is generated before this class.
    # Skip std:: base classes — Rice handles standard library types automatically.
    # This prevents walking libstdc++ internals (e.g., cv::Ptr<T> inherits from
    # std::shared_ptr<T> → std::__shared_ptr<T> → std::__shared_ptr_access<T>).
    # Also check the base template's own namespace since resolve_base_instantiation
    # may incorrectly use the derived class's namespace (e.g., "cv::shared_ptr").
    base_template_decl = template_cursor_definition(base_ref.find_first_by_kind(false, :cursor_type_ref, :cursor_template_ref)&.referenced)
    base_in_std = base_template_decl&.location&.in_system_header?
    # Use cursor_template_ref specifically to get the base class template declaration
    # (cursor_type_ref may resolve to a template parameter like T instead)
    base_class_template = template_cursor_definition(base_ref.find_first_by_kind(false, :cursor_template_ref)&.referenced)
    base_in_current_file = base_class_template &&
      translation_unit_file?(base_class_template)
    if base_spelling && !base_in_std
      if base_in_current_file
        base_typedef = @type_index.typedef_for(base_spelling)
        if base_typedef
          # Base has a typedef - check if it has been generated yet
          unless @classes.key?(base_typedef.cruby_name)
            # Force generate the typedef first (recursively handles its bases)
            result = visit_typedef_decl(base_typedef) || ""
          end
        elsif !@auto_generated_bases.include?(base_spelling)
          # No typedef - auto-generate
          result = auto_generate_base_class(base_ref, base_spelling, under)
        end
      elsif base_class_template && !base_class_template.location.in_system_header?
        # Base template is from an included file — include its .ipp so the
        # _instantiate builder is available for the derived class
        builder_ipp = ipp_path_for_cursor(base_class_template)
        current_ipp = File.join(@relative_dir, "#{@basename}.ipp")
        if builder_ipp != current_ipp
          ipp_relative = Pathname.new(builder_ipp).relative_path_from(Pathname.new(@relative_dir)).to_s
          @includes << "#include \"#{ipp_relative}\""
        end
      end
    end
  end

  template_specialization = @template_resolver.specialization_spelling(cursor, underlying_type, cursor_template)

  # If template is defined in a different file, include its .ipp for the _instantiate builder
  template_owner = template_cursor_definition(cursor_template)
  unless translation_unit_file?(template_owner)
    builder_ipp = ipp_path_for_cursor(template_owner)
    current_ipp = File.join(@relative_dir, "#{@basename}.ipp")
    if builder_ipp != current_ipp
      ipp_relative = Pathname.new(builder_ipp).relative_path_from(Pathname.new(@relative_dir)).to_s
      @includes << "#include \"#{ipp_relative}\""
    end
  end

  @classes[cursor.cruby_name] = template_specialization
  result + self.render_cursor(cursor, "class_template_specialization",
                     :cursor_template => cursor_template,
                     :template_specialization => template_specialization,
                     :template_arguments => template_arguments,
                     :base_ref => base_ref,
                     :base => base,
                     :base_spelling => base_spelling,
                     :under => under)
end

#visit_translation_unit(translation_unit, path, relative_path) ⇒ Object

Render one translation unit into its generated ‘-rb.hpp`, `-rb.cpp`, and optional `-rb.ipp` outputs.



432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 432

def visit_translation_unit(translation_unit, path, relative_path)
  @namespaces.clear
  @classes.clear
  @auto_generated_bases.clear
  @non_member_operators.clear
  @iterator_collector.clear
  cursor = translation_unit.cursor
  @translation_unit_cursor = cursor
  @type_speller.printing_policy = cursor.printing_policy

  # Build lookups for typedef resolution and simple-name qualification.
  @type_index.build!(cursor)

  # Figure out relative paths for generated header and cpp file
  @basename = "#{File.basename(relative_path, ".*")}-rb"
  @relative_dir = File.dirname(relative_path)
  rice_header = File.join(@relative_dir, "#{@basename}.hpp")
  rice_cpp = File.join(@relative_dir, "#{@basename}.cpp")

  # Track init names - use relative path to avoid conflicts (e.g., core/version vs dnn/version)
  path_parts = Pathname.new(relative_path).each_filename.to_a
  path_parts.shift if path_parts.length >= 2  # Remove top-level directory (e.g., opencv2)
  filename = Pathname.new(path_parts.pop).sub_ext('').to_s.camelize
  dir_part = path_parts.map(&:camelize).join('_')
  init_name = dir_part.empty? ? "Init_#{filename}" : "Init_#{dir_part}_#{filename}"
  @init_names[rice_header] = init_name

  @includes = Set.new
  @includes << "#include <#{relative_path}>"
  @includes << "#include \"#{@basename}.hpp\""

  class_templates, has_builders = render_class_templates(cursor)
  content = render_children(cursor, :indentation => 2)

  # Render non-member operators grouped by class
  non_member_ops = render_non_member_operators
  unless non_member_ops.empty?
    content = content + "\n\n  " + non_member_ops
  end

  # Generate .ipp file if builders exist (for reusability without duplicate Init symbols)
  rice_ipp = nil
  if has_builders
    rice_ipp = File.join(File.dirname(relative_path), "#{@basename}.ipp")
    STDOUT << "  Writing: " << rice_ipp << "\n"
    ipp_content = render_cursor(cursor, "translation_unit.ipp",
                                :class_templates => class_templates)
    self.outputter.write(rice_ipp, ipp_content)
  end

  # Render C++ file
  STDOUT << "  Writing: " << rice_cpp << "\n"
  content = render_cursor(cursor, "translation_unit.cpp",
                          :class_templates => class_templates,
                          :content => content,
                          :includes => @includes,
                          :init_name => init_name,
                          :rice_header => rice_header,
                          :incomplete_iterators => @iterator_collector.incomplete_iterators,
                          :rice_ipp => rice_ipp ? File.basename(rice_ipp) : nil)
  self.outputter.write(rice_cpp, content)

  # Render header file
  STDOUT << "  Writing: " << rice_header << "\n"
  # Compute relative path from translation unit directory to the include header
  relative_include = Pathname.new(rice_include_header).relative_path_from(File.dirname(relative_path)).to_s
  content = render_cursor(cursor, "translation_unit.hpp",
                          :init_name => init_name,
                          :rice_include_header => relative_include)
  self.outputter.write(rice_header, content)
end

#visit_type_alias_decl(cursor) ⇒ Object

Handle C++11 ‘using’ type alias declarations the same as typedef



1254
1255
1256
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1254

def visit_type_alias_decl(cursor)
  visit_typedef_decl(cursor)
end

#visit_typedef_decl(cursor) ⇒ Object

Resolve a top-level typedef or alias to the class template specialization it names, then render the specialization binding.



1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1233

def visit_typedef_decl(cursor)
  return if cursor.semantic_parent.kind == :cursor_class_decl || cursor.semantic_parent.kind == :cursor_struct
  return if skip_symbol?(cursor)

  # Skip if already processed (can happen when force-generating base classes)
  return if @classes.key?(cursor.cruby_name)

  # Skip typedefs to std:: types - Rice handles these automatically
  canonical_decl = cursor.underlying_type.canonical.declaration
  return if canonical_decl.kind != :cursor_no_decl_found && canonical_decl.location.in_system_header?

  template_specialization = template_specialization_target(cursor)
  return unless template_specialization

  cursor_template, underlying_type = template_specialization
  return if skip_symbol?(cursor_template)

  visit_template_specialization(cursor, cursor_template, underlying_type)
end

#visit_union(cursor) ⇒ Object

Render a union plus any embedded unions/structs that need to appear first.



1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1451

def visit_union(cursor)
  return if cursor.forward_declaration?
  return if cursor.anonymous?
  return if skip_symbol?(cursor)

  result = Array.new

  # Define any embedded unions (skip anonymous/skipped ones that return nil)
  cursor.find_by_kind(false, :cursor_union) do |union|
    content = visit_union(union)
    result << content if content
  end

  # Define any embedded structures (skip anonymous/skipped ones that return nil)
  cursor.find_by_kind(false, :cursor_struct) do |struct|
    content = visit_struct(struct)
    result << content if content
  end

  under = find_under(cursor)

  children = render_children(cursor, indentation: 2, chain: true, terminate: true, strip: true,
                                     exclude_kinds: Set[:cursor_struct, :cursor_union])
  result << self.render_cursor(cursor, "union", :under => under, :children => children,
                               :cpp_type => @type_speller.qualified_class_name(cursor),
                               :ruby_name => cursor.ruby_name)
  result.map { |s| s.chomp }.join("\n\n")
end

#visit_variable(cursor) ⇒ Object

Render a variable either as a Ruby constant or, for static class members, as a singleton attribute on the owning Rice class.



1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1482

def visit_variable(cursor)
  if CURSOR_CLASSES.include?(cursor.semantic_parent.kind) &&
    !cursor.public?
    return
  end
  return if skip_symbol?(cursor)

  # Skip compiler/cuda keywords like __device__ __forceinline__
  return if cursor.spelling.match(/^__.*__$/)

  # Const variables become Ruby constants
  if cursor.type.const_qualified?
    visit_variable_constant(cursor)
  else
    parent_kind = cursor.semantic_parent.kind
    if parent_kind == :cursor_translation_unit || parent_kind == :cursor_namespace
      # Non-const variables at global/namespace scope become Ruby constants
      # Rice's define_singleton_attr only works on Data_Type<T>, not Class or Module
      visit_variable_constant(cursor)
    else
      # Static class fields use define_singleton_attr on Data_Type<T>
      qualified_parent = @type_speller.qualified_display_name(cursor.semantic_parent)
      self.render_cursor(cursor, "variable",
                         :qualified_parent => qualified_parent)
    end
  end
end

#visit_variable_constant(cursor) ⇒ Object

Render one constant definition for an enum value, macro, or variable.



1511
1512
1513
1514
1515
# File 'lib/ruby-bindgen/generators/rice/rice.rb', line 1511

def visit_variable_constant(cursor)
  self.render_cursor(cursor, "constant",
                     :name => cursor.spelling.upcase_first,
                     :qualified_name => @type_speller.qualified_display_name(cursor))
end