Simon Jakobi pushed to branch wip/sjakobi/T2057 at Glasgow Haskell Compiler / GHC
Commits:
-
500dde14
by Simon Jakobi at 2026-03-09T01:18:11+01:00
-
2dea8545
by Simon Jakobi at 2026-03-09T01:22:05+01:00
-
f6f4894e
by Simon Jakobi at 2026-03-09T01:32:25+01:00
-
4f3ec643
by Simon Jakobi at 2026-03-09T01:33:36+01:00
-
89ed54d6
by Simon Jakobi at 2026-03-09T01:41:47+01:00
11 changed files:
- 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.conf.in
- testsuite/tests/driver/T2057/pkgA1/A.hs
- testsuite/tests/driver/T2057/pkgA2.conf.in
- testsuite/tests/driver/T2057/pkgA2/A.hs
- testsuite/tests/driver/T2057/pkgB.conf.in
- testsuite/tests/driver/T2057/pkgB/B.hs
Changes:
| ... | ... | @@ -9,6 +9,7 @@ PKGA2 = $(WORK)/pkgA2 |
| 9 | 9 | PKGB = $(WORK)/pkgB
|
| 10 | 10 | APP = $(WORK)/app
|
| 11 | 11 | OUT = $(WORK)/T2057.out
|
| 12 | +BASE_ID := $(shell "$(GHC_PKG)" field base id --simple-output)
|
|
| 12 | 13 | |
| 13 | 14 | .PHONY: T2057 clean
|
| 14 | 15 | |
| ... | ... | @@ -16,31 +17,31 @@ clean: |
| 16 | 17 | rm -rf $(WORK)
|
| 17 | 18 | |
| 18 | 19 | # Dependency graph:
|
| 19 | -# pkgB is built against pkgA1.
|
|
| 20 | -# We then rebuild the same installed unit id (pkgA1-1) from the pkgA2 sources,
|
|
| 21 | -# leaving pkgB with a stale unfolding that still references pkgA1's old API.
|
|
| 22 | -# Compiling Main against pkgB should therefore stop at the interface error.
|
|
| 20 | +# pkgB is built against pkgA1, where A exports f1.
|
|
| 21 | +# We then rebuild the same installed unit id (pkgA-1) from the pkgA2 sources,
|
|
| 22 | +# where A instead exports f2.
|
|
| 23 | +# Reading B.hi therefore finds an unfolding for g that still refers to f1,
|
|
| 24 | +# and compiling Main against pkgB should stop at the interface error.
|
|
| 23 | 25 | T2057: clean
|
| 24 | - @set -eu; \
|
|
| 25 | - BASE_ID=`'$(GHC_PKG)' field base id --simple-output`; \
|
|
| 26 | - mkdir -p '$(PKGA1)' '$(PKGA2)' '$(PKGB)' '$(APP)'; \
|
|
| 27 | - '$(GHC_PKG)' init '$(PKGDB)'; \
|
|
| 28 | - '$(TEST_HC)' $(TEST_HC_OPTS) -v0 -package-db '$(PKGDB)' -this-unit-id pkgA1-1 -O -c pkgA1/A.hs -outputdir '$(PKGA1)'; \
|
|
| 29 | - ar q '$(PKGA1)/libHSpkgA1-1.a' '$(PKGA1)/A.o' >/dev/null 2>&1; \
|
|
| 30 | - sed "s|@BASE_ID@|$$BASE_ID|g" pkgA1.conf.in >'$(WORK)/pkgA1.conf'; \
|
|
| 31 | - '$(GHC_PKG)' --package-db '$(PKGDB)' register '$(WORK)/pkgA1.conf' >/dev/null; \
|
|
| 32 | - '$(TEST_HC)' $(TEST_HC_OPTS) -v0 -package-db '$(PKGDB)' -package pkgA1 -this-unit-id pkgB-1 -O -c pkgB/B.hs -outputdir '$(PKGB)'; \
|
|
| 33 | - ar q '$(PKGB)/libHSpkgB-1.a' '$(PKGB)/B.o' >/dev/null 2>&1; \
|
|
| 34 | - sed "s|@BASE_ID@|$$BASE_ID|g" pkgB.conf.in >'$(WORK)/pkgB.conf'; \
|
|
| 35 | - '$(GHC_PKG)' --package-db '$(PKGDB)' register '$(WORK)/pkgB.conf' >/dev/null; \
|
|
| 36 | - '$(TEST_HC)' $(TEST_HC_OPTS) -v0 -package-db '$(PKGDB)' -this-unit-id pkgA1-1 -O -c pkgA2/A.hs -outputdir '$(PKGA2)'; \
|
|
| 37 | - ar q '$(PKGA2)/libHSpkgA1-1.a' '$(PKGA2)/A.o' >/dev/null 2>&1; \
|
|
| 38 | - sed "s|@BASE_ID@|$$BASE_ID|g" pkgA2.conf.in >'$(WORK)/pkgA2.conf'; \
|
|
| 39 | - '$(GHC_PKG)' --package-db '$(PKGDB)' update '$(WORK)/pkgA2.conf' >/dev/null; \
|
|
| 40 | - status=0; \
|
|
| 41 | - '$(TEST_HC)' $(TEST_HC_OPTS) -v0 --make app/Main.hs -O -fforce-recomp -package-db '$(PKGDB)' -package pkgB >'$(OUT)' 2>&1 || status=$$?; \
|
|
| 42 | - if [ $$status -eq 0 ]; then \
|
|
| 43 | - echo "expected compilation failure" >&2; \
|
|
| 44 | - exit 1; \
|
|
| 45 | - fi; \
|
|
| 46 | - sed "s#`pwd`/##g" '$(OUT)' >&2 |
|
| 26 | + # Create an isolated package DB and output directories for the repro.
|
|
| 27 | + mkdir -p '$(PKGA1)' '$(PKGA2)' '$(PKGB)' '$(APP)'
|
|
| 28 | + '$(GHC_PKG)' init '$(PKGDB)'
|
|
| 29 | + # Build and register pkgA1, the original version of A.
|
|
| 30 | + '$(TEST_HC)' $(TEST_HC_OPTS) -v0 -package-db '$(PKGDB)' -this-unit-id pkgA-1 -O -c pkgA1/A.hs -outputdir '$(PKGA1)'
|
|
| 31 | + ar q '$(PKGA1)/libHSpkgA-1.a' '$(PKGA1)/A.o' >/dev/null 2>&1
|
|
| 32 | + sed "s|@BASE_ID@|$(BASE_ID)|g" pkgA1.conf.in >'$(WORK)/pkgA1.conf'
|
|
| 33 | + '$(GHC_PKG)' --package-db '$(PKGDB)' register '$(WORK)/pkgA1.conf' >/dev/null
|
|
| 34 | + # Build and register pkgB against pkgA1 so B.hi records the unfolding of g = f1.
|
|
| 35 | + '$(TEST_HC)' $(TEST_HC_OPTS) -v0 -package-db '$(PKGDB)' -package pkgA1 -this-unit-id pkgB-1 -O -c pkgB/B.hs -outputdir '$(PKGB)'
|
|
| 36 | + ar q '$(PKGB)/libHSpkgB-1.a' '$(PKGB)/B.o' >/dev/null 2>&1
|
|
| 37 | + sed "s|@BASE_ID@|$(BASE_ID)|g" pkgB.conf.in >'$(WORK)/pkgB.conf'
|
|
| 38 | + '$(GHC_PKG)' --package-db '$(PKGDB)' register '$(WORK)/pkgB.conf' >/dev/null
|
|
| 39 | + # Rebuild the same installed unit id from pkgA2, replacing f1 with f2.
|
|
| 40 | + '$(TEST_HC)' $(TEST_HC_OPTS) -v0 -package-db '$(PKGDB)' -this-unit-id pkgA-1 -O -c pkgA2/A.hs -outputdir '$(PKGA2)'
|
|
| 41 | + ar q '$(PKGA2)/libHSpkgA-1.a' '$(PKGA2)/A.o' >/dev/null 2>&1
|
|
| 42 | + sed "s|@BASE_ID@|$(BASE_ID)|g" pkgA2.conf.in >'$(WORK)/pkgA2.conf'
|
|
| 43 | + '$(GHC_PKG)' --package-db '$(PKGDB)' update '$(WORK)/pkgA2.conf' >/dev/null
|
|
| 44 | + # Compiling Main against pkgB should now fail while loading the stale B.hi.
|
|
| 45 | + ! '$(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; }
|
|
| 46 | + # Strip the absolute test directory prefix before comparing against T2057.stderr.
|
|
| 47 | + 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. `pkgB` is compiled
|
|
| 5 | +against one version of package `A`, then the same unit id is replaced by an
|
|
| 6 | +incompatible build of `A`. When `Main` imports `B`, GHC has to read `B.hi`,
|
|
| 7 | +finds an unfolding that still mentions the old `A`, and should fail while
|
|
| 8 | +loading interfaces.
|
|
| 9 | + |
|
| 10 | +The golden [`T2057.stderr`](T2057.stderr) captures the expected behaviour on a
|
|
| 11 | +fixed compiler: report the missing declaration from the stale interface and
|
|
| 12 | +then abort with `Cannot continue after interface file error`. Any linker output
|
|
| 13 | +would be a regression. |
| 1 | 1 | work/pkgB/B.hi
|
| 2 | -Declaration for saved
|
|
| 3 | -Unfolding of saved:
|
|
| 4 | - staleDependencyBinding ErrorWithoutFlag
|
|
| 5 | - Can't find interface-file declaration for variable staleDependencyBinding
|
|
| 2 | +Declaration for g
|
|
| 3 | +Unfolding of g:
|
|
| 4 | + f1 ErrorWithoutFlag
|
|
| 5 | + Can't find interface-file declaration for variable f1
|
|
| 6 | 6 | Probable cause: bug in .hi-boot file, or inconsistent .hi file
|
| 7 | 7 | Use -ddump-if-trace to get an idea of which file caused the error
|
| 8 | 8 | <no location info>:
|
| 1 | 1 | test(
|
| 2 | 2 | 'T2057',
|
| 3 | - [ extra_files(['pkgA1', 'pkgA2', 'pkgB', 'app', 'pkgA1.conf.in', 'pkgA2.conf.in', 'pkgB.conf.in'])
|
|
| 3 | + [ extra_files(['pkgA1', 'pkgA2', 'pkgB', 'app', 'README.md', 'pkgA1.conf.in', 'pkgA2.conf.in', 'pkgB.conf.in'])
|
|
| 4 | 4 | , when(opsys('mingw32'), skip)
|
| 5 | 5 | , js_skip
|
| 6 | 6 | , wasm_skip
|
| ... | ... | @@ -3,4 +3,4 @@ module Main where |
| 3 | 3 | import B
|
| 4 | 4 | |
| 5 | 5 | main :: IO ()
|
| 6 | -main = print (saved 41) |
|
| 6 | +main = print (g 41) |
| 1 | 1 | name: pkgA1
|
| 2 | 2 | version: 1.0
|
| 3 | -id: pkgA1-1
|
|
| 4 | -key: pkgA1-1
|
|
| 3 | +id: pkgA-1
|
|
| 4 | +key: pkgA-1
|
|
| 5 | 5 | exposed: True
|
| 6 | 6 | exposed-modules: A
|
| 7 | 7 | import-dirs: ${pkgroot}/pkgA1
|
| 8 | 8 | library-dirs: ${pkgroot}/pkgA1
|
| 9 | 9 | dynamic-library-dirs: ${pkgroot}/pkgA1
|
| 10 | -hs-libraries: HSpkgA1-1
|
|
| 10 | +hs-libraries: HSpkgA-1
|
|
| 11 | 11 | depends: @BASE_ID@ |
| 1 | -module A (staleDependencyBinding) where
|
|
| 1 | +module A (f1) where
|
|
| 2 | 2 | |
| 3 | -{-# INLINE staleDependencyBinding #-}
|
|
| 4 | -staleDependencyBinding :: Int -> Int
|
|
| 5 | -staleDependencyBinding x = x + 1 |
|
| 3 | +{-# INLINE f1 #-}
|
|
| 4 | +f1 :: Int -> Int
|
|
| 5 | +f1 x = x + 1 |
| 1 | 1 | name: pkgA1
|
| 2 | 2 | version: 1.0
|
| 3 | -id: pkgA1-1
|
|
| 4 | -key: pkgA1-1
|
|
| 3 | +id: pkgA-1
|
|
| 4 | +key: pkgA-1
|
|
| 5 | 5 | exposed: True
|
| 6 | 6 | exposed-modules: A
|
| 7 | 7 | import-dirs: ${pkgroot}/pkgA2
|
| 8 | 8 | library-dirs: ${pkgroot}/pkgA2
|
| 9 | 9 | dynamic-library-dirs: ${pkgroot}/pkgA2
|
| 10 | -hs-libraries: HSpkgA1-1
|
|
| 10 | +hs-libraries: HSpkgA-1
|
|
| 11 | 11 | depends: @BASE_ID@ |
| 1 | -module A (replacementBinding) where
|
|
| 1 | +module A (f2) where
|
|
| 2 | 2 | |
| 3 | -replacementBinding :: Int -> Int
|
|
| 4 | -replacementBinding x = x + 100 |
|
| 3 | +f2 :: Int -> Int
|
|
| 4 | +f2 x = x + 100 |
| ... | ... | @@ -8,4 +8,4 @@ import-dirs: ${pkgroot}/pkgB |
| 8 | 8 | library-dirs: ${pkgroot}/pkgB
|
| 9 | 9 | dynamic-library-dirs: ${pkgroot}/pkgB
|
| 10 | 10 | hs-libraries: HSpkgB-1
|
| 11 | -depends: pkgA1-1 @BASE_ID@ |
|
| 11 | +depends: pkgA-1 @BASE_ID@ |
| 1 | -module B (saved) where
|
|
| 1 | +module B (g) where
|
|
| 2 | 2 | |
| 3 | 3 | import A
|
| 4 | 4 | |
| 5 | -{-# INLINE saved #-}
|
|
| 6 | -saved :: Int -> Int
|
|
| 7 | -saved x = staleDependencyBinding x |
|
| 5 | +{-# INLINE g #-}
|
|
| 6 | +g :: Int -> Int
|
|
| 7 | +g x = f1 x |