qt 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,12 +4,52 @@ def unsupported_cpp_type?(type_name)
4
4
  type_name.include?('<') || type_name.include?('>') || type_name.include?('[') || type_name.include?('(')
5
5
  end
6
6
 
7
- def map_cpp_pointer_arg_type(type_name, qt_class)
7
+ def ast_nested_types_by_class(ast)
8
+ @ast_nested_types_cache ||= {}.compare_by_identity
9
+ @ast_nested_types_cache[ast] ||= begin
10
+ nested = Hash.new { |hash, key| hash[key] = Set.new }
11
+ walk = lambda do |node, record_stack|
12
+ next unless node.is_a?(Hash)
13
+
14
+ parent_record = record_stack.last
15
+ nested_type_kind = %w[CXXRecordDecl EnumDecl TypedefDecl TypeAliasDecl].include?(node['kind'])
16
+ if nested_type_kind && parent_record && node['name'] && node['name'] != parent_record['name'] &&
17
+ (node['parentDeclContextId'].nil? || node['parentDeclContextId'] == parent_record['id'])
18
+ nested[parent_record['name']] << node['name']
19
+ end
20
+
21
+ child_stack = if node['kind'] == 'CXXRecordDecl' && node['name']
22
+ record_stack + [node]
23
+ else
24
+ record_stack
25
+ end
26
+ Array(node['inner']).each { |child| walk.call(child, child_stack) }
27
+ end
28
+ walk.call(ast, [])
29
+ nested
30
+ end
31
+ end
32
+
33
+ def ast_nested_type_owner(ast, qt_class, nested_name, visited = {})
34
+ return nil if qt_class.nil? || visited[qt_class]
35
+
36
+ visited[qt_class] = true
37
+ return qt_class if ast_nested_types_by_class(ast)[qt_class].include?(nested_name)
38
+
39
+ collect_class_bases(ast, qt_class).each do |base_class|
40
+ owner = ast_nested_type_owner(ast, base_class, nested_name, visited)
41
+ return owner if owner
42
+ end
43
+ nil
44
+ end
45
+
46
+ def map_cpp_pointer_arg_type(type_name, qt_class, ast:)
8
47
  return nil unless type_name.end_with?('*')
9
48
 
10
49
  base = type_name.sub(/\s*\*\z/, '').strip
11
- if qt_class && !base.include?('::') && base.match?(/\A[A-Z]\w*\z/) && !base.start_with?('Q')
12
- base = "#{qt_class}::#{base}"
50
+ if qt_class && !base.include?('::') && base.match?(/\A[A-Z]\w*\z/)
51
+ owner = ast_nested_type_owner(ast, qt_class, base)
52
+ base = "#{owner}::#{base}" if owner
13
53
  end
14
54
  { ffi: :pointer, cast: "#{base}*" }
15
55
  end
@@ -21,26 +61,46 @@ def map_builtin_intlike_arg_type(type_name)
21
61
  nil
22
62
  end
23
63
 
24
- def map_qualified_intlike_arg_type(type_name, qt_class, int_cast_types)
25
- return nil unless qt_class && type_name.match?(/\A[A-Z]\w*\z/)
64
+ def inherited_intlike_type_name(type_name, qt_class, ast, int_cast_types, visited = {})
65
+ return nil if qt_class.nil? || visited[qt_class]
26
66
 
67
+ visited[qt_class] = true
27
68
  qualified = "#{qt_class}::#{type_name}"
28
- return { ffi: :int, cast: qualified } if int_cast_types&.include?(qualified)
69
+ return qualified if int_cast_types&.include?(qualified)
70
+
71
+ collect_class_bases(ast, qt_class).each do |base_class|
72
+ inherited = inherited_intlike_type_name(type_name, base_class, ast, int_cast_types, visited)
73
+ return inherited if inherited
74
+ end
75
+ nil
76
+ end
77
+
78
+ def map_qualified_intlike_arg_type(type_name, qt_class, int_cast_types, ast:)
79
+ return nil unless qt_class && type_name.match?(/\A[A-Z]\w*\z/)
80
+
81
+ qualified = inherited_intlike_type_name(type_name, qt_class, ast, int_cast_types)
82
+ return { ffi: :int, cast: qualified } if qualified
29
83
 
30
84
  nil
31
85
  end
32
86
 
33
- def map_cpp_intlike_arg_type(type_name, qt_class, int_cast_types)
87
+ def map_cpp_intlike_arg_type(type_name, qt_class, int_cast_types, ast:)
34
88
  builtin = map_builtin_intlike_arg_type(type_name)
35
89
  return builtin if builtin
36
90
  return { ffi: :int, cast: type_name } if type_name.include?('::') && int_cast_types&.include?(type_name)
37
91
 
38
- map_qualified_intlike_arg_type(type_name, qt_class, int_cast_types)
92
+ map_qualified_intlike_arg_type(type_name, qt_class, int_cast_types, ast: ast)
39
93
  end
40
94
 
41
95
  def map_cpp_arg_type(type_name, qt_class: nil, int_cast_types: nil, ast: nil)
42
96
  raw = type_name.to_s.strip
43
- return nil if raw.end_with?('&') && !raw.start_with?('const ')
97
+ if raw.end_with?('&') && !raw.start_with?('const ')
98
+ mutable_reference_type = normalized_cpp_type_name(raw)
99
+ return nil if SCALAR_BRIDGED_QT_TYPES.include?(mutable_reference_type) || %w[QCursor QIcon].include?(mutable_reference_type)
100
+ return nil unless ast_qt_value_class?(ast, mutable_reference_type)
101
+
102
+ return { ffi: :pointer, cast: :qt_value_ref, value_class: mutable_reference_type }
103
+ end
44
104
 
45
105
  compact_raw = raw.gsub(/\s+/, ' ')
46
106
 
@@ -64,7 +124,7 @@ def map_cpp_arg_type(type_name, qt_class: nil, int_cast_types: nil, ast: nil)
64
124
 
65
125
  return nil if unsupported_cpp_type?(type)
66
126
 
67
- map_cpp_pointer_arg_type(type, qt_class) || map_cpp_intlike_arg_type(type, qt_class, int_cast_types)
127
+ map_cpp_pointer_arg_type(type, qt_class, ast: ast) || map_cpp_intlike_arg_type(type, qt_class, int_cast_types, ast: ast)
68
128
  end
69
129
 
70
130
  def normalized_cpp_type_name(type_name)
@@ -74,14 +134,21 @@ def normalized_cpp_type_name(type_name)
74
134
  type
75
135
  end
76
136
 
77
- def map_cpp_return_type(type_name, ast: nil)
137
+ def map_cpp_return_type(type_name, ast: nil, qt_class: nil)
78
138
  raw = type_name.to_s.strip
79
139
  type = raw.sub(/\Aconst\s+/, '').sub(/\s*&\z/, '').strip
80
140
  return { ffi_return: :string, return_cast: :qint_list_to_variant } if type == 'QList<int>'
81
141
  return nil if unsupported_cpp_type?(raw)
82
142
  return nil if raw.start_with?('const ') && raw.end_with?('*')
83
143
 
84
- map_scalar_cpp_return_type(type) || map_pointer_cpp_return_type(type, ast: ast) || map_qt_value_return_type(type, ast: ast)
144
+ scalar = map_scalar_cpp_return_type(type)
145
+ return scalar if scalar
146
+
147
+ int_cast_types = ast ? ast_int_cast_type_set(ast) : nil
148
+ intlike = map_cpp_intlike_arg_type(type, qt_class, int_cast_types, ast: ast)
149
+ return { ffi_return: :int, return_cast: :int_cast } if intlike
150
+
151
+ map_pointer_cpp_return_type(type, ast: ast) || map_qt_value_return_type(type, ast: ast)
85
152
  end
86
153
 
87
154
  def ast_qt_value_class?(ast, type)
@@ -102,6 +169,7 @@ def map_scalar_cpp_return_type(type)
102
169
  return { ffi_return: :int } if type == 'int'
103
170
  return { ffi_return: :bool } if type == 'bool'
104
171
  return { ffi_return: :string, return_cast: :qstring_to_utf8 } if type == 'QString'
172
+ return { ffi_return: :string, return_cast: :qstring_list_to_json } if type == 'QStringList'
105
173
  return { ffi_return: :string, return_cast: :qobject_list_to_wrapped_array, object_list_class: 'QObject' } if type == 'QObjectList'
106
174
  return { ffi_return: :string, return_cast: :qdatetime_to_utf8 } if type == 'QDateTime'
107
175
  return { ffi_return: :string, return_cast: :qdate_to_utf8 } if type == 'QDate'
@@ -175,7 +243,8 @@ def build_auto_method_args(parsed, entry, qt_class, int_cast_types, ast)
175
243
  name: param[:name],
176
244
  ffi: arg_info[:ffi],
177
245
  cast: cast_override || arg_info[:cast],
178
- value_class: arg_info[:value_class]
246
+ value_class: arg_info[:value_class],
247
+ cpp_type: param[:type]
179
248
  }.compact
180
249
  required_arg_count += 1 unless param[:has_default]
181
250
  end
@@ -197,6 +266,7 @@ def build_auto_method_hash(entry, ret_info, args, required_arg_count)
197
266
  args: args,
198
267
  required_arg_count: required_arg_count
199
268
  }
269
+ method[:static] = entry[:static] if entry.key?(:static)
200
270
  method[:return_cast] = ret_info[:return_cast] if ret_info[:return_cast]
201
271
  method[:pointer_class] = ret_info[:pointer_class] if ret_info[:pointer_class]
202
272
  method[:value_class] = ret_info[:value_class] if ret_info[:value_class]
@@ -207,7 +277,7 @@ def build_auto_method_from_decl(method_decl, entry, qt_class:, int_cast_types:,
207
277
  parsed = parse_method_signature(method_decl)
208
278
  return nil unless parsed
209
279
 
210
- ret_info = map_cpp_return_type(parsed[:return_type], ast: ast)
280
+ ret_info = map_cpp_return_type(parsed[:return_type], ast: ast, qt_class: qt_class)
211
281
  return nil unless ret_info
212
282
 
213
283
  args, required_arg_count = build_auto_method_args(parsed, entry, qt_class, int_cast_types, ast)
@@ -263,8 +333,15 @@ def build_auto_method_candidate(ast, decl, entry, qt_class, int_cast_types)
263
333
  parsed = parse_method_signature(decl)
264
334
  return nil unless parsed
265
335
 
266
- method = build_auto_method_from_decl(decl, entry, qt_class: qt_class, int_cast_types: int_cast_types, ast: ast)
336
+ method_entry = entry.merge(static: decl['storageClass'] == 'static')
337
+ method = build_auto_method_from_decl(decl, method_entry, qt_class: qt_class, int_cast_types: int_cast_types, ast: ast)
267
338
  return nil unless method
339
+ method[:owner_class] = qt_class if method[:static]
340
+ method[:cpp_return_type] = parsed[:return_type]
341
+ method[:virtual] = virtual_method_declaration?(ast, decl)
342
+ method[:cpp_const] = decl.dig('type', 'qualType').to_s.match?(/\)\s*const\b/)
343
+ method[:cpp_params] = parsed[:params].map { |param| param[:type] }
344
+ method[:final] = final_method_declaration?(ast, decl)
268
345
 
269
346
  {
270
347
  method: method,
@@ -272,6 +349,77 @@ def build_auto_method_candidate(ast, decl, entry, qt_class, int_cast_types)
272
349
  }
273
350
  end
274
351
 
352
+ def method_declaration_signature_key(decl)
353
+ parsed = parse_method_signature(decl)
354
+ return nil unless parsed
355
+
356
+ params = parsed[:params].map do |param|
357
+ param[:type].to_s.gsub(/\s+/, ' ').gsub(/\s*([*&])\s*/, '\\1').strip
358
+ end
359
+ const_method = decl.dig('type', 'qualType').to_s.match?(/\)\s*const\b/)
360
+ [params, const_method]
361
+ end
362
+
363
+ def method_declaration_has_attribute?(decl, attribute)
364
+ if attribute == 'OverrideAttr'
365
+ return decl['virtual'] == true || Array(decl['inner']).any? { |node| node['kind'] == attribute }
366
+ end
367
+
368
+ Array(decl['inner']).any? { |node| node['kind'] == attribute }
369
+ end
370
+
371
+ def inherited_method_attribute?(ast, owner_class, method_name, signature_key, attribute, visited = {})
372
+ return false if owner_class.nil? || visited[owner_class]
373
+
374
+ visited[owner_class] = true
375
+ collect_class_bases(ast, owner_class).any? do |base_class|
376
+ matching_declaration = collect_method_decls(ast, base_class, method_name).find do |candidate|
377
+ method_declaration_signature_key(candidate) == signature_key
378
+ end
379
+ next true if matching_declaration && method_declaration_has_attribute?(matching_declaration, attribute)
380
+ next false unless matching_declaration
381
+
382
+ inherited_method_attribute?(
383
+ ast,
384
+ base_class,
385
+ method_name,
386
+ signature_key,
387
+ attribute,
388
+ visited
389
+ )
390
+ end
391
+ end
392
+
393
+ def method_inherits_attribute?(ast, decl, attribute)
394
+ return true if method_declaration_has_attribute?(decl, attribute)
395
+
396
+ owner_class = decl['__owner_class']
397
+ signature_key = method_declaration_signature_key(decl)
398
+ return false unless owner_class && signature_key
399
+
400
+ cache = (@inherited_method_attribute_cache ||= {}.compare_by_identity)
401
+ per_ast = (cache[ast] ||= {})
402
+ cache_key = [owner_class, decl['name'], signature_key, attribute]
403
+ return per_ast[cache_key] if per_ast.key?(cache_key)
404
+
405
+ per_ast[cache_key] = inherited_method_attribute?(
406
+ ast,
407
+ owner_class,
408
+ decl['name'],
409
+ signature_key,
410
+ attribute
411
+ )
412
+ end
413
+
414
+ def virtual_method_declaration?(ast, decl)
415
+ method_inherits_attribute?(ast, decl, 'OverrideAttr') ||
416
+ decl['virtual'] == true
417
+ end
418
+
419
+ def final_method_declaration?(ast, decl)
420
+ method_inherits_attribute?(ast, decl, 'FinalAttr')
421
+ end
422
+
275
423
  def auto_method_decl_candidate?(decl)
276
424
  return false unless decl['__effective_access'] == 'public'
277
425
  return false if deprecated_method_decl?(decl)
@@ -378,11 +526,13 @@ def resolve_auto_method(ast, qt_class, auto_entry)
378
526
  built = resolve_auto_method_built_candidates(ast, qt_class, entry)
379
527
  return per_ast_cache[cache_key] = nil unless built
380
528
 
381
- per_ast_cache[cache_key] = built.min_by do |candidate|
529
+ methods = built.uniq { |candidate| candidate[:param_types] }
530
+ .sort_by do |candidate|
382
531
  method = candidate[:method]
383
532
  value_arg_count = method[:args].count { |arg| arg[:cast] == :qt_value_ref }
384
- [value_arg_count, method[:args].length]
385
- end[:method]
533
+ [method[:required_arg_count], -value_arg_count, method[:args].length, candidate[:param_types]]
534
+ end.map { |candidate| candidate[:method] }
535
+ per_ast_cache[cache_key] = methods
386
536
  end
387
537
 
388
538
  def auto_entries_for_spec(spec, ast)
@@ -401,9 +551,10 @@ def resolve_auto_methods_for_spec(ast, spec, auto_entries, manual_methods, auto_
401
551
  resolver = build_auto_method_resolver(ast, spec, manual_methods, auto_mode)
402
552
  spec_resolved = 0
403
553
  spec_skipped = 0
404
- auto_methods = auto_entries.filter_map do |entry|
554
+ auto_methods = []
555
+ auto_entries.each do |entry|
405
556
  method, spec_skipped, spec_resolved = resolve_with_resolver(resolver, entry, spec_skipped, spec_resolved)
406
- method
557
+ auto_methods.concat(Array(method))
407
558
  end
408
559
 
409
560
  [auto_methods, spec_resolved, spec_skipped]
@@ -12,6 +12,7 @@ class CppMethodReturnEmitter
12
12
  return emit_void if method[:ffi_return] == :void
13
13
  return emit_qt_value_copy if method[:return_cast] == :qt_value_copy
14
14
  return emit_qint_list if method[:return_cast] == :qint_list_to_variant
15
+ return emit_qstring_list if method[:return_cast] == :qstring_list_to_json
15
16
  return emit_qstring if qstring_return?
16
17
  return emit_qobject_list if qobject_list_return?
17
18
  return emit_qdatetime if qdatetime_return?
@@ -19,6 +20,7 @@ class CppMethodReturnEmitter
19
20
  return emit_qtime if qtime_return?
20
21
  return emit_qvariant if qvariant_return?
21
22
  return emit_pointer if method[:ffi_return] == :pointer
23
+ return emit_int_cast if method[:return_cast] == :int_cast
22
24
 
23
25
  emit_value
24
26
  end
@@ -64,6 +66,15 @@ class CppMethodReturnEmitter
64
66
  lines << " return #{utf8}.constData();"
65
67
  end
66
68
 
69
+ def emit_qstring_list
70
+ result = unique_local_name('result')
71
+ utf8 = unique_local_name('utf8')
72
+ lines << " const QStringList #{result} = #{invocation};"
73
+ lines << " thread_local QByteArray #{utf8};"
74
+ lines << " #{utf8} = qstring_list_to_bridge_string(#{result}).toUtf8();"
75
+ lines << " return #{utf8}.constData();"
76
+ end
77
+
67
78
  def emit_qvariant
68
79
  result = unique_local_name('result')
69
80
  utf8 = unique_local_name('utf8')
@@ -132,6 +143,10 @@ class CppMethodReturnEmitter
132
143
  lines << " return #{invocation};"
133
144
  end
134
145
 
146
+ def emit_int_cast
147
+ lines << " return static_cast<int>(#{invocation});"
148
+ end
149
+
135
150
  def unique_local_name(suffix)
136
151
  used = Array(method[:args]).map { |arg| arg[:name].to_s }.to_set
137
152
  candidate = "qt_ruby_#{suffix}"
@@ -58,7 +58,7 @@ end
58
58
 
59
59
  def append_method_ffi_functions(fns, spec)
60
60
  spec[:methods].each do |method|
61
- args = [:pointer] + method[:args].map { |arg| arg[:ffi] }
61
+ args = [:pointer] + (method[:dispatch] ? [:int] : []) + method[:args].map { |arg| arg[:ffi] }
62
62
  fns << { name: method_function_name(spec, method), ffi_return: method[:ffi_return], args: args }
63
63
  end
64
64
  end
@@ -2,24 +2,12 @@
2
2
 
3
3
  RUNTIME_HEADER_PATH = File.expand_path('../../ext/qt_ruby_bridge/qt_ruby_runtime.hpp', __dir__)
4
4
 
5
- SCALAR_CLASS_METHOD_FFI_RETURNS = %i[void int bool string pointer].freeze
6
- QAPPLICATION_STATIC_METHOD_EXCLUSIONS = %w[exec].freeze
7
- QAPPLICATION_STATIC_TEXT_SETTERS = %w[
8
- setApplicationName
9
- setDesktopFileName
10
- setOrganizationName
11
- setApplicationDisplayName
12
- ].freeze
13
-
14
5
  def qt_free_function_specs(ast)
15
6
  @qt_free_function_specs_cache ||= {}.compare_by_identity
16
7
  return @qt_free_function_specs_cache[ast] if @qt_free_function_specs_cache.key?(ast)
17
8
 
18
9
  qt_specs = []
19
10
  qt_specs << qversion_free_function_spec(ast)
20
- qt_specs.concat(qapplication_static_free_function_specs(ast))
21
- qt_specs.concat(qapplication_static_text_setter_specs(ast))
22
- qt_specs << qapplication_top_level_widgets_count_spec(ast)
23
11
  runtime_specs = runtime_free_function_specs_from_header
24
12
  @qt_free_function_specs_cache[ast] = merge_free_function_specs(qt_specs, runtime_specs).freeze
25
13
  end
@@ -140,152 +128,6 @@ def qversion_free_function_spec(ast)
140
128
  }
141
129
  end
142
130
 
143
- def qapplication_top_level_widgets_count_spec(ast)
144
- has_top_level_widgets = collect_method_decls_with_bases(ast, 'QApplication', 'topLevelWidgets').any?
145
- raise 'Unable to find QApplication::topLevelWidgets in AST' unless has_top_level_widgets
146
-
147
- {
148
- name: 'qt_ruby_qapplication_top_level_widgets_count',
149
- ffi_return: :int,
150
- args: [],
151
- cpp_return: 'int',
152
- cpp_args: '',
153
- cpp_body: ['return QApplication::topLevelWidgets().size();'],
154
- qapplication_method: { ruby_name: 'topLevelWidgetsCount', native: 'qapplication_top_level_widgets_count', args: [] }
155
- }
156
- end
157
-
158
- def qapplication_static_free_function_specs(ast)
159
- int_cast_types = ast_int_cast_type_set(ast)
160
- method_names = collect_method_names_with_bases(ast, 'QApplication').uniq
161
- method_names -= QAPPLICATION_STATIC_METHOD_EXCLUSIONS
162
- method_names.filter_map do |qt_name|
163
- build_qapplication_static_free_function_spec(ast, qt_name, int_cast_types)
164
- end
165
- end
166
-
167
- def qapplication_static_text_setter_specs(ast)
168
- QAPPLICATION_STATIC_TEXT_SETTERS.filter_map do |qt_name|
169
- build_qapplication_static_text_setter_spec(ast, qt_name)
170
- end
171
- end
172
-
173
- def build_qapplication_static_text_setter_spec(ast, qt_name)
174
- return nil unless resolve_qapplication_static_text_setter_candidate(ast, qt_name)
175
-
176
- native_name = "qapplication_#{to_snake(qt_name)}"
177
- {
178
- name: "qt_ruby_#{native_name}",
179
- ffi_return: :void,
180
- args: [:string],
181
- cpp_return: 'void',
182
- cpp_args: 'const char* value',
183
- cpp_body: ["QApplication::#{qt_name}(as_qstring(value));"],
184
- qapplication_method: {
185
- ruby_name: qt_name,
186
- native: native_name,
187
- args: [{ name: :value, ffi: :string, cast: :qstring }],
188
- required_arg_count: 1
189
- }
190
- }
191
- end
192
-
193
- def resolve_qapplication_static_text_setter_candidate(ast, qt_name)
194
- decls = collect_method_decls_with_bases(ast, 'QApplication', qt_name)
195
- decls.any? do |decl|
196
- next false unless decl['storageClass'] == 'static'
197
- next false unless decl['__effective_access'] == 'public'
198
-
199
- parsed = parse_method_signature(decl)
200
- next false unless parsed
201
- next false unless parsed[:return_type].to_s.strip == 'void'
202
- next false unless parsed[:required_arg_count] == 1
203
- next false unless parsed[:params].length == 1
204
-
205
- normalized = normalized_cpp_type_name(parsed[:params].first[:type])
206
- normalized == 'QString'
207
- end
208
- end
209
-
210
- def build_qapplication_static_free_function_spec(ast, qt_name, int_cast_types)
211
- candidate = resolve_qapplication_static_noarg_candidate(ast, qt_name, int_cast_types)
212
- return nil unless candidate
213
-
214
- native_name = "qapplication_#{to_snake(qt_name)}"
215
- cpp_body =
216
- if candidate[:ffi_return] == :void
217
- ["QApplication::#{qt_name}();"]
218
- elsif candidate[:return_cast] == :qt_value_copy
219
- ["return new #{candidate[:value_class]}(QApplication::#{qt_name}());"]
220
- elsif candidate[:enum_cast]
221
- ["return static_cast<int>(QApplication::#{qt_name}());"]
222
- elsif candidate[:ffi_return] == :string
223
- [
224
- "const QString value = QApplication::#{qt_name}();",
225
- 'thread_local QByteArray utf8;',
226
- 'utf8 = value.toUtf8();',
227
- 'return utf8.constData();'
228
- ]
229
- else
230
- ["return QApplication::#{qt_name}();"]
231
- end
232
-
233
- {
234
- name: "qt_ruby_#{native_name}",
235
- ffi_return: candidate[:ffi_return],
236
- value_class: candidate[:value_class],
237
- args: [],
238
- cpp_return: ffi_return_to_cpp(candidate[:ffi_return]),
239
- cpp_args: '',
240
- cpp_body: cpp_body,
241
- qapplication_method: {
242
- ruby_name: qt_name,
243
- native: native_name,
244
- args: [],
245
- return_cast: candidate[:return_cast],
246
- pointer_class: candidate[:pointer_class],
247
- value_class: candidate[:value_class]
248
- }
249
- }
250
- end
251
-
252
- def resolve_qapplication_static_noarg_candidate(ast, qt_name, int_cast_types)
253
- decls = collect_method_decls_with_bases(ast, 'QApplication', qt_name)
254
- candidates = decls.filter_map do |decl|
255
- next unless decl['storageClass'] == 'static'
256
- next unless decl['__effective_access'] == 'public'
257
-
258
- parsed = parse_method_signature(decl)
259
- next unless parsed && parsed[:required_arg_count].zero?
260
-
261
- ret_info = qapplication_static_return_info(parsed[:return_type], int_cast_types, ast: ast)
262
- next unless ret_info
263
-
264
- { decl: decl, param_count: parsed[:params].length }.merge(ret_info)
265
- end
266
- return nil if candidates.empty?
267
-
268
- candidates.min_by { |item| item[:param_count] }
269
- end
270
-
271
- def qapplication_static_return_info(return_type, int_cast_types, ast:)
272
- mapped = map_cpp_return_type(return_type, ast: ast)
273
- if mapped && SCALAR_CLASS_METHOD_FFI_RETURNS.include?(mapped[:ffi_return])
274
- return {
275
- ffi_return: mapped[:ffi_return],
276
- return_cast: mapped[:return_cast],
277
- pointer_class: mapped[:pointer_class],
278
- value_class: mapped[:value_class],
279
- enum_cast: false
280
- }
281
- end
282
-
283
- normalized = normalized_cpp_type_name(return_type)
284
- return nil unless normalized.include?('::') && int_cast_types.include?(normalized)
285
-
286
- { ffi_return: :int, return_cast: nil, enum_cast: true }
287
- end
288
-
289
131
  def qapplication_class_method_specs(ast)
290
132
  qt_free_function_specs(ast).filter_map { |spec| spec[:qapplication_method] }
291
133
  end
@@ -12,7 +12,7 @@ def constructor_supports_parent_only?(decl)
12
12
  return false if params.empty?
13
13
 
14
14
  first_type = normalized_cpp_type_name(params.first[:type])
15
- return false unless %w[QWidget* QObject*].include?(first_type)
15
+ return false unless first_type.match?(/\AQ[A-Z]\w*\*\z/)
16
16
 
17
17
  params.drop(1).all? { |param| param[:has_default] }
18
18
  end
@@ -44,7 +44,7 @@ def parent_constructor_first_type(decl)
44
44
  return nil if params.empty?
45
45
 
46
46
  first_type = normalized_cpp_type_name(params.first[:type])
47
- return nil unless %w[QWidget* QObject*].include?(first_type)
47
+ return nil unless first_type.match?(/\AQ[A-Z]\w*\*\z/)
48
48
  return nil unless params.drop(1).all? { |param| param[:has_default] }
49
49
 
50
50
  first_type
@@ -139,7 +139,7 @@ def discover_related_value_classes(ast, seed_classes, all_classes, template_clas
139
139
 
140
140
  until queue.empty?
141
141
  klass = queue.shift
142
- related_qt_type_names(ast, klass).each do |candidate|
142
+ (related_qt_type_names(ast, klass).to_a + derived_qt_classes(ast, klass, all_classes)).uniq.each do |candidate|
143
143
  next if discovered.include?(candidate)
144
144
  next unless related_value_class_candidate?(ast, candidate, all_classes, template_classes)
145
145
 
@@ -151,6 +151,42 @@ def discover_related_value_classes(ast, seed_classes, all_classes, template_clas
151
151
  discovered.to_a
152
152
  end
153
153
 
154
+ def derived_qt_classes(ast, qt_class, all_classes)
155
+ all_classes.select { |candidate| candidate != qt_class && class_inherits?(ast, candidate, qt_class) }
156
+ end
157
+
158
+ def discover_related_static_api_classes(ast, seed_classes, all_classes, template_classes)
159
+ discovered = seed_classes.to_set
160
+ loop do
161
+ added = all_classes.select do |owner|
162
+ next false if discovered.include?(owner) || template_classes.include?(owner)
163
+
164
+ method_decls_for_related_wrapper_scan(ast, owner).any? do |decl|
165
+ next false unless decl['storageClass'] == 'static'
166
+ next false unless decl['__effective_access'] == 'public'
167
+
168
+ parsed = parse_method_signature(decl)
169
+ next false unless parsed
170
+
171
+ return_type = related_qt_type_name(parsed[:return_type])
172
+ next false unless discovered.include?(return_type)
173
+
174
+ !build_auto_method_from_decl(
175
+ decl,
176
+ { qt_name: decl['name'] },
177
+ qt_class: owner,
178
+ int_cast_types: ast_int_cast_type_set(ast),
179
+ ast: ast
180
+ ).nil?
181
+ end
182
+ end
183
+ break if added.empty?
184
+
185
+ discovered.merge(added)
186
+ end
187
+ discovered.to_a - seed_classes
188
+ end
189
+
154
190
  def discover_related_qobject_wrapper_classes(ast, seed_classes, all_classes, template_classes, scope)
155
191
  discovered = seed_classes.to_set
156
192
  queue = seed_classes.dup
@@ -265,9 +301,7 @@ def append_related_qt_types_from_decl(out, parsed, method_name)
265
301
  candidates << [related_qt_type_name(parsed[:return_type]), parsed[:return_type].to_s] if parsed[:return_type]
266
302
  parsed[:params].each { |param| candidates << [related_qt_type_name(param[:type]), param[:type].to_s] }
267
303
  candidates.each do |candidate, raw_type|
268
- next if candidate.empty? || !candidate.start_with?('Q')
269
- next unless related_type_name_matches_method?(candidate, method_name) ||
270
- (qt_pointer_type?(raw_type) && candidate.end_with?('Item'))
304
+ next unless candidate.match?(/\AQ[A-Z]\w*\z/)
271
305
 
272
306
  out << candidate
273
307
  end
@@ -299,6 +333,15 @@ def discover_target_qt_classes(ast, scope)
299
333
  targets = timed("discover_target_qt_classes/#{scope}/related") do
300
334
  discover_related_value_classes(ast, base_targets, all_classes, template_classes).sort
301
335
  end
336
+ targets = timed("discover_target_qt_classes/#{scope}/static_api_owners") do
337
+ related_static_owners = discover_related_static_api_classes(
338
+ ast,
339
+ (targets + ['QApplication']).uniq,
340
+ all_classes,
341
+ template_classes
342
+ )
343
+ (targets + related_static_owners).sort
344
+ end
302
345
  wrapper_targets = timed("discover_target_qt_classes/#{scope}/related_qobject_wrappers") do
303
346
  seed_classes = (targets + ['QApplication']).uniq
304
347
  discover_related_qobject_wrapper_classes(ast, seed_classes, all_classes, template_classes, scope).sort
@@ -392,17 +435,14 @@ def constructor_string_path_cast(ctor_decls)
392
435
  ctor_decls.filter_map do |decl|
393
436
  parsed = parse_method_signature(decl)
394
437
  next nil unless parsed && parsed[:params].first
438
+ next nil unless parsed[:required_arg_count] <= 1
439
+ next nil unless parsed[:params].drop(1).all? { |param| param[:has_default] }
395
440
 
396
441
  constructor_string_like_arg_cast(parsed[:params].first[:type])
397
442
  end.first
398
443
  end
399
444
 
400
445
  def base_spec_hash(qt_class, parent_ctor)
401
- auto_method_rules = {}
402
- # Prefer QShortcut#setKeys(StandardKey) in generated API and provide
403
- # QKeySequence path via setKey(QKeySequence) + Ruby-level compatibility shim.
404
- auto_method_rules[:setKeys] = { param_types: ['QKeySequence::StandardKey'] } if qt_class == 'QShortcut'
405
-
406
446
  {
407
447
  qt_class: qt_class,
408
448
  ruby_class: qt_class,
@@ -411,7 +451,6 @@ def base_spec_hash(qt_class, parent_ctor)
411
451
  constructor: parent_ctor,
412
452
  methods: [],
413
453
  auto_methods: :all,
414
- auto_method_rules: auto_method_rules,
415
454
  # Constructor availability is already filtered during discovery.
416
455
  # Keep validation lightweight for auto-discovered classes to avoid false negatives
417
456
  # on template/specialized constructor names in Clang AST.