SourceKitten icon indicating copy to clipboard operation
SourceKitten copied to clipboard

Swift API generated from Objective-C code doesn't resolve forward declarations

Open steviki opened this issue 3 years ago • 4 comments

We noticed that there are issues with the listing of Swift name and declaration in the SourceKitten output for Objective-C API that includes forward declarations. When using a forward declaration of e.g. a protocol, the Swift name/declaration is missing completely.

Consider the following two files in a project, and an umbrella header importing both of those:

PSPDFProtocol.h
#import <Foundation/Foundation.h>

@protocol PSPDFProtocol
@end
PSPDFClass.h
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@protocol PSPDFProtocol;

@interface PSPDFClass

- (id<PSPDFProtocol>)protocolAPI;

@end

NS_ASSUME_NONNULL_END

The SourceKitten doc command results in the following output:

PSPDFKit.json
[
  {
    "PSPDFClass.h" : {
      "key.diagnostic_stage" : "",
      "key.substructure" : [
        {
          "key.always_deprecated" : false,
          "key.always_unavailable" : false,
          "key.deprecation_message" : "",
          "key.doc.column" : 12,
          "key.doc.file" : "PSPDFClass.h",
          "key.doc.line" : 7,
          "key.filepath" : "PSPDFClass.h",
          "key.kind" : "sourcekitten.source.lang.objc.decl.class",
          "key.name" : "PSPDFClass",
          "key.parsed_declaration" : "@interface PSPDFClass\n\n- (id<PSPDFProtocol>)protocolAPI;\n\n@end",
          "key.parsed_scope.end" : 11,
          "key.parsed_scope.start" : 7,
          "key.substructure" : [
            {
              "key.always_deprecated" : false,
              "key.always_unavailable" : false,
              "key.deprecation_message" : "",
              "key.doc.column" : 22,
              "key.doc.file" : "PSPDFClass.h",
              "key.doc.line" : 9,
              "key.filepath" : "PSPDFClass.h",
              "key.kind" : "sourcekitten.source.lang.objc.decl.method.instance",
              "key.name" : "-protocolAPI",
              "key.parsed_declaration" : "- (id<PSPDFProtocol>)protocolAPI;",
              "key.parsed_scope.end" : 9,
              "key.parsed_scope.start" : 9,
              "key.unavailable_message" : "",
              "key.usr" : "c:objc(cs)PSPDFClass(im)protocolAPI"
            }
          ],
          "key.swift_declaration" : "class PSPDFClass",
          "key.swift_name" : "PSPDFClass",
          "key.unavailable_message" : "",
          "key.usr" : "c:objc(cs)PSPDFClass"
        }
      ]
    }
  },
  {
    "PSPDFProtocol.h" : {
      "key.diagnostic_stage" : "",
      "key.substructure" : [
        {
          "key.always_deprecated" : false,
          "key.always_unavailable" : false,
          "key.deprecation_message" : "",
          "key.doc.column" : 11,
          "key.doc.file" : "PSPDFProtocol.h",
          "key.doc.line" : 3,
          "key.filepath" : "PSPDFProtocol.h",
          "key.kind" : "sourcekitten.source.lang.objc.decl.protocol",
          "key.name" : "PSPDFProtocol",
          "key.parsed_declaration" : "@protocol PSPDFProtocol\n@end",
          "key.parsed_scope.end" : 4,
          "key.parsed_scope.start" : 3,
          "key.swift_declaration" : "protocol PSPDFProtocol",
          "key.swift_name" : "PSPDFProtocol",
          "key.unavailable_message" : "",
          "key.usr" : "c:objc(pl)PSPDFProtocol"
        }
      ]
    }
  }
]

Note that the swift_name and swift_declaration are missing from the protocolAPI symbol.


One solution to this would be to replace the forward declaration with an actual import of the file that includes the protocol, but in some cases this is not a viable solution as it results in circular dependencies.

Is there any way that the Swift API for this could be generated correctly?

steviki avatar Mar 21 '22 17:03 steviki

I don't know why the @protocol forward declaration prevents SourceKit from generating an interface for that declaration.

The behaviour is the same in Xcode (counterparts -> Swift 5 interface) so I think this is likely a Swift bug/limitation.

Afraid I don't know enough about how Objective-C protocols are implemented or what the id<xxx> means to guess at whether there's good reason for the importer not generating the decl without the full protocol definition: you could try asking on the forums or bugs.swift.org.

(forward declarations for @class used as regular types seem to work OK)


edit:

To actually answer your question: no, as far as I can tell there is no way short of doing the @import to get this declaration working.

johnfairh avatar Mar 21 '22 17:03 johnfairh

Thank you for the quick reply John!

The behaviour is the same in Xcode (counterparts -> Swift 5 interface) so I think this is likely a Swift bug/limitation.

Yes, I observed the same. Sounds like this really is a limitation somewhere in SourceKit/Swift.

(forward declarations for @class used as regular types seem to work OK)

In most cases they indeed work. However there is also an issue with those: When an Objective-C class is renamed in Swift via NS_SWIFT_NAME, the SourceKitten output uses the Objective-C name instead of the name given to the class in Swift when the class is used as a forward declaration instead of being imported. But since this also happens in Xcode when looking at the generated Swift interface, it might be the same cause.

To actually answer your question: no, as far as I can tell there is no way short of doing the @import to get this declaration working.

That answers my question. That's unfortunate, but it was expected. Thank you for looking into this!

steviki avatar Mar 22 '22 09:03 steviki

Given that this is very likely to be a Swift / ClangImporter issue, could you file a bugs.swift.org issue?

jpsim avatar Mar 22 '22 15:03 jpsim

My apologies for the delay here. I just created an issue on bugs.swift.org via https://bugs.swift.org/browse/SR-16109.

steviki avatar Apr 08 '22 11:04 steviki