Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SR-14195] Swift emits an invalid module interface when a public type has the same name as a module #56573

Open
beccadax opened this issue Feb 12, 2021 · 1 comment
Assignees
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself ParseableInterfaces

Comments

@beccadax
Copy link
Contributor

Previous ID SR-14195
Radar None
Original Reporter @beccadax
Type Bug
Additional Detail from JIRA
Votes 9
Component/s Compiler
Labels Bug, ParseableInterfaces
Assignee @beccadax
Priority Medium

md5: 61edb3ff0102d366e7e0d5868573a20b

is blocked by:

  • SR-898 Unresolvable "ambiguous for type lookup" error when using multiple modules

Issue Description:

When a module with library evolution enabled either declares or imports an ABI-public type with the same name as a module it imports, the Swift compiler can emit a module interface with invalid type references that cannot be imported. This happens because attempts to prefix type names with that module are instead interpreted as attempts to find a nested type within the identically-named type. This problem is usually invisible until a client tries to read the module interface, which is rarely done in normal development workflows, so the invalid module interface may not be noticed until much later.

(This bug is already tracked indirectly by SR-898, but I am creating a separate bug that is specific to module interfaces so we can clearly document workarounds and track emitting SR-898's new syntax into module interfaces.)

Example

The simplest example is a module which is named after the main type it provides. For instance, suppose a module called Compass has a file which declares:

public class Compass {
   // ...
   public var direction: Direction
}
public struct Direction { ... }

When you build the Compass module with library evolution enabled, Swift will emit a module interface for this file which looks something like this:

public class Compass {
   // ...
   public var direction: Compass.Direction { get set }
}
public struct Direction { ... }

Note that, in the var declaration, Swift prints Compass.Direction to mean "the Direction type in the Compass module". However, when a later Swift compiler tries to import this file (which may only happen if it's a different compiler version), it will resolve the name Compass in Compass.Direction to the class Compass, not the module Compass. This will usually cause a compiler error:

.../Compass.swiftinterface:6:34: error: 'Direction' is not a member type of class 'Compass.Compass'
   public var direction: Compass.Direction { get set }
                                 ^

It's possible to see more complicated versions of this bug; for instance, the new type could have the same name as a module imported by the interface, or it could import a type with its own name. Especially in these cases, it's possible that your own module interface will be valid because it never references the affected module, but your clients' module interfaces may be affected.

Workarounds

The simplest, most reliable solution is to rename either the module or the type. For instance, if the module is named CompassKit or Orienteer and the type is named Compass, there will be no conflict between them.

If that isn't possible, you may be able to work around this bug by adding the special compiler flags -Xfrontend -module-interface-preserve-types-as-written to OTHER_SWIFT_FLAGS. This solution is not 100% reliable; in particular, you may have to manually implement conformances to protocols like Hashable to get them to print correctly. It is also something your module's clients may need to adopt if their interfaces refer to types from the affected module.

If all else fails, you can write a script or tool which edits the .swiftinterface files generated by your project. A simple regular expression substitution usually works, but these sorts of tools are often quite fragile, so use them as a last resort.

Prevention and verification

Swift 5.4 supports a -verify-emitted-module-interface flag which causes it to immediately typecheck .swiftinterface files after emitting them to verify that they are valid. You can add that flag to OTHER_SWIFT_FLAGS to make sure your interfaces are readable.

You can also take a copy of your framework or library, delete the .swiftmodule files from it (leaving the .swiftinterface files alone), and check that you can still import it in a test project.

Eventual compiler fix

The root cause of this bug is that Swift's language rules don't provide a way to unambiguously specify a module name. SR-898 tracks this issue; once an appropriate feature is added to the language, we will need to start printing it into module interfaces. This bug will eventually track that adoption work.

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
nicoelayda added a commit to screeningeagledreamlab/YapDatabase that referenced this issue Aug 30, 2022
…tinterface files.

This is a workaround for a compiler bug that generates a broken textual interface. See apple/swift#56573 for details.
hujunfeng pushed a commit to screeningeagledreamlab/YapDatabase that referenced this issue Aug 30, 2022
…tinterface files.

This is a workaround for a compiler bug that generates a broken textual interface. See apple/swift#56573 for details.
JT501 added a commit to JT501/CFNotify that referenced this issue Sep 20, 2022
@etasdemir
Copy link

If file contains enum for example public enum ExampleEnum ... , passing -module-interface-preserve-types-as-written flag to the Swift compiler in Xcode generates *.swiftinterface file that contains ModuleName.ExampleEnum which should be ExampleEnum only.

Example:

public enum ExampleEnum: CaseIterable {
    case CASE_1
    case CASE_2

    public func ordinal() -> Int32 {
        for type in ExampleEnum.allCases {
          // ...
        }
    }
}

Generated *.swiftinterface file:

public static func == (a: ExampleModuleName.ExampleEnum, b: ExampleModuleName.ExampleEnum) -> Swift.Bool
public typealias AllCases = [ExampleModuleName.ExampleEnum]
public static var allCases: [ExampleModuleName.ExampleEnum] {
  get
}

Expected *.swiftinterface file:

public static func == (a: ExampleEnum, b: ExampleEnum) -> Swift.Bool
public typealias AllCases = [ExampleEnum]
public static var allCases: [ExampleEnum] {
  get
}

kattouf added a commit to KosyanMedia/Reachability.swift that referenced this issue Dec 22, 2022
kattouf added a commit to KosyanMedia/CollectionSwipableCellExtension that referenced this issue Dec 26, 2022
kattouf added a commit to KosyanMedia/CollectionSwipableCellExtension that referenced this issue Dec 27, 2022
kattouf added a commit to KosyanMedia/Reachability.swift that referenced this issue Dec 27, 2022
ofavre added a commit to wonderpush/wonderpush-ios-sdk that referenced this issue Jan 12, 2023
Added `BUILD_LIBRARIES_FOR_DISTRIBUTION=YES` to include the
.swiftinterface file in the built framework.
See: https://developer.apple.com/forums/thread/125646

Needed some additional Swift flags because our module contains a class
with the same name, to work around a Swift bug.
See: apple/swift#56573

Mixing Swift and Objective C thanks to tips in:
https://joesusnick.medium.com/swift-package-manager-with-a-mixed-swift-and-objective-c-project-part-2-2-e71dad234e6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself ParseableInterfaces
Projects
None yet
Development

No branches or pull requests

2 participants