Skip to content

Commit def6950

Browse files
committed
validate that imports only contain definitions + imports
1 parent cdb0938 commit def6950

File tree

3 files changed

+42
-12
lines changed

3 files changed

+42
-12
lines changed

src/swarm-lang/Swarm/Language/Load.hs

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,18 @@ import Network.HTTP.Simple (getResponseBody, httpBS, parseRequest)
3232
import Swarm.Failure (Asset (..), AssetData (..), Entry (..), LoadingFailure (..), SystemFailure (..))
3333
import Swarm.Language.Parser (readTerm')
3434
import Swarm.Language.Parser.Core (defaultParserConfig, importLoc)
35-
import Swarm.Language.Syntax (ImportPhaseFor, Phase (..), SwarmType, Syntax)
35+
import Swarm.Language.Syntax
3636
import Swarm.Language.Syntax.Import hiding (ImportPhase (..))
3737
import Swarm.Language.Syntax.Import qualified as Import
38-
import Swarm.Language.Syntax.Util (Erasable(..), traverseSyntax)
38+
import Swarm.Language.Syntax.Util (Erasable(..))
3939
import Swarm.Language.Types (TCtx, UCtx)
40+
import Swarm.Pretty (prettyText)
4041
import Swarm.Util (readFileMayT, showT)
4142
import Swarm.Util.Graph (findCycle)
4243
import Witch (into)
4344

45+
type ResLoc = ImportLoc Import.Resolved
46+
4447
-- | The context for a module, containing names and types of things
4548
-- defined in the module (once typechecking has run).
4649
type family ModuleCtx (phase :: Phase) where
@@ -120,8 +123,6 @@ resolve' = traverseSyntax pure (throwError . DisallowedImport)
120123
eraseSourceMap :: SourceMap Elaborated -> SourceMap Resolved
121124
eraseSourceMap = M.map erase
122125

123-
type ResLoc = ImportLoc Import.Resolved
124-
125126
-- | Convert a 'SourceMap' into a suitable form for 'findCycle'.
126127
toImportGraph :: SourceMap Resolved -> [(ResLoc, ResLoc, [ResLoc])]
127128
toImportGraph = map processNode . M.assocs
@@ -172,8 +173,8 @@ resolveImport parent loc = do
172173
add $ S.singleton canonicalLoc
173174

174175
srcMap <- get @(SourceMap Resolved)
175-
case M.lookup canonicalLoc srcMap of
176-
Just _ -> pure () -- Already loaded - do nothing
176+
resMod <- case M.lookup canonicalLoc srcMap of
177+
Just m -> pure m -- Already loaded - do nothing
177178
Nothing -> do
178179
-- Record this import loc in the source map using a temporary, empty module,
179180
-- to prevent it from attempting to load itself recursively
@@ -188,14 +189,35 @@ resolveImport parent loc = do
188189
let (imps, mt') = sequence mres
189190

190191
-- Finally, record the loaded module in the SourceMap.
191-
modify @(SourceMap Resolved) (M.insert canonicalLoc $ Module mt' () imps)
192+
let m = Module mt' () imps
193+
modify @(SourceMap Resolved) (M.insert canonicalLoc m)
194+
195+
pure m
192196

193-
-- XXX make sure imports contain ONLY defs + more imports?
194-
-- Otherwise we can't cache their values---we would have to
195-
-- re-execute them every time they are imported.
197+
-- Make sure imports are pure, i.e. contain ONLY defs + imports.
198+
validateImport canonicalLoc resMod
196199

197200
pure canonicalLoc
198201

202+
-- | Validate the source code of the import to ensure that it contains
203+
-- *only* imports and definitions. This is so we do not have to worry
204+
-- about side-effects happening every time a module is imported. In
205+
-- other words, imports must be pure so we can get away with only
206+
-- evaluating them once.
207+
validateImport :: forall sig m. (Has (Throw SystemFailure) sig m) => ResLoc -> Module Resolved -> m ()
208+
validateImport loc = maybe (pure ()) validate . moduleTerm
209+
where
210+
validate :: Syntax Resolved -> m ()
211+
validate = validateTerm . _sTerm
212+
213+
validateTerm :: Term Resolved -> m ()
214+
validateTerm = \case
215+
SLet LSDef _ _ _ _ _ _ t -> validate t
216+
SImportIn _ t -> validate t
217+
STydef _ _ _ t -> validate t
218+
TConst Noop -> pure ()
219+
t -> throwError $ ImpureImport loc (prettyText t)
220+
199221
-- | Try to read and parse a term from a specific import location,
200222
-- either over the network or on disk.
201223
readLoc ::

src/swarm-lang/Swarm/Language/Typecheck.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1127,7 +1127,7 @@ infer s@(CSyntax l t cs) = addLocToTypeErr l $ case t of
11271127
check s sTy
11281128

11291129
-- | Collect up the names and types of any top-level definitions into
1130-
-- a context.
1130+
-- a context. XXX this must collect tydefs as well!
11311131
collectDefs ::
11321132
(Has Unification sig m, Has (Reader UCtx) sig m) =>
11331133
Syntax Inferred ->
@@ -1136,6 +1136,7 @@ collectDefs (Syntax _ (SLet LSDef _ x _ _ _ body t) _ _) = do
11361136
ty' <- generalize (body ^. sType)
11371137
(Ctx.singleton (lvVar x) ty' <>) <$> collectDefs t
11381138
collectDefs (Syntax _ (SImportIn _ t) _ _) = collectDefs t
1139+
collectDefs (Syntax _ (STydef _ _ _ t) _ _) = collectDefs t
11391140
collectDefs _ = pure Ctx.empty
11401141

11411142
-- | Infer the type of a module, i.e. import, by (1) typechecking and

src/swarm-util/Swarm/Failure.hs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import Data.Text.Encoding.Error qualified as T
2828
import Data.Void
2929
import Data.Yaml (ParseException, prettyPrintParseException)
3030
import Prettyprinter (Pretty (pretty), nest, squotes, vcat, (<+>))
31-
import Swarm.Language.Syntax.Import (ImportLoc, ImportPhase (Raw))
31+
import Swarm.Language.Syntax.Import (ImportLoc, ImportPhase (..))
3232
import Swarm.Language.Syntax.Loc (SrcLoc)
3333
import Swarm.Pretty (BulletList (..), PrettyPrec (..), ppr, prettyShowLow, prettyString)
3434
import Swarm.Util (showLowT)
@@ -89,6 +89,7 @@ data SystemFailure
8989
| ImportCycle [FilePath]
9090
| EmptyTerm
9191
| DisallowedImport (ImportLoc Raw)
92+
| ImpureImport (ImportLoc Resolved) Text -- See Note [Pretty-printing typechecking errors]
9293
| CustomFailure Text
9394
deriving (Show)
9495

@@ -162,4 +163,10 @@ instance PrettyPrec SystemFailure where
162163
ppr $ BulletList "Imports form a cycle:" (map (into @Text) imps)
163164
EmptyTerm -> "Term was only whitespace"
164165
DisallowedImport _imp -> "Import is not allowed here"
166+
ImpureImport imp t ->
167+
nest 2 . vcat $
168+
[ "While processing import" <+> ppr imp <> ":"
169+
, "Imported modules must only contain imports + definitions, but found:"
170+
, squotes (pretty t)
171+
]
165172
CustomFailure m -> pretty m

0 commit comments

Comments
 (0)