Simon Jakobi pushed to branch wip/sjakobi/T2057 at Glasgow Haskell Compiler / GHC Commits: d968566a by Simon Jakobi at 2026-03-09T02:40:59+01:00 Add regression test for #2057 Test that GHC stops after an interface-file error instead of continuing into the linker. The test constructs a stale package dependency on purpose. `pkgB` is compiled against one version of package `A`, then the same unit id is replaced by an incompatible build of `A`. When `Main` imports `B`, GHC has to read `B.hi`, finds an unfolding that still mentions the old `A`, and should fail while loading interfaces. Closes #2057. Assisted-by: Codex - - - - - 12 changed files: - + testsuite/tests/driver/T2057/.gitignore - + testsuite/tests/driver/T2057/Makefile - + testsuite/tests/driver/T2057/README.md - + testsuite/tests/driver/T2057/T2057.stderr - + testsuite/tests/driver/T2057/all.T - + testsuite/tests/driver/T2057/app/Main.hs - + testsuite/tests/driver/T2057/pkgA1/A.hs - + testsuite/tests/driver/T2057/pkgA1/pkg.conf.in - + testsuite/tests/driver/T2057/pkgA2/A.hs - + testsuite/tests/driver/T2057/pkgA2/pkg.conf.in - + testsuite/tests/driver/T2057/pkgB/B.hs - + testsuite/tests/driver/T2057/pkgB/pkg.conf.in Changes: ===================================== testsuite/tests/driver/T2057/.gitignore ===================================== @@ -0,0 +1 @@ +work/ ===================================== testsuite/tests/driver/T2057/Makefile ===================================== @@ -0,0 +1,60 @@ +TOP=../../.. +include $(TOP)/mk/boilerplate.mk +include $(TOP)/mk/test.mk + +WORK = work +PKGDB = $(WORK)/pkgdb +PKGA1 = $(WORK)/pkgA1 +PKGA2 = $(WORK)/pkgA2 +PKGB = $(WORK)/pkgB +APP = $(WORK)/app +OUT = $(WORK)/T2057.out +BASE_ID := $(shell "$(GHC_PKG)" field base id --simple-output) + +.PHONY: T2057 clean + +clean: + rm -rf $(WORK) + +# Dependency graph: +# pkgA is first registered from the pkgA1 source tree, where A exports f1. +# pkgB is built against this pkgA. +# We then rebuild that same package from the pkgA2 source tree, where A +# instead exports f2. Reading B.hi therefore finds an unfolding for g that +# still refers to f1, and compiling Main against pkgB should stop at the +# interface error. +T2057: clean + + # Create an isolated package DB and output directories for the repro. + mkdir -p '$(PKGA1)' '$(PKGA2)' '$(PKGB)' '$(APP)' + '$(GHC_PKG)' init '$(PKGDB)' + + # Build and register pkgA from the pkgA1 sources. + '$(TEST_HC)' $(TEST_HC_OPTS) -v0 -package-db '$(PKGDB)' \ + -this-unit-id pkgA -O -c pkgA1/A.hs -outputdir '$(PKGA1)' + ar q '$(PKGA1)/libHSpkgA.a' '$(PKGA1)/A.o' >/dev/null 2>&1 + sed "s|@BASE_ID@|$(BASE_ID)|g" pkgA1/pkg.conf.in >'$(WORK)/pkgA1.conf' + '$(GHC_PKG)' --package-db '$(PKGDB)' register '$(WORK)/pkgA1.conf' >/dev/null + + # Build and register pkgB against pkgA so B.hi records the unfolding of g = f1. + '$(TEST_HC)' $(TEST_HC_OPTS) -v0 -package-db '$(PKGDB)' \ + -package pkgA -this-unit-id pkgB -O -c pkgB/B.hs \ + -outputdir '$(PKGB)' + ar q '$(PKGB)/libHSpkgB.a' '$(PKGB)/B.o' >/dev/null 2>&1 + sed "s|@BASE_ID@|$(BASE_ID)|g" pkgB/pkg.conf.in >'$(WORK)/pkgB.conf' + '$(GHC_PKG)' --package-db '$(PKGDB)' register '$(WORK)/pkgB.conf' >/dev/null + + # Rebuild pkgA from the pkgA2 source tree, replacing f1 with f2. + '$(TEST_HC)' $(TEST_HC_OPTS) -v0 -package-db '$(PKGDB)' \ + -this-unit-id pkgA -O -c pkgA2/A.hs -outputdir '$(PKGA2)' + ar q '$(PKGA2)/libHSpkgA.a' '$(PKGA2)/A.o' >/dev/null 2>&1 + sed "s|@BASE_ID@|$(BASE_ID)|g" pkgA2/pkg.conf.in >'$(WORK)/pkgA2.conf' + '$(GHC_PKG)' --package-db '$(PKGDB)' update '$(WORK)/pkgA2.conf' >/dev/null + + # Compiling Main against pkgB should now fail while loading the stale B.hi. + ! '$(TEST_HC)' $(TEST_HC_OPTS) -v0 --make app/Main.hs \ + -O -fforce-recomp -package-db '$(PKGDB)' -package pkgB \ + >'$(OUT)' 2>&1 || { echo "expected compilation failure" >&2; exit 1; } + + # Strip the absolute test directory prefix before comparing against T2057.stderr. + sed "s#$(CURDIR)/##g" '$(OUT)' >&2 ===================================== testsuite/tests/driver/T2057/README.md ===================================== @@ -0,0 +1,20 @@ +`T2057` checks that GHC stops after an interface-file error instead of +continuing into the linker. + +The test constructs a stale package dependency on purpose. The two directories +`pkgA1/` and `pkgA2/` are just two source trees for the same registered +package, `pkgA`. + +The Makefile first registers `pkgA` from `pkgA1/`, where module `A` exports +`f1`. It then builds `pkgB` against that package, so `B.hi` records an +unfolding `g = f1`. + +After that, the Makefile updates the same package `pkgA` from `pkgA2/`, where +module `A` exports `f2` instead. When `Main` imports `B`, GHC has to load +`B.hi`, sees the stale reference to `f1`, and should fail while loading +interfaces. + +The golden [`T2057.stderr`](T2057.stderr) captures the fixed behaviour: +diagnose the missing declaration in the stale interface and then stop with +`Cannot continue after interface file error`. Any linker output would be a +regression. ===================================== testsuite/tests/driver/T2057/T2057.stderr ===================================== @@ -0,0 +1,9 @@ +work/pkgB/B.hi +Declaration for g +Unfolding of g: + f1 ErrorWithoutFlag + Can't find interface-file declaration for variable f1 + Probable cause: bug in .hi-boot file, or inconsistent .hi file + Use -ddump-if-trace to get an idea of which file caused the error +<no location info>: + Cannot continue after interface file error ===================================== testsuite/tests/driver/T2057/all.T ===================================== @@ -0,0 +1,11 @@ +test( + 'T2057', + [ extra_files(['pkgA1', 'pkgA2', 'pkgB', 'app', 'README.md']) + , when(opsys('mingw32'), skip) + , js_skip + , wasm_skip + , ignore_stdout + ], + makefile_test, + [] +) ===================================== testsuite/tests/driver/T2057/app/Main.hs ===================================== @@ -0,0 +1,6 @@ +module Main where + +import B + +main :: IO () +main = print (g 41) ===================================== testsuite/tests/driver/T2057/pkgA1/A.hs ===================================== @@ -0,0 +1,5 @@ +module A (f1) where + +{-# INLINE f1 #-} +f1 :: Int -> Int +f1 x = x + 1 ===================================== testsuite/tests/driver/T2057/pkgA1/pkg.conf.in ===================================== @@ -0,0 +1,11 @@ +name: pkgA +version: 1.0 +id: pkgA +key: pkgA +exposed: True +exposed-modules: A +import-dirs: ${pkgroot}/pkgA1 +library-dirs: ${pkgroot}/pkgA1 +dynamic-library-dirs: ${pkgroot}/pkgA1 +hs-libraries: HSpkgA +depends: @BASE_ID@ ===================================== testsuite/tests/driver/T2057/pkgA2/A.hs ===================================== @@ -0,0 +1,4 @@ +module A (f2) where + +f2 :: Int -> Int +f2 x = x + 100 ===================================== testsuite/tests/driver/T2057/pkgA2/pkg.conf.in ===================================== @@ -0,0 +1,11 @@ +name: pkgA +version: 1.0 +id: pkgA +key: pkgA +exposed: True +exposed-modules: A +import-dirs: ${pkgroot}/pkgA2 +library-dirs: ${pkgroot}/pkgA2 +dynamic-library-dirs: ${pkgroot}/pkgA2 +hs-libraries: HSpkgA +depends: @BASE_ID@ ===================================== testsuite/tests/driver/T2057/pkgB/B.hs ===================================== @@ -0,0 +1,7 @@ +module B (g) where + +import A + +{-# INLINE g #-} +g :: Int -> Int +g x = f1 x ===================================== testsuite/tests/driver/T2057/pkgB/pkg.conf.in ===================================== @@ -0,0 +1,11 @@ +name: pkgB +version: 1.0 +id: pkgB +key: pkgB +exposed: True +exposed-modules: B +import-dirs: ${pkgroot}/pkgB +library-dirs: ${pkgroot}/pkgB +dynamic-library-dirs: ${pkgroot}/pkgB +hs-libraries: HSpkgB +depends: pkgA @BASE_ID@ View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/d968566af553c83a5d113565c2453e3b... -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/d968566af553c83a5d113565c2453e3b... You're receiving this email because of your account on gitlab.haskell.org.
participants (1)
-
Simon Jakobi (@sjakobi2)