Module: MilkTea::Bindgen::Generator::GeneratorDeclaration

Included in:
MilkTea::Bindgen::Generator
Defined in:
lib/milk_tea/bindings/bindgen/declaration.rb

Instance Method Summary collapse

Instance Method Details

#alias_qual_type(node) ⇒ Object



129
130
131
# File 'lib/milk_tea/bindings/bindgen/declaration.rb', line 129

def alias_qual_type(node)
  type_qual_type(node)
end

#constant_expression_kind(node) ⇒ Object



259
260
261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/milk_tea/bindings/bindgen/declaration.rb', line 259

def constant_expression_kind(node)
  current = node

  while current.is_a?(Hash)
    case current["kind"]
    when "ImplicitCastExpr", "ConstantExpr", "CompoundLiteralExpr", "ParenExpr"
      current = Array(current["inner"]).first
    else
      return current["kind"]
    end
  end

  nil
end

#constant_qual_type(node) ⇒ Object



279
280
281
282
283
284
285
286
# File 'lib/milk_tea/bindings/bindgen/declaration.rb', line 279

def constant_qual_type(node)
  qual_type = node.dig("type", "qualType")
  if macro_probe_declaration?(node) && qual_type.to_s.include?("typeof")
    node.dig("type", "desugaredQualType") || qual_type
  else
    qual_type
  end
end

#constant_var_decl?(node) ⇒ Boolean

Returns:

  • (Boolean)


274
275
276
277
# File 'lib/milk_tea/bindings/bindgen/declaration.rb', line 274

def constant_var_decl?(node)
  qual_type = node.dig("type", "qualType")
  node["init"] && strip_qualifiers(normalize_c_type(qual_type)) != normalize_c_type(qual_type)
end

#enum_kind(node) ⇒ Object



303
304
305
306
307
308
309
# File 'lib/milk_tea/bindings/bindgen/declaration.rb', line 303

def enum_kind(node)
  values = enum_member_values(node).map { |member| member[:value] }
  non_zero = values.reject(&:zero?)
  return "enum" if non_zero.empty?

  non_zero.all? { |value| power_of_two?(value) } ? "flags" : "enum"
end

#function_return_type(node) ⇒ Object

Raises:



288
289
290
291
292
293
294
# File 'lib/milk_tea/bindings/bindgen/declaration.rb', line 288

def function_return_type(node)
  qual_type = type_qual_type(node)
  match = qual_type&.match(/\A(.+?)\s*\((?:.*)\)\z/)
  raise BindgenError, "unsupported function type for #{node["name"]}: #{qual_type.inspect}" unless match

  match[1]
end

#macro_string_constant_type(node, initializer, context:) ⇒ Object

Raises:



248
249
250
251
252
253
254
255
256
257
# File 'lib/milk_tea/bindings/bindgen/declaration.rb', line 248

def macro_string_constant_type(node, initializer, context:)
  return nil unless macro_probe_declaration?(node)
  return nil unless constant_expression_kind(initializer) == "StringLiteral"

  qual_type = normalize_c_type(constant_qual_type(node))
  qual_type, = extract_top_level_nullability(qual_type)
  return "cstr" if string_literal_macro_compatible_c_type?(qual_type)

  raise BindgenError, "unsupported string macro type #{qual_type.inspect} for #{context}"
end

#power_of_two?(value) ⇒ Boolean

Returns:

  • (Boolean)


311
312
313
# File 'lib/milk_tea/bindings/bindgen/declaration.rb', line 311

def power_of_two?(value)
  value.positive? && (value & (value - 1)).zero?
end

#record_complete_definition?(node) ⇒ Boolean

Returns:

  • (Boolean)


70
71
72
73
74
# File 'lib/milk_tea/bindings/bindgen/declaration.rb', line 70

def record_complete_definition?(node)
  return true if node["completeDefinition"]

  Array(node["inner"]).any? { |child| child["kind"] == "FieldDecl" }
end

#select_constant_declarations(nodes) ⇒ Object



220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/milk_tea/bindings/bindgen/declaration.rb', line 220

def select_constant_declarations(nodes)
  nodes.each_with_index.filter_map do |node, index|
    next unless node["kind"] == "VarDecl"
    next if node["isInvalid"]
    next unless constant_var_decl?(node)
    next unless allowed_declaration_name?(constant_name_for(node))

    begin
      initializer = Array(node["inner"]).first
      type = macro_string_constant_type(node, initializer, context: constant_name_for(node))
      type ||= map_c_type(constant_qual_type(node), context: constant_name_for(node))
      value = lower_constant_expression(initializer, expected_type: type, context: constant_name_for(node))
    rescue BindgenError
      raise unless macro_probe_declaration?(node)

      next
    end

    {
      index:,
      kind: "const",
      name: constant_name_for(node),
      type:,
      value:,
    }
  end
end

#select_enum_declarations(nodes) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/milk_tea/bindings/bindgen/declaration.rb', line 76

def select_enum_declarations(nodes)
  selected = {}

  nodes.each_with_index do |node, index|
    next unless node["kind"] == "EnumDecl"

    original_name = @enum_aliases[node["id"]] || node["name"]
    next unless original_name
    next unless allowed_declaration_name?(original_name)

    visible_name = visible_type_name(original_name)

    selected[visible_name] = { index:, kind: enum_kind(node), name: visible_name, node: }
  end

  @enum_visible_names = {}
  selected.each_value do |declaration|
    tag_name = declaration[:node]["name"]
    @enum_visible_names[tag_name] = declaration[:name] if tag_name
    @enum_visible_names[declaration[:name]] = declaration[:name]
  end
  selected.values
end

#select_function_declarations(nodes) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/milk_tea/bindings/bindgen/declaration.rb', line 133

def select_function_declarations(nodes)
  selected = {}

  nodes.each_with_index do |node, index|
    next unless node["kind"] == "FunctionDecl"
    has_body = Array(node["inner"]).any? { |child| child["kind"] == "CompoundStmt" }
    next if node["storageClass"] == "static" && !@allow_static_inline_functions
    next if has_body && !(node["storageClass"] == "static" && @allow_static_inline_functions)
    next unless allowed_declaration_name?(node["name"])

    params = Array(node["inner"]).select { |child| child["kind"] == "ParmVarDecl" }.each_with_index.map do |param, param_index|
      param_name = param["name"] || "arg#{param_index}"
      override_type = function_param_type_override(node["name"], param_name)
      record_nullable_param_override(node["name"], param_name, param, override_type) if override_type
      {
        name: param_name,
        type: override_type || map_type_node(param, context: "parameter #{param_name} of #{node["name"]}"),
      }
    end

    override_return_type = function_return_type_override(node["name"])
    record_nullable_return_override(node, override_return_type) if override_return_type
    return_type = override_return_type || map_c_type(function_return_type(node), context: "return type of #{node["name"]}")
    declaration = {
      index:,
      kind: "function",
      name: node["name"],
      params:,
      variadic: node["variadic"],
      return_type:,
    }
    selected[declaration[:name]] ||= declaration
  rescue BindgenError
    next
  end

  selected.values
end

#select_record_declarations(nodes) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/milk_tea/bindings/bindgen/declaration.rb', line 7

def select_record_declarations(nodes)
  selected = {}
  type_aliases = []

  nodes.each_with_index do |node, index|
    record_node, original_name = case node["kind"]
    when "RecordDecl"
      [node, @record_aliases[node["id"]] || node["name"]]
    when "TypedefDecl"
      target = typedef_target(node)
      next unless target&.fetch(:kind, nil) == "RecordDecl"

      record_node = @referenceable_record_declarations_by_id[target[:id]] || @referenceable_record_declarations[target[:name]]
      next unless record_node

      primary_alias = @record_aliases[target[:id]]
      if primary_alias && primary_alias != node["name"] && allowed_declaration_name?(node["name"])
        type_alias_name = visible_type_name(node["name"])
        type_alias_target = visible_type_name(primary_alias)
        unless type_aliases.any? { |ta| ta[:name] == type_alias_name }
          type_aliases << { index:, kind: "type_alias", name: type_alias_name, mapped_type: type_alias_target }
        end
        next
      end

      [record_node, node["name"]]
    else
      next
    end

    next unless %w[struct union].include?(record_node["tagUsed"])

    next unless original_name
    next unless allowed_declaration_name?(original_name)

    visible_name = visible_type_name(original_name)

    candidate = {
      index:,
      kind: record_complete_definition?(record_node) ? record_node["tagUsed"] : "opaque",
      name: visible_name,
      linkage_name: record_c_name(record_node),
      node: record_node,
    }

    existing = selected[visible_name]
    if existing.nil? || (existing[:kind] == "opaque" && candidate[:kind] != "opaque")
      selected[visible_name] = candidate
    end
  end

  declarations = selected.values + type_aliases

  @record_visible_names = {}
  selected.each_value do |declaration|
    tag_name = declaration[:node]["name"]
    @record_visible_names[tag_name] = declaration[:name] if tag_name
    @record_visible_names[declaration[:name]] = declaration[:name]
    @aggregate_declarations[declaration[:name]] = declaration[:node] if %w[struct union].include?(declaration[:kind])
  end
  declarations
end

#select_type_alias_declarations(nodes) ⇒ Object



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/milk_tea/bindings/bindgen/declaration.rb', line 100

def select_type_alias_declarations(nodes)
  alias_names = nodes.filter_map { |node| node["name"] if node["kind"] == "TypedefDecl" }

  nodes.each_with_index.filter_map do |node, index|
    next unless node["kind"] == "TypedefDecl"
    next if typedef_target(node)
    next unless allowed_declaration_name?(node["name"])

    qual_type = alias_qual_type(node)
    mapped_type = if extract_function_proto(node)
                    map_function_pointer_typedef(node, context: node["name"])
                  else
                    map_c_type(qual_type, context: node["name"])
                  end
    next if node["name"] == mapped_type
    next if unresolved_alias_target?(mapped_type, alias_names)

    {
      index:,
      kind: "type_alias",
      name: visible_type_name(node["name"]),
      mapped_type:,
    }
  rescue BindgenError
    nil
  end

end

#type_qual_type(node) ⇒ Object



296
297
298
299
300
301
# File 'lib/milk_tea/bindings/bindgen/declaration.rb', line 296

def type_qual_type(node)
  qual_type = node.dig("type", "qualType")
  return qual_type if qual_type.to_s.match?(/_Nullable|_Nonnull|_Null_unspecified|_Nullable_result/)

  node.dig("type", "desugaredQualType") || qual_type
end

#validate_field_type_overrides!(record_declarations) ⇒ Object



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/milk_tea/bindings/bindgen/declaration.rb', line 202

def validate_field_type_overrides!(record_declarations)
  return if @field_type_overrides.empty?

  declarations_by_name = record_declarations.to_h { |declaration| [declaration[:name], declaration] }

  @field_type_overrides.each do |type_name, field_overrides|
    declaration = declarations_by_name[type_name]
    raise BindgenError, "field_type_overrides references unknown type #{type_name} for #{@header_path}" unless declaration

    field_names = Array(declaration[:node]["inner"]).select { |child| child["kind"] == "FieldDecl" }.map { |field| field["name"] }
    field_overrides.each_key do |field_name|
      next if field_names.include?(field_name)

      raise BindgenError, "field_type_overrides references unknown field #{type_name}.#{field_name} for #{@header_path}"
    end
  end
end

#validate_function_param_type_overrides!(function_declarations) ⇒ Object



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/milk_tea/bindings/bindgen/declaration.rb', line 172

def validate_function_param_type_overrides!(function_declarations)
  return if @function_param_type_overrides.empty?

  declarations_by_name = function_declarations.to_h { |declaration| [declaration[:name], declaration] }

  @function_param_type_overrides.each do |function_name, param_overrides|
    declaration = declarations_by_name[function_name]
    raise BindgenError, "function_param_type_overrides references unknown function #{function_name} for #{@header_path}" unless declaration

    param_names = declaration[:params].map { |param| param[:name] }
    param_overrides.each_key do |param_name|
      next if param_names.include?(param_name)

      raise BindgenError, "function_param_type_overrides references unknown parameter #{function_name}.#{param_name} for #{@header_path}"
    end
  end
end

#validate_function_return_type_overrides!(function_declarations) ⇒ Object



190
191
192
193
194
195
196
197
198
199
200
# File 'lib/milk_tea/bindings/bindgen/declaration.rb', line 190

def validate_function_return_type_overrides!(function_declarations)
  return if @function_return_type_overrides.empty?

  declarations_by_name = function_declarations.to_h { |declaration| [declaration[:name], declaration] }

  @function_return_type_overrides.each_key do |function_name|
    next if declarations_by_name.key?(function_name)

    raise BindgenError, "function_return_type_overrides references unknown function #{function_name} for #{@header_path}"
  end
end