Skip to content

Commit bde9483

Browse files
committed
Update ExternsDiff.checkUsage, add tests
1 parent 1d25867 commit bde9483

2 files changed

Lines changed: 77 additions & 41 deletions

File tree

src/Language/PureScript/Make/ExternsDiff.hs

Lines changed: 20 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -241,21 +241,22 @@ checkDiffs (P.Module _ _ _ decls exports) diffs
241241
-- Goes though the module and try to find any usage of the refs.
242242
-- Takes a set of refs to search in module's declarations, if found returns True.
243243
checkUsage :: Set (Maybe ModuleName, Ref) -> [P.Declaration] -> Bool
244-
checkUsage searches decls = foldMap findUsage decls /= mempty
244+
checkUsage searches decls = anyUsages
245245
where
246-
findUsage decl =
247-
let (extr, _, _, _, _) = P.everythingWithScope goDecl goExpr goBinder mempty mempty
248-
in extr mempty decl
246+
-- Two traversals: one to pick up usages of types, one for the rest.
247+
Any anyUsages =
248+
foldMap checkUsageInTypes decls
249+
<> foldMap checkOtherUsages decls
249250

250-
toSearched = (,) <$> P.getQual <*> P.disqualify
251+
-- To check data constructors we remove an origin type from it (see `checkCtor`).
252+
searches' = S.map (map stripCtorType) searches
251253

252254
-- To check data constructors we remove an origin type from it.
253255
emptyName = P.ProperName ""
254256
stripCtorType (ConstructorRef _ n) = ConstructorRef emptyName n
255257
stripCtorType x = x
256258

257-
searches' = S.map (map stripCtorType) searches
258-
check = Any . flip S.member searches' . toSearched
259+
check q = Any $ S.member (P.getQual q, P.disqualify q) searches'
259260

260261
checkType = check . map TypeRef
261262
checkTypeOp = check . map TypeOpRef
@@ -264,31 +265,21 @@ checkUsage searches decls = foldMap findUsage decls /= mempty
264265
checkCtor = check . map (ConstructorRef emptyName)
265266
checkClass = check . map TypeClassRef
266267

267-
onTypes = P.everythingOnTypes (<>) $ \case
268-
P.TypeConstructor _ n -> checkType n
269-
P.TypeOp _ n -> checkTypeOp n
270-
P.ConstrainedType _ c _ -> checkClass (P.constraintClass c)
271-
_ -> mempty
272-
273-
foldCtor f (P.DataConstructorDeclaration _ _ vars) =
274-
foldMap (f . snd) vars
268+
-- A nested traversal: pick up types in the module then traverse the structure of the types
269+
(checkUsageInTypes, _, _, _, _) =
270+
P.accumTypes $ P.everythingOnTypes (<>) $ \case
271+
P.TypeConstructor _ n -> checkType n
272+
P.TypeOp _ n -> checkTypeOp n
273+
P.ConstrainedType _ c _ -> checkClass (P.constraintClass c)
274+
_ -> mempty
275275

276-
constraintTypes =
277-
foldMap (\c -> P.constraintArgs c <> P.constraintKindArgs c)
276+
checkOtherUsages =
277+
let (extr, _, _, _, _) = P.everythingWithScope goDecl goExpr goBinder mempty mempty
278+
in extr mempty
278279

279280
goDecl _ = \case
280-
P.TypeDeclaration t -> onTypes (P.tydeclType t)
281-
P.DataDeclaration _ _ _ _ ctors -> foldMap (foldCtor onTypes) ctors
282-
P.TypeSynonymDeclaration _ _ _ t -> onTypes t
283-
P.KindDeclaration _ _ _ t -> onTypes t
284-
P.FixityDeclaration _ (Right (P.TypeFixity _ tn _)) ->
285-
checkType tn
286-
P.FixityDeclaration _ (Left (P.ValueFixity _ (P.Qualified by val) _)) ->
287-
either (checkValue . P.Qualified by) (checkCtor . P.Qualified by) val
288-
P.TypeClassDeclaration _ _ _ cs _ _ ->
289-
foldMap onTypes (constraintTypes cs)
290-
P.TypeInstanceDeclaration _ _ _ _ _ cs tc sts _ ->
291-
foldMap onTypes (constraintTypes cs <> sts) <> checkClass tc
281+
P.TypeInstanceDeclaration _ _ _ _ _ _ tc _ _ ->
282+
checkClass tc
292283
_ -> mempty
293284

294285
isLocal scope ident = P.LocalIdent ident `S.member` scope
@@ -298,7 +289,6 @@ checkUsage searches decls = foldMap findUsage decls /= mempty
298289
| otherwise -> checkValue n
299290
P.Constructor _ n -> checkCtor n
300291
P.Op _ n -> checkValueOp n
301-
P.TypedValue _ _ t -> onTypes t
302292
_ -> mempty
303293

304294
goBinder _ binder = case binder of

tests/TestMake.hs

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import Language.PureScript qualified as P
99
import Language.PureScript.CST qualified as CST
1010

1111
import Control.Concurrent (threadDelay)
12-
import Control.Monad (guard, void, forM_)
12+
import Control.Monad (guard, void, forM_, when)
1313
import Control.Exception (tryJust)
1414
import Control.Monad.IO.Class (liftIO)
1515
import Control.Concurrent.MVar (readMVar, newMVar, modifyMVar_)
@@ -19,11 +19,13 @@ import Data.Text qualified as T
1919
import Data.Set (Set)
2020
import Data.Set qualified as Set
2121
import Data.Map qualified as M
22+
import Data.Version (showVersion)
2223

24+
import Paths_purescript qualified as Paths
2325
import System.FilePath ((</>))
2426
import System.Directory (createDirectory, removeDirectoryRecursive, removeFile, setModificationTime)
2527
import System.IO.Error (isDoesNotExistError)
26-
import System.IO.UTF8 (readUTF8FilesT, writeUTF8FileT)
28+
import System.IO.UTF8 (readUTF8FilesT, readUTF8FileT, writeUTF8FileT)
2729

2830
import Test.Hspec (Spec, before_, it, shouldReturn)
2931

@@ -55,6 +57,9 @@ spec = do
5557

5658
forM_ (zip [0..] modules) $ \(idx, (mn, content, _)) -> do
5759
writeFile (modulePath mn) (timestamp idx) content
60+
-- Write a fake foreign module to bypass compiler's check.
61+
when (T.isInfixOf "\nforeign import" content) $
62+
writeFile (foreignJsPath mn) (timestamp idx) content
5863

5964
compile paths `shouldReturn` moduleNames names
6065

@@ -189,15 +194,15 @@ spec = do
189194
it "recompiles if docs are requested but not up to date" $ do
190195
let mPath = sourcesDir </> "Module.purs"
191196

192-
moduleContent1 = "module Module where\nx :: Int\nx = 1"
193-
moduleContent2 = moduleContent1 <> "\ny :: Int\ny = 1"
197+
mContent1 = "module Module where\nx :: Int\nx = 1"
198+
mContent2 = mContent1 <> "\ny :: Int\ny = 1"
194199

195200
optsWithDocs = P.defaultOptions { P.optionsCodegenTargets = Set.fromList [P.JS, P.Docs] }
196201
go opts = compileWithOptions opts [mPath] >>= assertSuccess
197202

198-
writeFile mPath timestampA moduleContent1
203+
writeFile mPath timestampA mContent1
199204
go optsWithDocs `shouldReturn` moduleNames ["Module"]
200-
writeFile mPath timestampB moduleContent2
205+
writeFile mPath timestampB mContent2
201206
-- See Note [Sleeping to avoid flaky tests]
202207
threadDelay oneSecond
203208
go P.defaultOptions `shouldReturn` moduleNames ["Module"]
@@ -207,21 +212,42 @@ spec = do
207212

208213
it "recompiles if CoreFn is requested but not up to date" $ do
209214
let mPath = sourcesDir </> "Module.purs"
210-
moduleContent1 = "module Module where\nx :: Int\nx = 1"
211-
moduleContent2 = moduleContent1 <> "\ny :: Int\ny = 1"
215+
mContent1 = "module Module where\nx :: Int\nx = 1"
216+
mContent2 = mContent1 <> "\ny :: Int\ny = 1"
212217
optsCoreFnOnly = P.defaultOptions { P.optionsCodegenTargets = Set.singleton P.CoreFn }
213218
go opts = compileWithOptions opts [mPath] >>= assertSuccess
214219

215-
writeFile mPath timestampA moduleContent1
220+
writeFile mPath timestampA mContent1
216221
go optsCoreFnOnly `shouldReturn` moduleNames ["Module"]
217-
writeFile mPath timestampB moduleContent2
222+
writeFile mPath timestampB mContent2
218223
-- See Note [Sleeping to avoid flaky tests]
219224
threadDelay oneSecond
220225
go P.defaultOptions `shouldReturn` moduleNames ["Module"]
221226
-- Since the existing CoreFn.json is now outdated, the module should be
222227
-- recompiled.
223228
go optsCoreFnOnly `shouldReturn` moduleNames ["Module"]
224229

230+
it "recompiles if cache-db version differs from the current" $ do
231+
let mPath = sourcesDir </> "Module.purs"
232+
mContent = "module Module where\nfoo :: Int\nfoo = 1\n"
233+
234+
writeFile mPath timestampA mContent
235+
compile [mPath] `shouldReturn` moduleNames ["Module"]
236+
237+
-- Replace version with illegal in cache-db file.
238+
let cacheDbFilePath = P.cacheDbFile modulesDir
239+
versionText ver = "\"version\":\"" <> ver <> "\""
240+
241+
cacheContent <- readUTF8FileT cacheDbFilePath
242+
243+
let currentVer = T.pack (showVersion Paths.version)
244+
let newContent =
245+
T.replace (versionText currentVer) (versionText "0.0.0") cacheContent
246+
247+
writeUTF8FileT cacheDbFilePath newContent
248+
249+
compile [mPath] `shouldReturn` moduleNames ["Module"]
250+
225251
-- Cut off rebuild tests.
226252

227253
-- If a module is compiled with effective changes for downstream they should
@@ -433,13 +459,34 @@ spec = do
433459
)
434460
["A"]
435461

462+
-- Type synonym in foreign import.
463+
recompile2 it "type synonym changed in foreign import"
464+
( "module A where\ntype SynA = Int\n"
465+
, "module A where\ntype SynA = String\n"
466+
, "module B where\nimport A as A\nforeign import a :: A.SynA\n"
467+
)
468+
436469
-- Type synonym change.
437470
recompile2 it "type synonym changed"
438471
( "module A where\ntype SynA = Int\n"
439472
, "module A where\ntype SynA = String\n"
440473
, "module B where\nimport A as A\ntype SynB = Array A.SynA\n"
441474
)
442475

476+
-- Type synonym change in value.
477+
recompile2 it "type synonym changed in value"
478+
( "module A where\ntype SynA = Int\n"
479+
, "module A where\ntype SynA = String\n"
480+
, "module B where\nimport A as A\nvalue = ([] :: Array A.SynA)\n"
481+
)
482+
483+
-- Type synonym change in pattern.
484+
recompile2 it "type synonym changed in pattern"
485+
( "module A where\ntype SynA = Int\n"
486+
, "module A where\ntype SynA = String\n"
487+
, "module B where\nimport A as A\nfn = \\(_ :: Array A.SynA) -> 0\n"
488+
)
489+
443490
-- Type synonym indirect change.
444491
recompile2 it "type synonym dependency changed"
445492
( "module A where\ntype SynA = Int\ntype SynA2 = SynA\n"
@@ -516,7 +563,6 @@ spec = do
516563
, "module B where\nimport A\nt = 1 :+: \"1\" "
517564
)
518565

519-
520566
-- Type operator change.
521567
recompile2 it "type op changed"
522568
( "module A where\ndata T a b = T a b\ninfixl 2 type T as :+:\n"

0 commit comments

Comments
 (0)