ffi-clang 0.15.1 → 0.16.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 33f36e58a42accf204240213d014bfbd1527ebe23239a74c7472829d502d8fc5
4
- data.tar.gz: 9b1e75ed934bb3572fd324cf80ea1c1278880abdc487482281e181bb4098932d
3
+ metadata.gz: 1550a49c2829fb140990ff821a8681f1c6ab38587e167b408a6d7dccc8bee9df
4
+ data.tar.gz: 9ce46bcbba80c9f97bef6f5f3a444fb8608d280dc21c75410855088fdbc2eaec
5
5
  SHA512:
6
- metadata.gz: 07164ccdc62ecb4e8a106fa49f0776c2487e38a2d0aa2df47cf32f0ae2402a63bfb4ac7e27f5ed41d5222cc61a09eca3b64963c7c560b846e5329ef1173d3168
7
- data.tar.gz: 7e5ae23e82ec66cb97a6d6943fcdf99b124dd6cb902cd12e10216b75310885b2f10a1ec9b62855a83d2acfe523202fa1382fdedebc1c302b25c1315a586591cd
6
+ metadata.gz: 03053f8d75313265b8bfba38addd7b3ba5fcfc93f064136e79fd9b6c09a97c268b56eb02ad1716c352eade3121483d3c0b7d5b6caf6745f5f2e8cd2e72e74f3f
7
+ data.tar.gz: b987eb3c2ebfc91f946079b8ee0e64c8cf21181791393a934c7c6fdd3bfab0c4147afeda83198b52d85c1a07a69ed2e0d97a34bc5084d9f72a5b2057c05122bc
checksums.yaml.gz.sig CHANGED
Binary file
@@ -813,6 +813,61 @@ module FFI
813
813
  Lib.is_abstract(@cursor) != 0
814
814
  end
815
815
 
816
+ # Check if this class/struct is copyable: it has no inaccessible
817
+ # (deleted, private, or protected) copy constructor, and every base
818
+ # class is copyable (recursively). Returns true on cursors that
819
+ # don't denote a class/struct, since the question doesn't apply.
820
+ #
821
+ # A class with no explicit copy constructor is treated as copyable —
822
+ # the implicit one is generated. We only flag explicit `= delete` or
823
+ # private/protected access.
824
+ #
825
+ # @returns [Boolean] True if instances of this type can be copied.
826
+ def copyable?
827
+ return true unless [:cursor_class_decl, :cursor_struct].include?(self.kind)
828
+
829
+ copy_constructors = self.find_by_kind(false, :cursor_constructor).select(&:copy_constructor?)
830
+ copy_constructors.each do |constructor|
831
+ return false if constructor.deleted? || constructor.private? || constructor.protected?
832
+ end
833
+
834
+ self.find_by_kind(false, :cursor_cxx_base_specifier).each do |base|
835
+ base_decl = base.type.declaration
836
+ next if base_decl.kind == :cursor_no_decl_found
837
+ return false unless base_decl.copyable?
838
+ end
839
+
840
+ true
841
+ end
842
+
843
+ # Check if this class/struct is copy-assignable: it has no
844
+ # inaccessible (deleted, private, or protected) copy assignment
845
+ # operator, and every base class is copy-assignable (recursively).
846
+ # Returns true on cursors that don't denote a class/struct, since
847
+ # the question doesn't apply.
848
+ #
849
+ # A class with no explicit copy assignment operator is treated as
850
+ # copy-assignable — the implicit one is generated. We only flag
851
+ # explicit `= delete` or private/protected access.
852
+ #
853
+ # @returns [Boolean] True if instances of this type can be copy-assigned.
854
+ def copy_assignable?
855
+ return true unless [:cursor_class_decl, :cursor_struct].include?(self.kind)
856
+
857
+ copy_assignment_operators = self.find_by_kind(false, :cursor_cxx_method).select(&:copy_assignment_operator?)
858
+ copy_assignment_operators.each do |operator|
859
+ return false if operator.deleted? || operator.private? || operator.protected?
860
+ end
861
+
862
+ self.find_by_kind(false, :cursor_cxx_base_specifier).each do |base|
863
+ base_decl = base.type.declaration
864
+ next if base_decl.kind == :cursor_no_decl_found
865
+ return false unless base_decl.copy_assignable?
866
+ end
867
+
868
+ true
869
+ end
870
+
816
871
  # Check if this is a scoped enum.
817
872
  # @returns [Boolean] True if it's a scoped enum.
818
873
  def enum_scoped?
@@ -127,17 +127,79 @@ module FFI
127
127
 
128
128
  # Get the type with all qualifiers (const, volatile, restrict) removed.
129
129
  # @returns [Type] The unqualified type.
130
+ #
131
+ # Guards against :type_invalid input: clang_getUnqualifiedType has
132
+ # no null check on the underlying QualType (unlike its siblings
133
+ # clang_getNonReferenceType / clang_getCanonicalType / etc.) and
134
+ # segfaults on invalid types. Returning self preserves the
135
+ # invalid kind without entering libclang.
130
136
  def unqualified_type
137
+ return self if self.kind == :type_invalid
131
138
  Type.create Lib.get_unqualified_type(@type), @translation_unit
132
139
  end
133
140
 
141
+ # True if this type is an lvalue or rvalue reference.
142
+ # @returns [Boolean] Whether this type is `T &` or `T &&`.
143
+ def reference?
144
+ self.kind == :type_lvalue_ref || self.kind == :type_rvalue_ref
145
+ end
146
+
134
147
  # Get the non-reference type.
135
148
  # For reference types, returns the type that is being referenced.
136
149
  # @returns [Type] The non-reference type.
150
+ #
151
+ # Guards against :type_invalid input: clang_getNonReferenceType
152
+ # dereferences the underlying QualType without a null check and
153
+ # segfaults on invalid types. Returning self preserves the
154
+ # invalid kind without entering libclang.
137
155
  def non_reference_type
156
+ return self if self.kind == :type_invalid
138
157
  Type.create Lib.get_non_reference_type(@type), @translation_unit
139
158
  end
140
159
 
160
+ # Get the intrinsic type — strip the reference, follow pointer
161
+ # indirection until reaching a non-pointer type, then drop
162
+ # cv-qualifiers. Named after Rice's `intrinsic_type` metafunction
163
+ # of the same shape. Useful when asking "what does this type
164
+ # ultimately denote?" for skip-list and bindability checks.
165
+ #
166
+ # Examples:
167
+ # * `T &` becomes `T`
168
+ # * `T *` becomes `T`
169
+ # * `T **&` becomes `T`
170
+ # * `const T &` becomes `T`
171
+ #
172
+ # @returns [Type] The intrinsic (innermost, unqualified) type.
173
+ def intrinsic_type
174
+ type = self.non_reference_type
175
+ while type.kind == :type_pointer
176
+ type = type.pointee
177
+ end
178
+ type.unqualified_type
179
+ end
180
+
181
+ # Check if this type's declaration (after reference stripping)
182
+ # has an accessible copy constructor and copyable bases.
183
+ # Returns true for non-class types (fundamentals, pointers,
184
+ # enums) and for types whose declaration is unavailable
185
+ # (:cursor_no_decl_found).
186
+ #
187
+ # @returns [Boolean] True if instances of this type can be copied.
188
+ def copyable?
189
+ self.non_reference_type.declaration.copyable?
190
+ end
191
+
192
+ # Check if this type's declaration (after reference stripping)
193
+ # has an accessible copy assignment operator and copy-assignable
194
+ # bases. Returns true for non-class types (fundamentals, pointers,
195
+ # enums) and for types whose declaration is unavailable
196
+ # (:cursor_no_decl_found).
197
+ #
198
+ # @returns [Boolean] True if instances of this type can be copy-assigned.
199
+ def copy_assignable?
200
+ self.non_reference_type.declaration.copy_assignable?
201
+ end
202
+
141
203
  # Get the type of a template argument at the given index.
142
204
  # For template specializations (e.g., `std::vector<int>`), this returns the type of
143
205
  # the template argument at the specified position.
@@ -199,11 +261,236 @@ module FFI
199
261
  end
200
262
 
201
263
  # Get the fully qualified name of this type.
202
- # @parameter policy [PrintingPolicy] The printing policy to use.
264
+ #
265
+ # On libclang 21+ this dispatches to clang_getFullyQualifiedName.
266
+ # On earlier libclang versions it falls back to a Ruby shim that
267
+ # composes existing libclang APIs (declaration, qualified_name,
268
+ # template arguments, pointer/array/reference unwrapping, etc.).
269
+ #
270
+ # Known shim limitation: STL container typedefs that depend on
271
+ # default template arguments (e.g., `std::vector<T>::iterator`)
272
+ # don't expand the defaults. Output is valid C++ and matches
273
+ # the native result for non-STL types.
274
+ #
275
+ # @parameter policy [PrintingPolicy] The printing policy to use. Ignored by the shim.
203
276
  # @parameter with_global_ns_prefix [Boolean] Whether to prepend "::".
204
277
  # @returns [String] The fully qualified type name.
205
- def fully_qualified_name(policy, with_global_ns_prefix: false)
206
- Lib.extract_string Lib.get_fully_qualified_name(@type, policy, with_global_ns_prefix ? 1 : 0)
278
+ def fully_qualified_name(policy = nil, with_global_ns_prefix: false)
279
+ if Lib.respond_to?(:get_fully_qualified_name)
280
+ Lib.extract_string Lib.get_fully_qualified_name(@type, policy, with_global_ns_prefix ? 1 : 0)
281
+ else
282
+ result = fqn_impl(policy)
283
+ with_global_ns_prefix ? "::#{result}" : result
284
+ end
285
+ end
286
+
287
+ # Shim implementation of fully_qualified_name. Recursively walks the
288
+ # type tree, dispatching by kind. Public so it can be invoked across
289
+ # Type subclass boundaries during recursion.
290
+ # @parameter policy [PrintingPolicy] Threaded for native API parity; ignored.
291
+ # @returns [String] The fully qualified type spelling.
292
+ def fqn_impl(policy)
293
+ case self.kind
294
+ when :type_lvalue_ref
295
+ "#{self.non_reference_type.fqn_impl(policy)} &"
296
+ when :type_rvalue_ref
297
+ "#{self.non_reference_type.fqn_impl(policy)} &&"
298
+ when :type_pointer
299
+ fqn_pointer(policy)
300
+ when :type_constant_array
301
+ "#{self.element_type.fqn_impl(policy)}[#{self.size}]"
302
+ when :type_incomplete_array
303
+ "#{self.element_type.fqn_impl(policy)}[]"
304
+ when :type_elaborated
305
+ fqn_elaborated(policy)
306
+ when :type_record
307
+ fqn_record
308
+ else
309
+ self.spelling
310
+ end
311
+ end
312
+
313
+ # Spell a pointer type and its qualifier chain. Function pointers
314
+ # get a single rendering with parameter list; data pointers walk
315
+ # the chain collecting `*`/`*const` parts and qualify the leaf
316
+ # child once. Output matches native fqn: `int **`, `const char *const`, etc.
317
+ # @parameter policy [PrintingPolicy] Threaded to recursive fqn_impl calls.
318
+ # @returns [String] The fully qualified pointer type spelling.
319
+ def fqn_pointer(policy)
320
+ pointee = self.pointee
321
+
322
+ if [:type_function_proto, :type_function_no_proto].include?(pointee.kind)
323
+ ptr_const = self.const_qualified? ? " const" : ""
324
+ result_type = pointee.result_type.fqn_impl(policy)
325
+ arg_types = pointee.arg_types.map{|arg_type| arg_type.fqn_impl(policy)}.join(", ")
326
+ return "#{result_type} (*#{ptr_const})(#{arg_types})"
327
+ end
328
+
329
+ parts = []
330
+ current = self
331
+ while current.kind == :type_pointer
332
+ inner = current.pointee
333
+ break if [:type_function_proto, :type_function_no_proto].include?(inner.kind)
334
+
335
+ parts << (current.const_qualified? ? "*const" : "*")
336
+ current = inner
337
+ end
338
+
339
+ "#{current.fqn_impl(policy)} #{parts.reverse.join}"
340
+ end
341
+
342
+ # Spell an elaborated type (typedef / type alias / enum / class)
343
+ # preserving the alias name where appropriate and qualifying
344
+ # template arguments recursively.
345
+ # @parameter policy [PrintingPolicy] Threaded to recursive calls.
346
+ # @returns [String] The fully qualified elaborated type spelling.
347
+ def fqn_elaborated(policy)
348
+ decl = self.declaration
349
+ const_prefix = self.const_qualified? ? "const " : ""
350
+
351
+ case decl.kind
352
+ when :cursor_typedef_decl, :cursor_type_alias_decl
353
+ # Preserve the typedef/alias name and qualify with namespace.
354
+ spelling = self.unqualified_type.spelling
355
+ qualified = decl.qualified_name
356
+
357
+ if spelling.include?("::")
358
+ # Already partially qualified. For nested typedefs in
359
+ # template classes (e.g., std::vector<Pixel>::iterator),
360
+ # qualify template args using the parent type's fqn.
361
+ parent = decl.semantic_parent
362
+ if parent.kind == :cursor_class_decl || parent.kind == :cursor_struct
363
+ parent_type = parent.type
364
+ parent_fqn = parent_type.fqn_impl(policy)
365
+ member_name = decl.spelling
366
+ "#{const_prefix}#{parent_fqn}::#{member_name}"
367
+ else
368
+ "#{const_prefix}#{spelling}"
369
+ end
370
+ elsif qualified
371
+ "#{const_prefix}#{qualified}"
372
+ else
373
+ "#{const_prefix}#{spelling}"
374
+ end
375
+
376
+ when :cursor_enum_decl
377
+ "#{const_prefix}#{decl.qualified_name}"
378
+
379
+ else
380
+ # Alias-template detection: e.g. `AliasOptional<int>` -> `Optional<int>`.
381
+ # The elaborated spelling preserves the alias; fqn_record
382
+ # would resolve to the underlying type. Use spelling when
383
+ # it's already qualified.
384
+ unqual = self.unqualified_type.spelling
385
+ if unqual.include?("::") && decl.spelling != unqual.sub(/<.*/, "").split("::").last
386
+ "#{const_prefix}#{unqual}"
387
+ else
388
+ base = fqn_record
389
+ if self.const_qualified? && !base.start_with?("const ")
390
+ "const #{base}"
391
+ else
392
+ base
393
+ end
394
+ end
395
+ end
396
+ end
397
+
398
+ # Spell a record type (class/struct) using its declaration's
399
+ # type spelling, which suppresses inline namespaces and includes
400
+ # template args. Falls back to qualified_name + spelling args
401
+ # for dependent types.
402
+ # @returns [String] The fully qualified record type spelling.
403
+ def fqn_record
404
+ decl = self.declaration
405
+ return self.spelling if decl.kind == :cursor_no_decl_found
406
+
407
+ const_prefix = self.const_qualified? ? "const " : ""
408
+
409
+ # decl.type.spelling gives the right qualification (no inline
410
+ # ns, with template args).
411
+ decl_spelling = decl.type.spelling
412
+ if decl_spelling && !decl_spelling.empty? && decl_spelling.include?("::")
413
+ # For concrete template types, recursively qualify args.
414
+ n = self.num_template_arguments
415
+ if n > 0
416
+ base = decl_spelling.sub(/<.*/, "")
417
+ template_args = fqn_template_args(nil)
418
+ "#{const_prefix}#{base}#{template_args}"
419
+ else
420
+ "#{const_prefix}#{decl_spelling}"
421
+ end
422
+ else
423
+ # Fallback for types where decl.type.spelling is unqualified.
424
+ qualified = decl.qualified_name
425
+ bare_spelling = self.unqualified_type.spelling
426
+ template_args = bare_spelling.include?("<") ? bare_spelling[/<.*/] : ""
427
+ "#{const_prefix}#{qualified}#{template_args}"
428
+ end
429
+ end
430
+
431
+ # Build the qualified template argument list by recursing into
432
+ # each type argument. Non-type template parameters (e.g.
433
+ # integral values) are recovered from the type's spelling.
434
+ # @parameter policy [PrintingPolicy] Threaded to recursive calls.
435
+ # @returns [String] The bracketed argument list, including angle brackets, or empty.
436
+ def fqn_template_args(policy)
437
+ n = self.num_template_arguments
438
+ return "" unless n > 0
439
+
440
+ # Extract original args from spelling for non-type template params.
441
+ spelling_args = parse_template_args_from_spelling
442
+
443
+ args = (0...n).map do |i|
444
+ arg_type = self.template_argument_type(i)
445
+ if arg_type.kind == :type_invalid
446
+ # Non-type template arg (e.g., int N=3) — use from spelling.
447
+ spelling_args ? spelling_args[i] : nil
448
+ else
449
+ arg_type.fqn_impl(policy)
450
+ end
451
+ end.compact
452
+
453
+ return "" if args.empty?
454
+ "<#{args.join(", ")}>"
455
+ end
456
+
457
+ # Parse template arguments from the type's unqualified spelling,
458
+ # respecting nested angle brackets. Used to recover non-type
459
+ # template arguments that libclang surfaces only as text.
460
+ # @returns [Array(String) | nil] The argument substrings, or nil if no `<` was found.
461
+ def parse_template_args_from_spelling
462
+ bare = self.unqualified_type.spelling
463
+ start = bare.index("<")
464
+ return nil unless start
465
+
466
+ depth = 0
467
+ args = []
468
+ current = +""
469
+ bare[start + 1..].each_char do |c|
470
+ case c
471
+ when "<"
472
+ depth += 1
473
+ current << c
474
+ when ">"
475
+ if depth == 0
476
+ args << current.strip unless current.strip.empty?
477
+ break
478
+ else
479
+ depth -= 1
480
+ current << c
481
+ end
482
+ when ","
483
+ if depth == 0
484
+ args << current.strip
485
+ current = +""
486
+ else
487
+ current << c
488
+ end
489
+ else
490
+ current << c
491
+ end
492
+ end
493
+ args
207
494
  end
208
495
 
209
496
  # Visit all base classes of a C++ record type.
@@ -10,6 +10,6 @@
10
10
  module FFI
11
11
  # @namespace
12
12
  module Clang
13
- VERSION = "0.15.1"
13
+ VERSION = "0.16.0"
14
14
  end
15
15
  end
data/readme.md CHANGED
@@ -41,6 +41,16 @@ For example, to use a specific LLVM installation:
41
41
 
42
42
  Please see the [project releases](https://socketry.github.io/ffi-clang/releases/index) for all releases.
43
43
 
44
+ ### v0.16.0
45
+
46
+ - Add <code class="language-ruby">FFI::Clang::Types::Type\#intrinsic\_type</code>, which strips references and follows pointer indirection until reaching a non-pointer type and then drops cv-qualifiers.
47
+ - Add <code class="language-ruby">FFI::Clang::Types::Type\#reference?</code>, a one-liner predicate over `:type_lvalue_ref` and `:type_rvalue_ref`.
48
+ - Add <code class="language-ruby">FFI::Clang::Cursor\#copyable?</code> and <code class="language-ruby">FFI::Clang::Types::Type\#copyable?</code>, predicates that return true when a class/struct has an accessible copy constructor (none deleted, private, or protected) and every base class is copyable.
49
+ - Add <code class="language-ruby">FFI::Clang::Cursor\#copy\_assignable?</code> and <code class="language-ruby">FFI::Clang::Types::Type\#copy\_assignable?</code>, predicates that return true when a class/struct has an accessible copy assignment operator (none deleted, private, or protected) and every base class is copy-assignable.
50
+ - <code class="language-ruby">FFI::Clang::Types::Type\#fully\_qualified\_name</code> now works on libclang versions earlier than 21 via a Ruby shim that composes existing libclang APIs (declaration, qualified\_name, template arguments, pointer/array/reference unwrapping).
51
+ - Guard <code class="language-ruby">FFI::Clang::Types::Type\#unqualified\_type</code> against `:type_invalid` input.
52
+ - Guard <code class="language-ruby">FFI::Clang::Types::Type\#non\_reference\_type</code> against `:type_invalid` input.
53
+
44
54
  ### v0.15.1
45
55
 
46
56
  - Use `-isystem` instead of `-I` for auto-discovered MSVC system include paths so that `in_system_header?` correctly identifies system headers.
@@ -101,18 +111,6 @@ Please see the [project releases](https://socketry.github.io/ffi-clang/releases/
101
111
  - Set `cursor_translation_unit` enum value based on the Clang version. (\#64)
102
112
  - Add various C++ introspection methods. (\#66)
103
113
 
104
- ### v0.7.0
105
-
106
- - Fix incorrect return type of `clang_getTranslationUnitSpelling`.
107
- - Fix `compilation_database_spec`.
108
- - Fix libclang lookup for Xcode.
109
- - Fix warning on class re-definition.
110
- - Update cursor kinds.
111
- - Find `libclang.dll` under Windows.
112
- - Allow retrieval of list of references from a Cursor.
113
- - Implement libclang `findReferencesInFile` functionality.
114
- - Allow `TranslationUnit#file` to return the main file.
115
-
116
114
  ## Contributing
117
115
 
118
116
  We welcome contributions to this project.
data/releases.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Releases
2
2
 
3
+ ## v0.16.0
4
+
5
+ - Add {ruby FFI::Clang::Types::Type\#intrinsic\_type}, which strips references and follows pointer indirection until reaching a non-pointer type and then drops cv-qualifiers.
6
+ - Add {ruby FFI::Clang::Types::Type\#reference?}, a one-liner predicate over `:type_lvalue_ref` and `:type_rvalue_ref`.
7
+ - Add {ruby FFI::Clang::Cursor\#copyable?} and {ruby FFI::Clang::Types::Type\#copyable?}, predicates that return true when a class/struct has an accessible copy constructor (none deleted, private, or protected) and every base class is copyable.
8
+ - Add {ruby FFI::Clang::Cursor\#copy\_assignable?} and {ruby FFI::Clang::Types::Type\#copy\_assignable?}, predicates that return true when a class/struct has an accessible copy assignment operator (none deleted, private, or protected) and every base class is copy-assignable.
9
+ - {ruby FFI::Clang::Types::Type\#fully\_qualified\_name} now works on libclang versions earlier than 21 via a Ruby shim that composes existing libclang APIs (declaration, qualified\_name, template arguments, pointer/array/reference unwrapping).
10
+ - Guard {ruby FFI::Clang::Types::Type\#unqualified\_type} against `:type_invalid` input.
11
+ - Guard {ruby FFI::Clang::Types::Type\#non\_reference\_type} against `:type_invalid` input.
12
+
3
13
  ## v0.15.1
4
14
 
5
15
  - Use `-isystem` instead of `-I` for auto-discovered MSVC system include paths so that `in_system_header?` correctly identifies system headers.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ffi-clang
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.1
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
metadata.gz.sig CHANGED
Binary file