Skip to content

Commit

Permalink
Qualified constructors (#3337)
Browse files Browse the repository at this point in the history
- Closes #3328

This pr makes an important breaking change to the Juvix scoper.
Previously, when defining a type, the constructors were put into the
current scope. After this pr, they must be accessed through the type
module that is generated.

E.g.
```
type Unit := unit;

-- BEFORE
fun : Unit := unit;

-- AFTER
fun : Unit := Unit.unit;
-- or
open Unit using {unit} public;
fun : Unit := unit;
```

Additionally, this pr adds the option `--migration` to the `juvix
format` command. This option takes the identity of some migration we
want to apply. For the purposes of migrating the existing codebase, I've
added the `export-constructors` migration. When this option is given,
the juvix formatter automatically adds an `open` statement after each
type definition.
E.g. Run `juvix format --migration export-constructors Example.juvix`.
```
module Example;

type T := a | b;
```
Result:
```
module Example;

type T :=
  | a
  | b;

open T using {a; b} public;
```
Using this method to migrate is not guaranteed to succeed, and manual
adjustments might need to be made.
1. Forward references to constructors need to be qualified. E.g.
```
syntax operator :: cons;  -- BEFORE
syntax operator List.:: cons;  -- AFTER

type List (A : Type) :=
  | nil
  | :: A (List A);
```
3. Another thing to consider is that the migration should not be run
twice on the same file, because then there will be repeated redundant
`open` statements:
```
open List using {nil; ::} public;

open List using {nil; ::} public;
```
I created a fish script for this:
```
#!/usr/bin/env fish

cd ./tests
for dir in (fd -a -t f "Package.juvix" | xargs -I{} dirname "{}")
    set witness "$dir/.migrated"
    if test -e $witness
        echo "Already processed: $dir"
    else
        echo "Processing $dir"
        if juvix format  $dir --migration export-constructors --in-place
            touch $witness
        end
    end
end
```
  • Loading branch information
janmasrovira authored Feb 28, 2025
1 parent 410a052 commit 3bb9d8a
Show file tree
Hide file tree
Showing 463 changed files with 3,656 additions and 2,355 deletions.
34 changes: 23 additions & 11 deletions app/Commands/Format.hs
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,22 @@ targetFromOptions opts = do
formatProject ::
forall r.
(Members (ScopeEff ': Output FormattedFileInfo ': AppEffects) r) =>
Migration ->
Sem r FormatResult
formatProject = silenceProgressLog . runPipelineOptions . runPipelineSetup $ do
res :: [ProcessedNode ScoperResult] <- processProjectUpToScoping
pkgId :: PackageId <- (^. entryPointPackageId) <$> ask
res' :: [(ImportNode, SourceCode)] <- runReader pkgId $ forM res $ \node -> do
src <- formatModuleInfo node
return (node ^. processedNode, src)
formatRes <- formatProjectSourceCode res'
formatPkgRes <- formatPackageDotJuvix
return (formatRes <> formatPkgRes)
formatProject migr = silenceProgressLog
. runPipelineOptions
. local (set pipelineMigration migr)
. runPipelineSetup
$ do
mpkg <- asks (^. entryPointMainPackageId)
res :: [ProcessedNode ScoperResult] <- runReader mpkg processProjectUpToScoping
pkgId :: PackageId <- (^. entryPointPackageId) <$> ask
res' :: [(ImportNode, SourceCode)] <- runReader pkgId $ forM res $ \node -> do
src <- formatModuleInfo node
return (node ^. processedNode, src)
formatRes <- formatProjectSourceCode res'
formatPkgRes <- formatPackageDotJuvix
return (formatRes <> formatPkgRes)

formatPackageDotJuvix :: forall r. (Members (Output FormattedFileInfo ': ScopeEff ': AppEffects) r) => Sem r FormatResult
formatPackageDotJuvix = do
Expand All @@ -72,7 +78,7 @@ runCommand opts = do
runOutputSem (renderFormattedOutput target opts) . runScopeFileApp $ do
res <- case target of
TargetFile p -> format p
TargetProject -> formatProject
TargetProject -> formatProject (opts ^. formatMigration)
TargetStdin -> do
entry <- getEntryPointStdin
runReader entry formatStdin
Expand All @@ -98,7 +104,13 @@ renderModeFromOptions target opts formattedInfo
| formattedInfo ^. formattedFileInfoContentsModified = res
| otherwise = NoEdit Silent

renderFormattedOutput :: forall r. (Members '[EmbedIO, App, Files] r) => FormatTarget -> FormatOptions -> FormattedFileInfo -> Sem r ()
renderFormattedOutput ::
forall r.
(Members '[EmbedIO, App, Files] r) =>
FormatTarget ->
FormatOptions ->
FormattedFileInfo ->
Sem r ()
renderFormattedOutput target opts fInfo = do
let renderMode = renderModeFromOptions target opts fInfo
outputResult renderMode
Expand Down
16 changes: 16 additions & 0 deletions app/Commands/Format/Options.hs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,27 @@ import CommonOptions
data FormatOptions = FormatOptions
{ _formatInput :: Maybe (AppPath FileOrDir),
_formatCheck :: Bool,
_formatMigration :: Migration,
_formatInPlace :: Bool
}
deriving stock (Data)

makeLenses ''FormatOptions

parseMigration :: Parser Migration
parseMigration = do
m <-
optional $
option
(enumReader (Proxy @Migrate))
( long "migration"
<> metavar "MIGRATION"
<> completer (enumCompleter (Proxy @Migrate))
<> help "Migrates files in a project (doesn't work in single file mode or stdin)"
)

pure (Migration m)

parseInputFileOrDir :: Parser (AppPath FileOrDir)
parseInputFileOrDir = do
_pathPath <-
Expand All @@ -26,6 +41,7 @@ parseInputFileOrDir = do
parseFormat :: Parser FormatOptions
parseFormat = do
_formatInput <- optional parseInputFileOrDir
_formatMigration <- parseMigration
_formatCheck <-
switch
( long "check"
Expand Down
3 changes: 2 additions & 1 deletion app/Commands/Markdown.hs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ goProject ::
(Members (Reader MarkdownOptions ': AppEffects) r) =>
Sem r ()
goProject = runPipelineOptions . runPipelineSetup $ do
res :: [ProcessedNode ScoperResult] <- processProjectUpToScoping
mpkg <- asks (^. entryPointMainPackageId)
res :: [ProcessedNode ScoperResult] <- runReader mpkg processProjectUpToScoping
forM_ res (goScoperResult . (^. processedNodeData))

goScoperResult :: (Members (Reader MarkdownOptions ': AppEffects) r) => Scoper.ScoperResult -> Sem r ()
Expand Down
2 changes: 2 additions & 0 deletions examples/demo/Demo.juvix
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ type Tree (A : Type) :=
right : Tree A;
};

open Tree using {leaf; node} public;

mirror {A} : (tree : Tree A) -> Tree A
| tree@(leaf _) := tree
| (node x l r) := node x (mirror r) (mirror l);
Expand Down
2 changes: 2 additions & 0 deletions examples/milestone/Bank/Bank.juvix
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ type Token :=
amount : Nat;
};

open Token using {mkToken} public;

--- This module defines the type for balances and its associated operations.
module Balances;
Balances : Type := List (Pair Field Nat);
Expand Down
4 changes: 4 additions & 0 deletions examples/milestone/Hanoi/Hanoi.juvix
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ type Peg :=
| middle
| right;

open Peg using {left; middle; right} public;

showPeg : Peg -> String
| left := "left"
| middle := "middle"
Expand All @@ -46,6 +48,8 @@ type Move :=
to : Peg;
};

open Move using {mkMove} public;

showMove (move : Move) : String :=
showPeg (Move.from move) ++str " -> " ++str showPeg (Move.to move);

Expand Down
2 changes: 2 additions & 0 deletions examples/milestone/TicTacToe/Logic/Board.juvix
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ type Board :=
squares : List (List Square);
};

open Board using {mkBoard} public;

--- Returns the list of numbers corresponding to the empty ;Square;s
possibleMoves : (list : List Square) -> List Nat
| nil := nil
Expand Down
4 changes: 4 additions & 0 deletions examples/milestone/TicTacToe/Logic/GameState.juvix
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,17 @@ type Error :=
| --- a fatal occurred
terminate : String → Error;

open Error using {noError; continue; terminate} public;

type GameState :=
mkGameState@{
board : Board;
player : Symbol;
error : Error;
};

open GameState using {mkGameState} public;

--- Textual representation of a ;GameState;
showGameState (state : GameState) : String := showBoard (GameState.board state);

Expand Down
4 changes: 3 additions & 1 deletion examples/milestone/TicTacToe/Logic/Square.juvix
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ type Square :=
player : Symbol;
};

open Square using {empty; occupied} public;

instance
eqSquareI : Eq Square :=
mkEq@{
Eq.mk@{
isEqual (square1 square2 : Square) : Bool :=
case square1, square2 of
| empty m, empty n := m == n
Expand Down
4 changes: 3 additions & 1 deletion examples/milestone/TicTacToe/Logic/Symbol.juvix
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ type Symbol :=
| --- The cross token
X;

open Symbol using {O; X} public;

instance
eqSymbolI : Eq Symbol :=
mkEq@{
Eq.mk@{
isEqual (sym1 sym2 : Symbol) : Bool :=
case sym1, sym2 of
| O, O := true
Expand Down
2 changes: 2 additions & 0 deletions include/package-base/Juvix/Builtin/V1/Bool.juvix
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ builtin bool
type Bool :=
| false
| true;

open Bool using {false; true} public;
4 changes: 3 additions & 1 deletion include/package-base/Juvix/Builtin/V1/List.juvix
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ module Juvix.Builtin.V1.List;

import Juvix.Builtin.V1.Fixity open;

syntax operator :: cons;
syntax operator List.:: cons;
--- Inductive list.
builtin list
type List (A : Type) :=
| --- The empty list
nil
| --- An element followed by a list
:: A (List A);

open List using {nil; ::} public;
2 changes: 2 additions & 0 deletions include/package-base/Juvix/Builtin/V1/Maybe.juvix
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ builtin maybe
type Maybe A :=
| nothing
| just A;

open Maybe using {nothing; just} public;
2 changes: 2 additions & 0 deletions include/package-base/Juvix/Builtin/V1/Nat/Base.juvix
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ type Nat :=
| zero
| suc Nat;

open Nat using {zero; suc} public;

syntax operator + additive;

--- Addition for ;Nat;s.
Expand Down
2 changes: 2 additions & 0 deletions include/package/PackageDescription/Basic.juvix
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ type Package :=
--- Use this in situations where you don't want the package configuration file
--- to use the standard library.
basicPackage;

open Package using {basicPackage} public;
10 changes: 8 additions & 2 deletions include/package/PackageDescription/V1.juvix
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ type Package :=
buildDir : Maybe String
};

open Package using {mkPackage} public;

--- A ;SemVer; defines a version that conforms to the Semantic Versioning specification.
type SemVer :=
mkSemVer {
Expand All @@ -27,11 +29,13 @@ type SemVer :=
meta : Maybe String
};

open SemVer using {mkSemVer} public;

--- A ;Dependency; defines a Juvix package dependency.
type Dependency :=
| --- A filesystem path to another Juvix Package.
path {path : String}
| --- A ;git; repository containing a Juvix package at its root
| --- A git repository containing a Juvix package at its root
git {
-- A name for this dependency
name : String;
Expand All @@ -40,9 +44,11 @@ type Dependency :=
-- The git ref to checkout
ref : String
}
| --- The ;defaultStdlib; that is bundled with the Juvix compiler.
| --- The default Stdlib that is bundled with the Juvix compiler.
defaultStdlib;

open Dependency using {path; git; defaultStdlib} public;

--- The default version used in `defaultPackage`.
defaultVersion : SemVer := mkVersion 0 0 0;

Expand Down
10 changes: 8 additions & 2 deletions include/package/PackageDescription/V2.juvix
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ type Package :=
buildDir : Maybe String
};

open Package using {mkPackage} public;

--- A ;SemVer; defines a version that conforms to the Semantic Versioning specification.
type SemVer :=
mkSemVer@{
Expand All @@ -27,11 +29,13 @@ type SemVer :=
meta : Maybe String
};

open SemVer using {mkSemVer} public;

--- A ;Dependency; defines a Juvix package dependency.
type Dependency :=
| --- A filesystem path to another Juvix Package.
path@{pathStr : String}
| --- A ;git; repository containing a Juvix package at its root
| --- A git repository containing a Juvix package at its root
git@{
-- A name for this dependency
name : String;
Expand All @@ -40,9 +44,11 @@ type Dependency :=
-- The git ref to checkout
ref : String
}
| --- The ;defaultStdlib; that is bundled with the Juvix compiler.
| --- The default Stdlib that is bundled with the Juvix compiler.
defaultStdlib;

open Dependency using {path; git; defaultStdlib} public;

--- Construct a ;SemVer; with useful default arguments.
mkVersion
(major minor patch : Nat)
Expand Down
2 changes: 1 addition & 1 deletion juvix-stdlib
Submodule juvix-stdlib updated 49 files
+7 −1 Package.juvix
+9 −8 Stdlib/Cairo/Ec.juvix
+3 −3 Stdlib/Cairo/Pedersen.juvix
+4 −4 Stdlib/Cairo/Poseidon.juvix
+13 −11 Stdlib/Data/BinaryTree.juvix
+1 −1 Stdlib/Data/Bool.juvix
+2 −2 Stdlib/Data/Byte.juvix
+4 −4 Stdlib/Data/Field.juvix
+3 −3 Stdlib/Data/Field/Base.juvix
+7 −7 Stdlib/Data/Int.juvix
+83 −80 Stdlib/Data/Int/Base.juvix
+3 −1 Stdlib/Data/Int/Ord.juvix
+8 −8 Stdlib/Data/List.juvix
+43 −41 Stdlib/Data/Map.juvix
+5 −5 Stdlib/Data/Maybe.juvix
+4 −4 Stdlib/Data/Nat.juvix
+3 −1 Stdlib/Data/Ordering.juvix
+2 −2 Stdlib/Data/Pair.juvix
+4 −2 Stdlib/Data/Pair/Base.juvix
+16 −16 Stdlib/Data/Queue/Base.juvix
+10 −10 Stdlib/Data/Range.juvix
+5 −5 Stdlib/Data/Result.juvix
+38 −29 Stdlib/Data/Result/Base.juvix
+10 −6 Stdlib/Data/Set.juvix
+2 −2 Stdlib/Data/String.juvix
+11 −10 Stdlib/Data/UnbalancedSet.juvix
+2 −2 Stdlib/Data/Unit.juvix
+2 −0 Stdlib/Data/Unit/Base.juvix
+2 −2 Stdlib/Trait/Applicative.juvix
+2 −2 Stdlib/Trait/DivMod.juvix
+1 −1 Stdlib/Trait/Eq.juvix
+3 −3 Stdlib/Trait/Foldable/Monomorphic.juvix
+2 −2 Stdlib/Trait/Foldable/Polymorphic.juvix
+3 −3 Stdlib/Trait/Functor/Monomorphic.juvix
+2 −2 Stdlib/Trait/Functor/Polymorphic.juvix
+1 −1 Stdlib/Trait/Integral.juvix
+2 −2 Stdlib/Trait/Monad.juvix
+1 −1 Stdlib/Trait/Numeric.juvix
+1 −1 Stdlib/Trait/Ord.juvix
+2 −2 Stdlib/Trait/Ord/Eq.juvix
+3 −3 Stdlib/Trait/Partial.juvix
+1 −1 Stdlib/Trait/Show.juvix
+3 −3 Stdlib/Trait/Traversable/Monomorphic.juvix
+2 −2 Stdlib/Trait/Traversable/Polymorphic.juvix
+5 −2 test/Package.juvix
+12 −12 test/Test.juvix
+5 −5 test/Test/Arb.juvix
+11 −11 test/Test/Set.juvix
+3 −3 test/juvix.lock.yaml
7 changes: 4 additions & 3 deletions src/Juvix/Compiler/Concrete/Language/Base.hs
Original file line number Diff line number Diff line change
Expand Up @@ -295,12 +295,13 @@ deriving stock instance Ord (Statement 'Scoped)

data ReservedInductiveDef = ReservedInductiveDef
{ _reservedInductiveDef :: InductiveDef 'Parsed,
_reservedInductiveConstructors :: NonEmpty S.Symbol,
_reservedInductiveDefModule :: Module 'Parsed 'ModuleLocal
}
deriving stock (Show, Eq, Ord)

data ProjectionDef s = ProjectionDef
{ _projectionConstructor :: S.Symbol,
{ _projectionConstructor :: SymbolType s,
_projectionField :: SymbolType s,
_projectionType :: ExpressionType s,
_projectionKind :: ProjectionKind,
Expand Down Expand Up @@ -3340,8 +3341,8 @@ instance HasLoc (OpenModule s short) where
<>? fmap getLoc _openModuleUsingHiding
<>? getLocPublicAnn _openModulePublic

instance HasLoc (ProjectionDef s) where
getLoc = getLoc . (^. projectionConstructor)
instance (SingI s) => HasLoc (ProjectionDef s) where
getLoc = getLocSymbolType . (^. projectionConstructor)

getLocFunctionSymbolType :: forall s. (SingI s) => FunctionSymbolType s -> Interval
getLocFunctionSymbolType = case sing :: SStage s of
Expand Down
2 changes: 1 addition & 1 deletion src/Juvix/Compiler/Concrete/Print/Base.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1635,7 +1635,7 @@ instance (SingI s) => PrettyPrint (ProjectionDef s) where
<+> noLoc ":= projection"
<+> noLoc (pretty _projectionFieldIx)
<+> noLoc "for"
<+> ppCode _projectionConstructor
<+> ppSymbolType _projectionConstructor

ppReservedInductiveDefType :: forall s. (SingI s) => PrettyPrinting (ReservedInductiveDefType s)
ppReservedInductiveDefType x = case sing :: SStage s of
Expand Down
5 changes: 3 additions & 2 deletions src/Juvix/Compiler/Concrete/Translation/FromParsed.hs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import Juvix.Prelude
fromParsed ::
( Members
'[ HighlightBuilder,
Reader Migration,
Reader PackageId,
Reader MainPackageId,
Reader ModuleTable,
Reader Parsed.ParserResult,
Error JuvixError,
Expand All @@ -27,7 +29,6 @@ fromParsed ::
) =>
Sem r ScoperResult
fromParsed = do
pkg <- ask
tab <- ask
r <- ask
scopeCheck pkg (getScopedModuleTable tab) r
scopeCheck (getScopedModuleTable tab) r
Loading

0 comments on commit 3bb9d8a

Please sign in to comment.