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
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:
| 1 | +work/ |
| 1 | +TOP=../../..
|
|
| 2 | +include $(TOP)/mk/boilerplate.mk
|
|
| 3 | +include $(TOP)/mk/test.mk
|
|
| 4 | + |
|
| 5 | +WORK = work
|
|
| 6 | +PKGDB = $(WORK)/pkgdb
|
|
| 7 | +PKGA1 = $(WORK)/pkgA1
|
|
| 8 | +PKGA2 = $(WORK)/pkgA2
|
|
| 9 | +PKGB = $(WORK)/pkgB
|
|
| 10 | +APP = $(WORK)/app
|
|
| 11 | +OUT = $(WORK)/T2057.out
|
|
| 12 | +BASE_ID := $(shell "$(GHC_PKG)" field base id --simple-output)
|
|
| 13 | + |
|
| 14 | +.PHONY: T2057 clean
|
|
| 15 | + |
|
| 16 | +clean:
|
|
| 17 | + rm -rf $(WORK)
|
|
| 18 | + |
|
| 19 | +# Dependency graph:
|
|
| 20 | +# pkgA is first registered from the pkgA1 source tree, where A exports f1.
|
|
| 21 | +# pkgB is built against this pkgA.
|
|
| 22 | +# We then rebuild that same package from the pkgA2 source tree, where A
|
|
| 23 | +# instead exports f2. Reading B.hi therefore finds an unfolding for g that
|
|
| 24 | +# still refers to f1, and compiling Main against pkgB should stop at the
|
|
| 25 | +# interface error.
|
|
| 26 | +T2057: clean
|
|
| 27 | + |
|
| 28 | + # Create an isolated package DB and output directories for the repro.
|
|
| 29 | + mkdir -p '$(PKGA1)' '$(PKGA2)' '$(PKGB)' '$(APP)'
|
|
| 30 | + '$(GHC_PKG)' init '$(PKGDB)'
|
|
| 31 | + |
|
| 32 | + # Build and register pkgA from the pkgA1 sources.
|
|
| 33 | + '$(TEST_HC)' $(TEST_HC_OPTS) -v0 -package-db '$(PKGDB)' \
|
|
| 34 | + -this-unit-id pkgA -O -c pkgA1/A.hs -outputdir '$(PKGA1)'
|
|
| 35 | + ar q '$(PKGA1)/libHSpkgA.a' '$(PKGA1)/A.o' >/dev/null 2>&1
|
|
| 36 | + sed "s|@BASE_ID@|$(BASE_ID)|g" pkgA1/pkg.conf.in >'$(WORK)/pkgA1.conf'
|
|
| 37 | + '$(GHC_PKG)' --package-db '$(PKGDB)' register '$(WORK)/pkgA1.conf' >/dev/null
|
|
| 38 | + |
|
| 39 | + # Build and register pkgB against pkgA so B.hi records the unfolding of g = f1.
|
|
| 40 | + '$(TEST_HC)' $(TEST_HC_OPTS) -v0 -package-db '$(PKGDB)' \
|
|
| 41 | + -package pkgA -this-unit-id pkgB -O -c pkgB/B.hs \
|
|
| 42 | + -outputdir '$(PKGB)'
|
|
| 43 | + ar q '$(PKGB)/libHSpkgB.a' '$(PKGB)/B.o' >/dev/null 2>&1
|
|
| 44 | + sed "s|@BASE_ID@|$(BASE_ID)|g" pkgB/pkg.conf.in >'$(WORK)/pkgB.conf'
|
|
| 45 | + '$(GHC_PKG)' --package-db '$(PKGDB)' register '$(WORK)/pkgB.conf' >/dev/null
|
|
| 46 | + |
|
| 47 | + # Rebuild pkgA from the pkgA2 source tree, replacing f1 with f2.
|
|
| 48 | + '$(TEST_HC)' $(TEST_HC_OPTS) -v0 -package-db '$(PKGDB)' \
|
|
| 49 | + -this-unit-id pkgA -O -c pkgA2/A.hs -outputdir '$(PKGA2)'
|
|
| 50 | + ar q '$(PKGA2)/libHSpkgA.a' '$(PKGA2)/A.o' >/dev/null 2>&1
|
|
| 51 | + sed "s|@BASE_ID@|$(BASE_ID)|g" pkgA2/pkg.conf.in >'$(WORK)/pkgA2.conf'
|
|
| 52 | + '$(GHC_PKG)' --package-db '$(PKGDB)' update '$(WORK)/pkgA2.conf' >/dev/null
|
|
| 53 | + |
|
| 54 | + # Compiling Main against pkgB should now fail while loading the stale B.hi.
|
|
| 55 | + ! '$(TEST_HC)' $(TEST_HC_OPTS) -v0 --make app/Main.hs \
|
|
| 56 | + -O -fforce-recomp -package-db '$(PKGDB)' -package pkgB \
|
|
| 57 | + >'$(OUT)' 2>&1 || { echo "expected compilation failure" >&2; exit 1; }
|
|
| 58 | + |
|
| 59 | + # Strip the absolute test directory prefix before comparing against T2057.stderr.
|
|
| 60 | + sed "s#$(CURDIR)/##g" '$(OUT)' >&2 |
| 1 | +`T2057` checks that GHC stops after an interface-file error instead of
|
|
| 2 | +continuing into the linker.
|
|
| 3 | + |
|
| 4 | +The test constructs a stale package dependency on purpose. The two directories
|
|
| 5 | +`pkgA1/` and `pkgA2/` are just two source trees for the same registered
|
|
| 6 | +package, `pkgA`.
|
|
| 7 | + |
|
| 8 | +The Makefile first registers `pkgA` from `pkgA1/`, where module `A` exports
|
|
| 9 | +`f1`. It then builds `pkgB` against that package, so `B.hi` records an
|
|
| 10 | +unfolding `g = f1`.
|
|
| 11 | + |
|
| 12 | +After that, the Makefile updates the same package `pkgA` from `pkgA2/`, where
|
|
| 13 | +module `A` exports `f2` instead. When `Main` imports `B`, GHC has to load
|
|
| 14 | +`B.hi`, sees the stale reference to `f1`, and should fail while loading
|
|
| 15 | +interfaces.
|
|
| 16 | + |
|
| 17 | +The golden [`T2057.stderr`](T2057.stderr) captures the fixed behaviour:
|
|
| 18 | +diagnose the missing declaration in the stale interface and then stop with
|
|
| 19 | +`Cannot continue after interface file error`. Any linker output would be a
|
|
| 20 | +regression. |
| 1 | +work/pkgB/B.hi
|
|
| 2 | +Declaration for g
|
|
| 3 | +Unfolding of g:
|
|
| 4 | + f1 ErrorWithoutFlag
|
|
| 5 | + Can't find interface-file declaration for variable f1
|
|
| 6 | + Probable cause: bug in .hi-boot file, or inconsistent .hi file
|
|
| 7 | + Use -ddump-if-trace to get an idea of which file caused the error
|
|
| 8 | +<no location info>:
|
|
| 9 | + Cannot continue after interface file error |
| 1 | +test(
|
|
| 2 | + 'T2057',
|
|
| 3 | + [ extra_files(['pkgA1', 'pkgA2', 'pkgB', 'app', 'README.md'])
|
|
| 4 | + , when(opsys('mingw32'), skip)
|
|
| 5 | + , js_skip
|
|
| 6 | + , wasm_skip
|
|
| 7 | + , ignore_stdout
|
|
| 8 | + ],
|
|
| 9 | + makefile_test,
|
|
| 10 | + []
|
|
| 11 | +) |
| 1 | +module Main where
|
|
| 2 | + |
|
| 3 | +import B
|
|
| 4 | + |
|
| 5 | +main :: IO ()
|
|
| 6 | +main = print (g 41) |
| 1 | +module A (f1) where
|
|
| 2 | + |
|
| 3 | +{-# INLINE f1 #-}
|
|
| 4 | +f1 :: Int -> Int
|
|
| 5 | +f1 x = x + 1 |
| 1 | +name: pkgA
|
|
| 2 | +version: 1.0
|
|
| 3 | +id: pkgA
|
|
| 4 | +key: pkgA
|
|
| 5 | +exposed: True
|
|
| 6 | +exposed-modules: A
|
|
| 7 | +import-dirs: ${pkgroot}/pkgA1
|
|
| 8 | +library-dirs: ${pkgroot}/pkgA1
|
|
| 9 | +dynamic-library-dirs: ${pkgroot}/pkgA1
|
|
| 10 | +hs-libraries: HSpkgA
|
|
| 11 | +depends: @BASE_ID@ |
| 1 | +module A (f2) where
|
|
| 2 | + |
|
| 3 | +f2 :: Int -> Int
|
|
| 4 | +f2 x = x + 100 |
| 1 | +name: pkgA
|
|
| 2 | +version: 1.0
|
|
| 3 | +id: pkgA
|
|
| 4 | +key: pkgA
|
|
| 5 | +exposed: True
|
|
| 6 | +exposed-modules: A
|
|
| 7 | +import-dirs: ${pkgroot}/pkgA2
|
|
| 8 | +library-dirs: ${pkgroot}/pkgA2
|
|
| 9 | +dynamic-library-dirs: ${pkgroot}/pkgA2
|
|
| 10 | +hs-libraries: HSpkgA
|
|
| 11 | +depends: @BASE_ID@ |
| 1 | +module B (g) where
|
|
| 2 | + |
|
| 3 | +import A
|
|
| 4 | + |
|
| 5 | +{-# INLINE g #-}
|
|
| 6 | +g :: Int -> Int
|
|
| 7 | +g x = f1 x |
| 1 | +name: pkgB
|
|
| 2 | +version: 1.0
|
|
| 3 | +id: pkgB
|
|
| 4 | +key: pkgB
|
|
| 5 | +exposed: True
|
|
| 6 | +exposed-modules: B
|
|
| 7 | +import-dirs: ${pkgroot}/pkgB
|
|
| 8 | +library-dirs: ${pkgroot}/pkgB
|
|
| 9 | +dynamic-library-dirs: ${pkgroot}/pkgB
|
|
| 10 | +hs-libraries: HSpkgB
|
|
| 11 | +depends: pkgA @BASE_ID@ |