Wrong "Redundant conformance constraint" warning

The problem

I'm working on GitHub - RemiBardon/swift-geo: Generic geographical library for Swift. I'm rewriting things to avoid code duplication, but I just stumbled upon a wrong Swift warning. You can see it at swift-geo/MultiPoint.swift at redundant-conformance-constraint-bug · RemiBardon/swift-geo · GitHub.

Basically, I have:

associatedtype CoordinateSystem: GeoModels.CoordinateSystem
	where Self.CoordinateSystem.Point == Self.Point
associatedtype Point: GeoModels.Point // ⚠️ Redundant conformance constraint 'Self.Point' : 'Point'

When I comment out the constraint, my code does not compile anymore

associatedtype CoordinateSystem: GeoModels.CoordinateSystem
	where Self.CoordinateSystem.Point == Self.Point
associatedtype Point//: GeoModels.Point // ⚠️ Redundant conformance constraint 'Self.Point' : 'Point'
if points.last != points.first {
// 💥 Operator function '!=' requires that 
// 'NonEmpty<NonEmpty<Array<Self.CoordinateSystem.Point>>>.Element'
// (aka 'Self.CoordinateSystem.Point') conform to 'Equatable'

The type tree is quite complex, and I think the compiler has a hard time remembering all the constraints. I had a similar problem ([SR-16024] Abort trap: 6 · Issue #58285 · apple/swift · GitHub) a few days ago, while working on Nested `NonEmpty` by RemiBardon · Pull Request #46 · pointfreeco/swift-nonempty · GitHub, so I wouldn't be surprised.

Is there a compiler flag or an annotation I could use to silence those warnings? I'd prefer writing too many redundant constraints and help the compiler, rather than having an exponential compile time… and errors :roll_eyes:

If there is no simple answer, I'll take some time to write down my type tree and add more code samples. I just don't want to spend a lot of time on it if there is one :relieved: I'm not even sure if I can create a minimal reproductible example, as the problem comes from a too complex type tree :confused:

My environment

  • Xcode 13.3 (13E113)
  • Apple Swift version 5.6 (swiftlang-5.6.0.323.62 clang-1316.0.20.8)
  • Target: x86_64-apple-macosx12.0
  • macOS 12.3 (21E230)
1 Like

This seems like it could an issue with the new generics implementation in Swift 5.6. You should be able to use the workaround here to see if disabling the new implementation fixes your problem.

Redundancy warnings are emitted after generic signature minimization, and Swift 5.6 still uses the old generics implementation for signature minimization and requirement diagnostics. I actually just implemented redundancy diagnostics for the new implementation on main, so you could try a development toolchain from Swift.org - Download Swift and build with the flags -Xfrontend -requirement-machine-protocol-signatures=on -Xfrontend -requirement-machine-inferred-signatures=on to see if it's fixed with the new implementation. If not, I'll take a look!

7 Likes

Note that redundant requirements don't help the compiler (though I think they can help the programmer if the requirement isn't obvious!) - the compiler will always canonicalize generic signatures in order to type check calls to a generic function, etc, so this computation would be done even if the compiler did not report warnings for redundant requirements.

5 Likes

As far as I can tell the requirement really is redundant, because it can be derived from Self.Point == Self.BoundingBox.Point. With -Xfrontend -requirement-machine-protocol-signatures=on the program builds both with and without the redundant requirement, so this bug will be fixed once this flag becomes the default.

This appears to be a bug in the GenericSignatureBuilder, and briefly looking at the code I see that the Point, BoundingBox, Line, CoordinateSystem, MultiPoint protocols are mutually inter-dependent. This is one of the scenarios the GSB did not handle very well.

I confirmed that in 5.6, -Xfrontend -requirement-machine=off does not fix the issue. As Holly already explained, the term rewriting engine is 'downstream' of the GenericSignatureBuilder in 5.6 so we would not expect behavior to change either way here.

The reduced test case is pretty cute:

protocol P1 {
  associatedtype A: P2 where Self.A.B == Self.B
  associatedtype B
}

protocol P2 {
  associatedtype B
  associatedtype C: P3 where Self.C.B == Self.B
}

protocol P3 {
  associatedtype B: P4
}

protocol P4 {
  associatedtype A: P2 where Self.A.B == Self
}

func takesP4<T : P4>(_: T.Type) {}

func testP1<T : P1>(_: T) {
  takesP4(T.B.self)
}

The call to takesP4() in 'testP1()` should succeed, but it fails in 5.6. It succeeds on main with the new minimization algorithm. I'll add it to our test suite.

11 Likes

I commented on that bug report. It is a different issue that can be fixed with a flag to increase the maximum term length. I put up a PR for the main branch to avoid having to set the flag in cases such as this one: RequirementMachine: Tweak rule limit non-termination heuristic by slavapestov · Pull Request #42017 · apple/swift · GitHub

1 Like

Thank you all for your very quick replies :heart: I'll have a try and tell you if everything works :slightly_smiling_face:

3 Likes

Thank you very much for your work, it fixes my problem :grinning:
(Just a note: you pasted the arguments twice :wink: Edit: I just hadn't seen the difference (protocol / inferred) :see_no_evil:)

I am using a Swift Package, so for those searching a "copy-paste" solution, here is what I had to change:

.target(
	name: "GeoModels",
	dependencies: [/* … */],
+	swiftSettings: [
+		.unsafeFlags(["-Xfrontend", "-requirement-machine-inferred-signatures=on"]),
+	]
),

Once again, thank you everyone, I wasn't hoping for a solution in just a few hours :heart_eyes:

They are two different frontend compiler flags :slightly_smiling_face: one is for generic function/type signatures and the other is for protocol requirement signatures.

My bad, I hadn't seen the subtle difference :see_no_evil:

1 Like

Why do I need -Xfrontend twice then?

With -Xfrontend -requirement-machine-inferred-signatures=on, it compiles.
With -Xfrontend -requirement-machine-protocol-signatures=on, I get Abort trap: 6.
With the four of them, I get Abort trap: 6 too.

Is it expected?

I'm afraid you'll have to :speak_no_evil:

I managed to avoid needing -Xfrontend -requirement-machine-inferred-signatures=on, probably because I was able to simplify type checking.

However, my build time increased a lot (compared to Swift 5.5), taking around 140s for incremental build and 260s (for clean build) :flushed:

It stays stuck at one step for most of the time, but I don't know how to identify it. What could I do to debug it?

I just ran GitHub - MobileNativeFoundation/XCLogParser: Tool to parse Xcode and xcodebuild logs stored in the xcactivitylog format to see where were the issues.

Here is an outline:

  • 125s to build target NonEmpty
  • 153s to build target GeoModels
  • 0.75s to build target Turf

I think my pull request to pointfreeco/swift-nonempty introduced too complex type checking. I will investigate :face_with_monocle:

Well… I see the problem now :flushed:

Duration (ms) File Function Line Column
120252.53 file:///redacted/swift-nonempty/Sources/NonEmpty/NonEmpty.swift initializer init(rawValue:) 27 10
119193.35 file:///redacted/swift-nonempty/Sources/NonEmpty/NonEmpty+SetAlgebra.swift initializer init(::) 5 10
118939.86 file:///redacted/swift-nonempty/Sources/NonEmpty/NonEmpty+Dictionary.swift initializer init(::) 21 10

I believe @Slava_Pestov has already diagnosed and dealt with the issue:

1 Like

-Xfrontend is an instruction to the swift driver that says "pass the next command I give you on to the front end". You're passing two different flags to the front end so you need to us that command for each one.

2 Likes

Thank you, I found out after some research on this forum :slight_smile: I should have searched first :roll_eyes:

Try -Xfrontend -enable-requirement-machine-loop-normalization. It addressed the slow build times for me. This flag will become the default shortly.

Can you share the build log? I didn't see this issue when I was building your project.

EDIT: Are you using the developer toolchain from swift.org, or Swift 5.6 that shipped with Xcode? The latter had these flags because I was just starting to implement this functionality when the branch was cut, but it won't actually work. You need the latest toolchain.

1 Like

I downloaded the toolchain yesterday, as @hborla asked me to

1 Like