Class: RubyBindgen::Generators::Rice::TemplateResolver

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby-bindgen/generators/rice/template_resolver.rb

Overview

Resolves template specializations, omitted defaults, and inherited template bases using libclang’s semantic APIs plus source-written fallback text when libclang does not expose a complete argument string.

Defined Under Namespace

Classes: TemplateArgumentInfo

Instance Method Summary collapse

Constructor Details

#initialize(reference_qualifier:, type_speller:, namer:) ⇒ TemplateResolver

Returns a new instance of TemplateResolver.



13
14
15
16
17
# File 'lib/ruby-bindgen/generators/rice/template_resolver.rb', line 13

def initialize(reference_qualifier:, type_speller:, namer:)
  @reference_qualifier = reference_qualifier
  @type_speller = type_speller
  @namer = namer
end

Instance Method Details

#full_template_arguments(cursor, underlying_type, template_cursor) ⇒ Object

Get full template arguments including default values. When a typedef uses a template with default arguments, libclang reports only the written arguments. Type arguments come from the semantic type API, while non-type and template-template args fall back to source text so expressions like ‘1 + 2` and names like `Box` are preserved.

Examples:

ExprValue<1 + 2>

stays

ExprValue_instantiate<1 + 2>

Matrix<int, 2>

becomes

Matrix_instantiate<int, 2, 1>


33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/ruby-bindgen/generators/rice/template_resolver.rb', line 33

def full_template_arguments(cursor, underlying_type, template_cursor)
  actual_args = specialization_template_arguments(cursor, underlying_type, template_cursor)
  
  return actual_args if template_cursor.nil?
  
  params = template_parameters(template_cursor)
  return actual_args if actual_args.length >= params.length
  
  argument_cursor = specialization_argument_cursor(underlying_type)
  missing_params = params.drop(actual_args.length)
  default_values = missing_params.each_with_index.map do |param, offset|
    template_param_default(param, argument_cursor: argument_cursor, argument_index: actual_args.length + offset)
  end.compact
  
  return actual_args if default_values.length != missing_params.length
  
  actual_args + default_values
end

#resolve_base_instantiation(cursor, underlying_type) ⇒ Object

Given a typedef cursor and its underlying type, resolve the base class to an actual instantiated type (e.g., PtrStep<unsigned char> instead of PtrStep<T>). Correctly handles cases where derived and base templates have different numbers of template parameters (e.g., Vec<_Tp, cn> : public Matx<_Tp, cn, 1>). Returns the resolved base class spelling or nil if no base class exists.



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/ruby-bindgen/generators/rice/template_resolver.rb', line 264

def resolve_base_instantiation(cursor, underlying_type)
  derived_template = specialized_template_cursor(underlying_type)
  if derived_template.nil?
    template_ref = cursor.find_first_by_kind(false, :cursor_template_ref)
    derived_template = template_ref&.referenced
  end
  return nil unless derived_template
  
  base_spec = derived_template.find_first_by_kind(false, :cursor_cxx_base_specifier)
  return nil unless base_spec
  
  base_template_ref = base_spec.find_first_by_kind(false, :cursor_template_ref)
  
  unless base_template_ref
    base_type_ref = base_spec.find_first_by_kind(false, :cursor_type_ref)
    return base_type_ref&.referenced&.qualified_name
  end
  
  template_params = template_parameters(derived_template).map(&:spelling)
  template_arg_values = full_template_arguments(cursor, underlying_type, derived_template)
  
  substitutions = {}
  template_params.each_with_index do |param, index|
    substitutions[param] = template_arg_values[index] if template_arg_values[index]
  end
  
  resolve_base_specifier_spelling(base_spec, substitutions: substitutions)
end

#resolve_base_specifier_spelling(base_specifier, substitutions: {}) ⇒ Object

Resolve a template base specifier by rewriting the written source with semantic names and any current template-parameter substitutions.

Examples:

substitutions = { 'T' => 'unsigned char' }
source        = 'BasePtr<T>'
=> 'Tests::BasePtr<unsigned char>'

substitutions = { 'Result' => 'void', 'Left' => 'int', 'Right' => 'int' }
source        = 'CallbackBase<Result (*)(Left, Right)>'
=> 'Tests::CallbackBase<void (*)(int, int)>'


304
305
306
307
308
309
310
311
312
313
314
315
316
317
# File 'lib/ruby-bindgen/generators/rice/template_resolver.rb', line 304

def resolve_base_specifier_spelling(base_specifier, substitutions: {})
  base_template_ref = base_specifier.find_first_by_kind(false, :cursor_template_ref)
  
  unless base_template_ref
    base_type_ref = base_specifier.find_first_by_kind(false, :cursor_type_ref)
    return base_type_ref&.referenced&.qualified_name
  end
  
  source = base_specifier_source(base_specifier, base_template_ref)
  return nil unless source
  
  source_text, source_offset = source
  @reference_qualifier.qualify_source_references(base_specifier, source_text, source_offset, substitutions: substitutions)
end

#ruby_name_from_template(base_spelling, template_arguments) ⇒ Object

Generate Ruby class name from a C++ template instantiation spelling e.g., “Tests::Matx<unsigned char, 2, 1>” -> “MatxUnsignedChar21”



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

def ruby_name_from_template(base_spelling, template_arguments)
  base_name = base_spelling.sub(/<.*>\z/, "").split("::").last.camelize
  argument_values = template_arguments.is_a?(Array) ? template_arguments : template_argument_texts(template_arguments)
  args_name = argument_values.map { |argument| ruby_name_from_template_argument(argument) }.join
  @namer.apply_rename_types(base_name + args_name)
end

#specialization_spelling(specialization_cursor, specialized_type, template_cursor) ⇒ Object

Build the C++ specialization spelling for a typedef/alias specialization using the semantic template cursor plus the written template arguments.

This is narrower than ‘full_template_arguments`: the Data_Type<T> side should preserve the number of arguments written at the use site rather than eagerly expanding omitted defaults. When libclang does not expose a complete argument list for a non-type specialization, fall back to the type speller’s direct output.

Examples:

typedef FunctionTemplate<callback_ints> FunctionTemplateCallback;
=> Tests::FunctionTemplate<&Tests::callback_ints>

typedef MultiDefault<int> MultiDefaultInt;
=> MultiDefault<int>


67
68
69
70
71
72
73
74
75
76
# File 'lib/ruby-bindgen/generators/rice/template_resolver.rb', line 67

def specialization_spelling(specialization_cursor, specialized_type, template_cursor)
  return @type_speller.type_spelling(specialized_type) unless template_cursor
  
  actual_args = specialization_template_arguments(specialization_cursor, specialized_type, template_cursor)
  count = specialized_type.num_template_arguments
  return @type_speller.type_spelling(specialized_type) if count <= 0
  return @type_speller.type_spelling(specialized_type) unless actual_args.length == count
  
  "#{template_cursor.qualified_name}<#{actual_args.join(', ')}>"
end

#template_argument_list_text(spelling) ⇒ Object

Extract only the outer template argument list from a fully resolved instantiation spelling.

Examples:

'Tests::Matx<unsigned char, 2, 1>'
=> 'unsigned char, 2, 1'

'Tests::CallbackBase<void (*)(int, int)>'
=> 'void (*)(int, int)'


240
241
242
243
244
245
246
247
248
# File 'lib/ruby-bindgen/generators/rice/template_resolver.rb', line 240

def template_argument_list_text(spelling)
  start_index = spelling.index('<')
  return nil unless start_index
  
  suffix, = balanced_template_suffix(spelling, start_index)
  return nil if suffix.empty?
  
  suffix[1..-2]
end

#template_argument_texts(args_text) ⇒ Object

Split a comma-separated template argument list while keeping nested templates, function pointer signatures, and string literals intact.

Examples:

'unsigned char, 2, 1'
=> ['unsigned char', '2', '1']

'void (*)(int, int)'
=> ['void (*)(int, int)']

'Support::Box<Support::Tag>, callback_t'
=> ['Support::Box<Support::Tag>', 'callback_t']


171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
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
# File 'lib/ruby-bindgen/generators/rice/template_resolver.rb', line 171

def template_argument_texts(args_text)
  return [] if args_text.nil? || args_text.empty?
  
  result = []
  current = String.new
  angle_depth = 0
  paren_depth = 0
  bracket_depth = 0
  brace_depth = 0
  quote = nil
  escaped = false
  
  args_text.each_char do |char|
    current << char
  
    if quote
      if escaped
        escaped = false
      elsif char == '\\'
        escaped = true
      elsif char == quote
        quote = nil
      end
      next
    end
  
    case char
    when '"', "'"
      quote = char
    when '<'
      angle_depth += 1
    when '>'
      angle_depth -= 1 if angle_depth > 0
    when '('
      paren_depth += 1
    when ')'
      paren_depth -= 1 if paren_depth > 0
    when '['
      bracket_depth += 1
    when ']'
      bracket_depth -= 1 if bracket_depth > 0
    when '{'
      brace_depth += 1
    when '}'
      brace_depth -= 1 if brace_depth > 0
    when ','
      if angle_depth.zero? && paren_depth.zero? && bracket_depth.zero? && brace_depth.zero?
        current.chop!
        piece = current.strip
        result << piece unless piece.empty?
        current = String.new
      end
    end
  end
  
  piece = current.strip
  result << piece unless piece.empty?
  result
end

#template_parameter_argument(template_parameter) ⇒ Object



142
143
144
145
146
147
148
149
150
# File 'lib/ruby-bindgen/generators/rice/template_resolver.rb', line 142

def template_parameter_argument(template_parameter)
  name = template_parameter.spelling
  return name if name.empty?
  
  declaration = template_parameter.extent.text
  return name unless declaration&.match?(/\.\.\.\s*#{Regexp.escape(name)}\b/)
  
  "#{name}..."
end

#template_parameter_signature(template_parameter) ⇒ Object

Render a class template parameter for the instantiate helper’s own template declaration.

Examples:

`typename T`

stays

`typename T`

`int N = 4`

becomes

`int N`

`void (*Fn)(int, int)`

stays

`void (*Fn)(int, int)`

`template<typename> class Container = Box`

becomes

`template<typename> class Container`

`template<typename U = int> class Container = Box`

becomes

`template<typename U = int> class Container`


108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/ruby-bindgen/generators/rice/template_resolver.rb', line 108

def template_parameter_signature(template_parameter)
  case template_parameter.kind
  when :cursor_template_type_parameter
    declaration = template_parameter.extent.text
    declaration = nil unless usable_template_parameter_declaration?(template_parameter, declaration)
    return "typename #{template_parameter_argument(template_parameter)}" if declaration.nil? || declaration.empty?
  
    separator_offset = @reference_qualifier.top_level_default_separator_offset(declaration)
    signature = separator_offset ? declaration.byteslice(0, separator_offset).rstrip : declaration.rstrip
  
    signature.sub(/\A(\s*)class\b/, '\1typename')
  when :cursor_non_type_template_parameter
    declaration = template_parameter.extent.text
    declaration = nil unless usable_template_parameter_declaration?(template_parameter, declaration)
    return "int #{template_parameter.spelling}" if declaration.nil? || declaration.empty?
  
    separator_offset = @reference_qualifier.top_level_default_separator_offset(declaration)
    return declaration.rstrip unless separator_offset
  
    declaration.byteslice(0, separator_offset).rstrip
  when :cursor_template_template_parameter
    declaration = template_parameter.extent.text
    declaration = nil unless usable_template_parameter_declaration?(template_parameter, declaration)
    return "template<typename> class #{template_parameter.spelling}" if declaration.nil? || declaration.empty?
  
    separator_offset = @reference_qualifier.top_level_default_separator_offset(declaration)
    return declaration.rstrip unless separator_offset
  
    declaration.byteslice(0, separator_offset).rstrip
  else
    raise("Unsupported template parameter kind: #{template_parameter.kind}")
  end
end

#template_parameters(template_cursor) ⇒ Object



78
79
80
81
82
83
# File 'lib/ruby-bindgen/generators/rice/template_resolver.rb', line 78

def template_parameters(template_cursor)
  template_parameter_kinds = [:cursor_template_type_parameter,
                              :cursor_non_type_template_parameter,
                              :cursor_template_template_parameter]
  template_cursor.find_by_kind(false, *template_parameter_kinds).to_a
end

#usable_template_parameter_declaration?(template_parameter, declaration) ⇒ Boolean

Returns:

  • (Boolean)


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

def usable_template_parameter_declaration?(template_parameter, declaration)
  return false if declaration.nil? || declaration.empty?
  
  parent_declaration = template_parameter.semantic_parent&.extent&.text
  declaration != parent_declaration
end