Why does Swift so often fail to find symbols?

I often run into this situation: I'll link against some third-party library, import that library into my Swift source file, reference a public symbol in that library, and the compiler will complain that no such symbol is defined.

In this case, I've imported FirebaseMessaging (I don't want to use Firebase, I'm being forced to). One of the symbols it exports is kFIRMessagingMessageIDKey. But if I refer to that key in my source file, Xcode complains that it's not defined in any scope.

If I look at the generated interfaces for the file (it's an Obj-C file), it shows me this:

public let kFIRMessagingMessageIDKey: String

The Obj-C looks like:

FOUNDATION_EXPORT NSString *const kFIRMessagingMessageIDKey;

My source file looks something like:

import Firebase
import FirebaseMessaging

…

    let messageID = userInfo[kFIRMessagingMessageIDKey]

I run into this a lot, and I'm never sure how to handle it.

There are only two kinds of "failure to find symbols". One is that Swift doesn't think the name is defined, the other is that the linker doesn't think the symbol exists.

The first is a failure to import the header file correctly. This can happen for lots of reasons: maybe the header file isn't on the search path, maybe the code isn't modularised correctly, maybe it's a definition the clang importer cannot parse.

The second occurs when the header file was found and parsed. This means that Swift expected to link a symbol, but when the linker tried to resolve it, it couldn't find it. This is usually caused by a missing linker directive.

In your case you haven't shown me the error message, so I don't know what the actual problem is.

3 Likes

maybe it's a definition the clang importer cannot parse.

Speaking of which, there’s progress on that front, to wit, Lazy ClangImporter Diagnostics Enabled by Default. Yay!

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

2 Likes
/Users/rmann/Projects/Clients/…/Utils/Notifications/NotificationCoordinator.swift:64:66: error: cannot find 'kFIRMessagingMessageIDKey' in scope
        let foo = response.notification.request.content.userInfo[kFIRMessagingMessageIDKey]

Interestingly, Xcode colors it green as a recognized symbol (although it seems overzealous at this sometimes), and if I Command-click it it jumps to the definition in the Obj-C file. However, it would not autocomplete the symbol, and a related symbol that it would autocomplete it neither colors nor can jump to.

The Firebase package has Firebase/FirebaseMessaging/Sources/Public/FirebaseMessaging.h, which, upon inspection, does not appear to include the Firebase/FirebaseMessaging/SourcesFIRMessagingConstants.h file in which this symbol is declared.

The target is defined in Package.swift like this. It's not clear to me if SPM (or even if it should) expose all the symbols it finds anywhere in the package that have appropriate visibility. I think it probably should (since location doesn't dictate that), and if I look at the generated header for the constants file, it does declare it as public.

    .target(
      name: "FirebaseMessaging",
      dependencies: [
        "FirebaseCore",
        "FirebaseInstallations",
        .product(name: "GULAppDelegateSwizzler", package: "GoogleUtilities"),
        .product(name: "GULEnvironment", package: "GoogleUtilities"),
        .product(name: "GULReachability", package: "GoogleUtilities"),
        .product(name: "GULUserDefaults", package: "GoogleUtilities"),
        .product(name: "GoogleDataTransport", package: "GoogleDataTransport"),
        .product(name: "nanopb", package: "nanopb"),
      ],
      path: "FirebaseMessaging/Sources",
      publicHeadersPath: "Public",
      cSettings: [
        .headerSearchPath("../../"),
        .define("PB_FIELD_32BIT", to: "1"),
        .define("PB_NO_PACKED_STRUCTS", to: "1"),
        .define("PB_ENABLE_MALLOC", to: "1"),
      ],
      linkerSettings: [
        .linkedFramework("SystemConfiguration", .when(platforms: [.iOS, .macOS, .tvOS])),
      ]
    ),

SwiftPM will not expose any part of the interface of a C module that is not in the public headers path. In this case, it seems that Firebase is not interested in exposing this symbol to you, so it's correct that you cannot find it.

1 Like

And I suppose Xcode is mistakenly included non-public symbols in the code completion, then?

However, it would not autocomplete the symbol

That seems like the correct behavior. Which did you observe?

In general, I find Xcode to be very bad at providing appropriate autocompletions. However, in this instance, while it won't autocomplete the symbol, if I type it out, it colors it green (which in the past I've seen it do for nonexistent symbols). And if I command-click on it, it takes me to the definition inside the package. These behaviors lead me to believe my code should compile, which we've established why it won't.