Skip to content

Swift 6.2.1: Linux: Interface changes aren't recognized in incremental builds #85717

@etcwilde

Description

@etcwilde

The compiler isn't updating function declarations after incremental rebuilds leading to programs compiling when they shouldn't, ultimately resulting in linking failures.

First reported on the CMake bug tracker: https://gitlab.kitware.com/cmake/cmake/-/issues/27416

Reproducer

There are two libraries, A and B. A defines a function that returns an integer, which is called by B. A compiles and B compiles. Then we update A so that the function by the same name returns a float without updating B. This should fail to compile, but it doesn't.

Library A consists of one file, a.swift:

let number: Int = 32

public func callA() -> Int { number }

Library B consists of one file, b.swift:

import A
public func callB() -> Int { callA() + 1 }

I then rebuild Library A where I replace callA with a function that doesn't return.

public func callA() -> Void {}

Rebuilding library B against this new interface should fail because callB expects callA to return an integer, but it doesn't, instead leading to a link-time failure because A.callA() -> Swift.Int doesn't exist.

This seems to fail consistently in the Swift 6.2.1 aarch64 Ubuntu Noble image.

$ docker run --rm -v <path to unpacked tar>:/repro -it swift:6.2-noble
# apt update && apt install zsh
# cd /repro
# ./driver.sh

Looking at the emitted textual interface, the function callA is seen to update from returning an Int to not returning anything:

+./driver.sh:5> SWIFTC=swiftc 
+./driver.sh:7> ROOT=/repro 
+./driver.sh:8> SOURCEROOT=/repro 
+./driver.sh:9> OBJROOT=build 
+./driver.sh:11> rm -fr build
+./driver.sh:12> mkdir -p build build/A.dir build/B.dir
+./driver.sh:16> perl -pe 's/\${PWD}/$ENV{PWD}/g' A-output-file-map.in.json
+./driver.sh:18> perl -pe 's/\${PWD}/$ENV{PWD}/g' B-output-file-map.in.json
+./driver.sh:21> pushd build/A.dir
+./driver.sh:22> swiftc -j 10 -num-threads 10 -c -module-cache-path /repro/build/module-cache -parse-as-library -static -emit-module -emit-module-path A.swiftmodule -emit-module-interface -module-name A -module-link-name A -incremental -output-file-map ofm.json /repro/a.swift
<unknown>:0: warning: module interfaces are only supported with -enable-library-evolution
<unknown>:0: warning: module interfaces are only supported with -enable-library-evolution
+./driver.sh:23> cat A.swiftinterface
// swift-interface-format-version: 1.0
// swift-compiler-version: Swift version 6.2.1 (swift-6.2.1-RELEASE)
// swift-module-flags: -target aarch64-unknown-linux-gnu -disable-objc-interop -module-link-name A -static -module-name A
// swift-module-flags-ignorable:  -formal-cxx-interoperability-mode=off -interface-compiler-version 6.2.1
import Swift
import _Concurrency
import _StringProcessing
import _SwiftConcurrencyShims
public func callA() -> Swift.Int
+./driver.sh:24> popd
+./driver.sh:26> sha256sum build/A.dir/A.swiftmodule build/A.dir/a.swift.o
b55c8dc5cf1591a174e2da2718822d25f967d521f8178deb826c1c7f084d9a1f  build/A.dir/A.swiftmodule
90e62312781fa0a78a484de3eb53509f6c6c6a7f70fb91c921ab7be471eabd98  build/A.dir/a.swift.o
+./driver.sh:29> pushd build/B.dir
+./driver.sh:30> swiftc -j 10 -num-threads 10 -c -module-cache-path /repro/build/module-cache -parse-as-library -static -emit-module -emit-module-path B.swiftmodule -module-name B -module-link-name B -incremental -output-file-map ofm.json /repro/b.swift -I /repro/build/A.dir
+./driver.sh:31> popd
+./driver.sh:33> sha256sum build/B.dir/B.swiftmodule build/B.dir/b.swift.o
287b153c93fb2a3f18f2e59ca0ea7d55eec8e0ea353d425d4f86e09ece5fdca5  build/B.dir/B.swiftmodule
9fa495a83f7361404ad57dc248e60732f978857b1f91e2a26c8fa0a0c2db7907  build/B.dir/b.swift.o
+./driver.sh:37> perl -pe 's/\${PWD}/$ENV{PWD}/g' A-new-output-file-map.in.json
+./driver.sh:39> pushd build/A.dir
+./driver.sh:40> swiftc -j 10 -num-threads 10 -c -module-cache-path /repro/build/module-cache -parse-as-library -static -emit-module -emit-module-path A.swiftmodule -emit-module-interface -module-name A -module-link-name A -incremental -output-file-map ofm.json /repro/a-new.swift
<unknown>:0: warning: module interfaces are only supported with -enable-library-evolution
<unknown>:0: warning: module interfaces are only supported with -enable-library-evolution
+./driver.sh:41> cat A.swiftinterface
// swift-interface-format-version: 1.0
// swift-compiler-version: Swift version 6.2.1 (swift-6.2.1-RELEASE)
// swift-module-flags: -target aarch64-unknown-linux-gnu -disable-objc-interop -module-link-name A -static -module-name A
// swift-module-flags-ignorable:  -formal-cxx-interoperability-mode=off -interface-compiler-version 6.2.1
import Swift
import _Concurrency
import _StringProcessing
import _SwiftConcurrencyShims
public func callA()
+./driver.sh:42> popd
+./driver.sh:44> sha256sum build/A.dir/A.swiftmodule build/A.dir/a.swift.o
d9b1001782a21b042b9ecd1a1440458018bb592b88a33cdc384f5f72f6ea0042  build/A.dir/A.swiftmodule
c01e875369087a7b59b92d2101a765cd7edea7454bda47fcd55bc663c654414d  build/A.dir/a.swift.o
+./driver.sh:47> pushd build/B.dir
+./driver.sh:48> swiftc -j 10 -num-threads 10 -c -module-cache-path /repro/build/module-cache -parse-as-library -static -emit-module -emit-module-path B.swiftmodule -module-name B -module-link-name B -incremental -output-file-map ofm.json /repro/b.swift -I /repro/build/A.dir
+./driver.sh:49> r=0 
+./driver.sh:50> popd
+./driver.sh:53> [[ 0 -ne 0 ]]
+./driver.sh:57> sha256sum build/B.dir/B.swiftmodule build/B.dir/b.swift.o
287b153c93fb2a3f18f2e59ca0ea7d55eec8e0ea353d425d4f86e09ece5fdca5  build/B.dir/B.swiftmodule
9fa495a83f7361404ad57dc248e60732f978857b1f91e2a26c8fa0a0c2db7907  build/B.dir/b.swift.o
+./driver.sh:59> exit 1

I'm seeing the compiler work correctly and report the expected build failure 5% of the time (with swiftinterfaces enabled):

/repro/b.swift:2:30: error: cannot convert value of type 'Void' to expected argument type 'Int'                                                                                                                                                   
1 | import A                                                                                                                                                                                                                                      
2 | public func callB() -> Int { callA() + 1 }                                                                                                                                                                                                    
  |                              `- error: cannot convert value of type 'Void' to expected argument type 'Int'                                                                                                                                    
3 |                                     

The expected behavior occurs less than that when only emitting binary swiftmodule files. Deleting the module-cache between each swiftc invocation appears to clear things up, so this seems like a module caching bug.

I've attached the reproducer files here: repro.tar.gz

Metadata

Metadata

Assignees

No one assigned

    Labels

    multiple filesFlag: An issue whose reproduction requires multiple filesmultiple modulesFlag: An issue whose reproduction requires multiple modulesswift 6.2

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions