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.
- checksums.yaml +4 -4
- data/ext/qt_ruby_bridge/extconf.rb +1 -0
- data/ext/qt_ruby_bridge/qt_ruby_runtime.hpp +8 -0
- data/ext/qt_ruby_bridge/runtime_virtual_methods.cpp +78 -0
- data/lib/qt/application_lifecycle.rb +1 -1
- data/lib/qt/generated_overload_runtime.rb +7 -0
- data/lib/qt/object_wrapper.rb +1 -0
- data/lib/qt/version.rb +1 -1
- data/lib/qt/virtual_method_runtime.rb +165 -0
- data/lib/qt.rb +3 -0
- data/scripts/generate_bridge/ast_introspection.rb +16 -3
- data/scripts/generate_bridge/auto_methods.rb +171 -20
- data/scripts/generate_bridge/cpp_method_return_emitter.rb +15 -0
- data/scripts/generate_bridge/ffi_api.rb +1 -1
- data/scripts/generate_bridge/free_function_specs.rb +0 -158
- data/scripts/generate_bridge/spec_discovery.rb +51 -12
- data/scripts/generate_bridge.rb +374 -30
- metadata +4 -1
|
@@ -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
|
|
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/)
|
|
12
|
-
|
|
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
|
|
25
|
-
return nil
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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.
|