diff --git a/.github/workflows/bundle-ucm.yaml b/.github/workflows/bundle-ucm.yaml index db53c80ac0..6c16a0924a 100644 --- a/.github/workflows/bundle-ucm.yaml +++ b/.github/workflows/bundle-ucm.yaml @@ -246,7 +246,7 @@ jobs: file: ucm.cmd content: | @echo off - SET UCM_WEB_UI="%~dp0ui" + SET UCM_WEB_UI=%~dp0ui "%~dp0unison\unison.exe" --runtime-path "%~dp0runtime\unison-runtime.exe" %* - name: package everything together run: | diff --git a/CONTRIBUTORS.markdown b/CONTRIBUTORS.markdown index 5649b15dd0..e35d40033b 100644 --- a/CONTRIBUTORS.markdown +++ b/CONTRIBUTORS.markdown @@ -87,3 +87,4 @@ The format for this list: name, GitHub handle * Dan Doel (@dolio) * Eric Torreborre (@etorreborre) * Eduard Nicodei (@neduard) +* Brian McKenna (@puffnfresh) diff --git a/CREDITS.md b/CREDITS.md index 321060f338..bd367b3aef 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -52,7 +52,6 @@ These are listed in alphabetical order. | [comonad-5.0.6](https://hackage.haskell.org/package/comonad-5.0.6) | [BSD3](https://hackage.haskell.org/package/comonad-5.0.6/src/LICENSE) | | [concurrent-supply-0.1.8](https://hackage.haskell.org/package/concurrent-supply-0.1.8) | [BSD3](https://hackage.haskell.org/package/concurrent-supply-0.1.8/src/LICENSE) | | [conduit-1.3.2](https://hackage.haskell.org/package/conduit-1.3.2) | [MIT](https://hackage.haskell.org/package/conduit-1.3.2/src/LICENSE) | -| [configurator-0.3.0.0](https://hackage.haskell.org/package/configurator-0.3.0.0) | [BSD3](https://hackage.haskell.org/package/configurator-0.3.0.0/src/LICENSE) | | [containers-0.6.2.1](https://hackage.haskell.org/package/containers-0.6.2.1) | [BSD3](https://hackage.haskell.org/package/containers-0.6.2.1/src/LICENSE) | | [contravariant-1.5.2](https://hackage.haskell.org/package/contravariant-1.5.2) | [BSD3](https://hackage.haskell.org/package/contravariant-1.5.2/src/LICENSE) | | [cryptohash-md5-0.11.100.1](https://hackage.haskell.org/package/cryptohash-md5-0.11.100.1) | [BSD3](https://hackage.haskell.org/package/cryptohash-md5-0.11.100.1/src/LICENSE) | diff --git a/contrib/cabal.project b/contrib/cabal.project index d23809d841..8f13162c7f 100644 --- a/contrib/cabal.project +++ b/contrib/cabal.project @@ -36,11 +36,6 @@ packages: unison-syntax yaks/easytest -source-repository-package - type: git - location: https://github.com/unisonweb/configurator.git - tag: e47e9e9fe1f576f8c835183b9def52d73c01327a - source-repository-package type: git location: https://github.com/unisonweb/haskeline.git diff --git a/dev-ui-install.sh b/dev-ui-install.sh index a9f3d5d64d..0ade79bf2a 100755 --- a/dev-ui-install.sh +++ b/dev-ui-install.sh @@ -1,3 +1,6 @@ +#!/usr/bin/env sh +set -eu + echo "This script downloads the latest Unison Local UI release" echo "and puts it in the correct spot next to the unison" echo "executable built by stack." @@ -7,4 +10,4 @@ stack build curl -L https://github.com/unisonweb/unison-local-ui/releases/download/latest/unisonLocal.zip --output unisonLocal.zip parent_dir="$(dirname -- $(stack exec which unison))" mkdir -p "$parent_dir/ui" -unzip -o unisonLocal.zip -d "$parent_dir/ui" +unzip -q -o unisonLocal.zip -d "$parent_dir/ui" diff --git a/nix/unison-project.nix b/nix/unison-project.nix index aa191a5a44..3ca79d706b 100644 --- a/nix/unison-project.nix +++ b/nix/unison-project.nix @@ -25,7 +25,6 @@ in } ]; branchMap = { - "https://github.com/unisonweb/configurator.git"."e47e9e9fe1f576f8c835183b9def52d73c01327a" = "unison"; "https://github.com/unisonweb/shellmet.git"."2fd348592c8f51bb4c0ca6ba4bc8e38668913746" = "topic/avoid-callCommand"; }; } diff --git a/parser-typechecker/package.yaml b/parser-typechecker/package.yaml index de5bbd70e3..5cc6ba5473 100644 --- a/parser-typechecker/package.yaml +++ b/parser-typechecker/package.yaml @@ -37,7 +37,6 @@ dependencies: - cereal - clock - concurrent-output - - configurator - containers >= 0.6.3 - cryptonite - data-default diff --git a/parser-typechecker/src/Unison/Hashing/V2/Convert.hs b/parser-typechecker/src/Unison/Hashing/V2/Convert.hs index 7585e6b8b9..972c55db2a 100644 --- a/parser-typechecker/src/Unison/Hashing/V2/Convert.hs +++ b/parser-typechecker/src/Unison/Hashing/V2/Convert.hs @@ -227,7 +227,7 @@ h2mReferent getCT = \case hashDataDecls :: (Var v) => Map v (Memory.DD.DataDeclaration v a) -> - ResolutionResult v a [(v, Memory.Reference.Id, Memory.DD.DataDeclaration v a)] + ResolutionResult a [(v, Memory.Reference.Id, Memory.DD.DataDeclaration v a)] hashDataDecls memDecls = do let hashingDecls = fmap m2hDecl memDecls hashingResult <- Hashing.hashDecls Name.unsafeParseVar hashingDecls @@ -239,7 +239,7 @@ hashDataDecls memDecls = do hashDecls :: (Var v) => Map v (Memory.DD.Decl v a) -> - ResolutionResult v a [(v, Memory.Reference.Id, Memory.DD.Decl v a)] + ResolutionResult a [(v, Memory.Reference.Id, Memory.DD.Decl v a)] hashDecls memDecls = do -- want to unwrap the decl before doing the rehashing, and then wrap it back up the same way let howToReassemble = diff --git a/parser-typechecker/src/Unison/Parsers.hs b/parser-typechecker/src/Unison/Parsers.hs index fc1500a12f..9b9024f970 100644 --- a/parser-typechecker/src/Unison/Parsers.hs +++ b/parser-typechecker/src/Unison/Parsers.hs @@ -78,7 +78,8 @@ unsafeParseFileBuiltinsOnly = Parser.ParsingEnv { uniqueNames = mempty, uniqueTypeGuid = \_ -> pure Nothing, - names = Builtin.names + names = Builtin.names, + maybeNamespace = Nothing } unsafeParseFile :: (Monad m) => String -> Parser.ParsingEnv m -> m (UnisonFile Symbol Ann) diff --git a/parser-typechecker/src/Unison/PatternMatchCoverage.hs b/parser-typechecker/src/Unison/PatternMatchCoverage.hs index 30973b8256..75cd0a7ce4 100644 --- a/parser-typechecker/src/Unison/PatternMatchCoverage.hs +++ b/parser-typechecker/src/Unison/PatternMatchCoverage.hs @@ -35,7 +35,6 @@ module Unison.PatternMatchCoverage ) where -import Data.List.NonEmpty (nonEmpty) import Data.Set qualified as Set import Debug.Trace import Unison.Debug @@ -63,16 +62,14 @@ checkMatch :: checkMatch scrutineeType cases = do ppe <- getPrettyPrintEnv v0 <- fresh - mgrdtree0 <- traverse (desugarMatch scrutineeType v0) (nonEmpty cases) - doDebug (P.hang (title "desugared:") (prettyGrdTreeMaybe (prettyPmGrd ppe) (\_ -> "") mgrdtree0)) (pure ()) + grdtree0 <- desugarMatch scrutineeType v0 cases + doDebug (P.hang (title "desugared:") (prettyGrdTree (prettyPmGrd ppe) (\_ -> "") grdtree0)) (pure ()) let initialUncovered = Set.singleton (NC.markDirty v0 $ NC.declVar v0 scrutineeType id NC.emptyNormalizedConstraints) - (uncovered, grdtree1) <- case mgrdtree0 of - Nothing -> pure (initialUncovered, Nothing) - Just grdtree0 -> fmap Just <$> uncoverAnnotate initialUncovered grdtree0 + (uncovered, grdtree1) <- uncoverAnnotate initialUncovered grdtree0 doDebug ( P.sep "\n" - [ P.hang (title "annotated:") (prettyGrdTreeMaybe (NC.prettyDnf ppe) (NC.prettyDnf ppe . fst) grdtree1), + [ P.hang (title "annotated:") (prettyGrdTree (NC.prettyDnf ppe) (NC.prettyDnf ppe . fst) grdtree1), P.hang (title "uncovered:") (NC.prettyDnf ppe uncovered) ] ) @@ -80,14 +77,9 @@ checkMatch scrutineeType cases = do uncoveredExpanded <- concat . fmap Set.toList <$> traverse (expandSolution v0) (Set.toList uncovered) doDebug (P.hang (title "uncovered expanded:") (NC.prettyDnf ppe (Set.fromList uncoveredExpanded))) (pure ()) let sols = map (generateInhabitants v0) uncoveredExpanded - let (_accessible, inaccessible, redundant) = case grdtree1 of - Nothing -> ([], [], []) - Just x -> classify x + let (_accessible, inaccessible, redundant) = classify grdtree1 pure (redundant, inaccessible, sols) where - prettyGrdTreeMaybe prettyNode prettyLeaf = \case - Nothing -> "" - Just x -> prettyGrdTree prettyNode prettyLeaf x title = P.bold doDebug out = case shouldDebug PatternCoverage of True -> trace (P.toAnsiUnbroken out) diff --git a/parser-typechecker/src/Unison/PatternMatchCoverage/Desugar.hs b/parser-typechecker/src/Unison/PatternMatchCoverage/Desugar.hs index 8587d44d6c..b813145986 100644 --- a/parser-typechecker/src/Unison/PatternMatchCoverage/Desugar.hs +++ b/parser-typechecker/src/Unison/PatternMatchCoverage/Desugar.hs @@ -3,7 +3,6 @@ module Unison.PatternMatchCoverage.Desugar ) where -import Data.List.NonEmpty (NonEmpty (..)) import U.Core.ABT qualified as ABT import Unison.Pattern import Unison.Pattern qualified as Pattern @@ -25,7 +24,7 @@ desugarMatch :: -- | scrutinee variable v -> -- | match cases - NonEmpty (MatchCase loc (Term' vt v loc)) -> + [MatchCase loc (Term' vt v loc)] -> m (GrdTree (PmGrd vt v loc) loc) desugarMatch scrutineeType v0 cs0 = Fork <$> traverse desugarClause cs0 where diff --git a/parser-typechecker/src/Unison/PatternMatchCoverage/GrdTree.hs b/parser-typechecker/src/Unison/PatternMatchCoverage/GrdTree.hs index 15b28e3da3..bf84bd71c2 100644 --- a/parser-typechecker/src/Unison/PatternMatchCoverage/GrdTree.hs +++ b/parser-typechecker/src/Unison/PatternMatchCoverage/GrdTree.hs @@ -10,8 +10,6 @@ module Unison.PatternMatchCoverage.GrdTree ) where -import Data.List.NonEmpty (NonEmpty (..)) -import Data.List.NonEmpty qualified as NEL import Data.ListLike (ListLike) import Unison.PatternMatchCoverage.Fix import Unison.Prelude @@ -55,7 +53,7 @@ data GrdTreeF n l a | -- | A constraint of some kind (structural pattern match, boolan guard, etc) GrdF n a | -- | A list of alternative matches, tried in order - ForkF (NonEmpty a) + ForkF [a] deriving stock (Functor, Show) prettyGrdTree :: forall n l s. (ListLike s Char, IsString s) => (n -> Pretty s) -> (l -> Pretty s) -> GrdTree n l -> Pretty s @@ -64,7 +62,7 @@ prettyGrdTree prettyNode prettyLeaf = cata phi phi = \case LeafF l -> prettyLeaf l GrdF n rest -> sep " " [prettyNode n, "──", rest] - ForkF xs -> "──" <> group (sep "\n" (makeTree $ NEL.toList xs)) + ForkF xs -> "──" <> group (sep "\n" $ makeTree xs) makeTree :: [Pretty s] -> [Pretty s] makeTree = \case [] -> [] @@ -82,7 +80,7 @@ pattern Leaf x = Fix (LeafF x) pattern Grd :: n -> GrdTree n l -> GrdTree n l pattern Grd x rest = Fix (GrdF x rest) -pattern Fork :: NonEmpty (GrdTree n l) -> GrdTree n l +pattern Fork :: [GrdTree n l] -> GrdTree n l pattern Fork alts = Fix (ForkF alts) {-# COMPLETE Leaf, Grd, Fork #-} diff --git a/parser-typechecker/src/Unison/PatternMatchCoverage/Solve.hs b/parser-typechecker/src/Unison/PatternMatchCoverage/Solve.hs index b605750686..29e93d187f 100644 --- a/parser-typechecker/src/Unison/PatternMatchCoverage/Solve.hs +++ b/parser-typechecker/src/Unison/PatternMatchCoverage/Solve.hs @@ -16,7 +16,6 @@ import Data.Foldable import Data.Function import Data.Functor import Data.Functor.Compose -import Data.List.NonEmpty (NonEmpty (..)) import Data.Map qualified as Map import Data.Sequence qualified as Seq import Data.Set qualified as Set @@ -74,12 +73,11 @@ uncoverAnnotate z grdtree0 = cata phi grdtree0 z LeafF l -> \nc -> do nc' <- ensureInhabited' nc pure (Set.empty, Leaf (nc', l)) - ForkF (kinit :| ks) -> \nc0 -> do + ForkF ks -> \nc0 -> do -- depth-first fold in match-case order to acculate the -- constraints for a match failure at every case. - (nc1, t1) <- kinit nc0 - (ncfinal, ts) <- foldlM (\(nc, ts) a -> a nc >>= \(nc', t) -> pure (nc', t : ts)) (nc1, []) ks - pure (ncfinal, Fork (t1 :| reverse ts)) + (ncfinal, ts) <- foldlM (\(nc, ts) a -> a nc >>= \(nc', t) -> pure (nc', t : ts)) (nc0, []) ks + pure (ncfinal, Fork $ reverse ts) GrdF grd k -> \nc0 -> case grd of PmEffect var con convars -> handleGrd (PosEffect var (Effect con) convars) (NegEffect var (Effect con)) k nc0 PmEffectPure var resume -> handleGrd (PosEffect var NoEffect [resume]) (NegEffect var NoEffect) k nc0 diff --git a/parser-typechecker/src/Unison/PrettyPrintEnv/Names.hs b/parser-typechecker/src/Unison/PrettyPrintEnv/Names.hs index 5d8264202c..46d3fb220c 100644 --- a/parser-typechecker/src/Unison/PrettyPrintEnv/Names.hs +++ b/parser-typechecker/src/Unison/PrettyPrintEnv/Names.hs @@ -9,6 +9,7 @@ module Unison.PrettyPrintEnv.Names dontSuffixify, suffixifyByHash, suffixifyByName, + suffixifyByHashWithUnhashedTermsInScope, -- * Pretty-print env makePPE, @@ -23,11 +24,14 @@ import Unison.Name (Name) import Unison.Name qualified as Name import Unison.Names (Names) import Unison.Names qualified as Names +import Unison.Names.ResolvesTo (ResolvesTo (..)) import Unison.NamesWithHistory qualified as Names import Unison.Prelude import Unison.PrettyPrintEnv (PrettyPrintEnv (PrettyPrintEnv)) import Unison.Reference (TypeReference) import Unison.Referent (Referent) +import Unison.Util.Relation (Relation) +import Unison.Util.Relation qualified as Relation ------------------------------------------------------------------------------------------------------------------------ -- Namer @@ -84,6 +88,23 @@ suffixifyByHash names = suffixifyType = \name -> Name.suffixifyByHash name (Names.types names) } +suffixifyByHashWithUnhashedTermsInScope :: Set Name -> Names -> Suffixifier +suffixifyByHashWithUnhashedTermsInScope localTermNames namespaceNames = + Suffixifier + { suffixifyTerm = \name -> + Name.suffixifyByHash + name + terms, -- (Relation.mapRanMonotonic ResolvesToNamespace (Names.terms names)), + suffixifyType = \name -> Name.suffixifyByHash name (Names.types namespaceNames) + } + where + terms :: Relation Name (ResolvesTo Referent) + terms = + Names.terms namespaceNames + & Relation.subtractDom localTermNames + & Relation.mapRan ResolvesToNamespace + & Relation.union (Relation.fromList (map (\name -> (name, ResolvesToLocal name)) (Set.toList localTermNames))) + ------------------------------------------------------------------------------------------------------------------------ -- Pretty-print env diff --git a/parser-typechecker/src/Unison/PrintError.hs b/parser-typechecker/src/Unison/PrintError.hs index 4b46cdd03f..a4cf9e4b25 100644 --- a/parser-typechecker/src/Unison/PrintError.hs +++ b/parser-typechecker/src/Unison/PrintError.hs @@ -32,7 +32,7 @@ import Text.Megaparsec qualified as P import Unison.ABT qualified as ABT import Unison.Builtin.Decls (unitRef, pattern TupleType') import Unison.ConstructorReference (ConstructorReference, GConstructorReference (..)) -import Unison.HashQualified (HashQualified) +import Unison.HashQualified qualified as HQ import Unison.HashQualifiedPrime qualified as HQ' import Unison.Kind (Kind) import Unison.Kind qualified as Kind @@ -59,6 +59,7 @@ import Unison.Syntax.Name qualified as Name (toText) import Unison.Syntax.NamePrinter (prettyHashQualified0) import Unison.Syntax.Parser (Annotated, ann) import Unison.Syntax.Parser qualified as Parser +import Unison.Syntax.Precedence qualified as Precedence import Unison.Syntax.TermPrinter qualified as TermPrinter import Unison.Term qualified as Term import Unison.Type (Type) @@ -1132,7 +1133,7 @@ renderTerm env e = else fromString s renderPattern :: Env -> Pattern ann -> ColorText -renderPattern env e = Pr.renderUnbroken . Pr.syntaxToColor . fst $ TermPrinter.prettyPattern env TermPrinter.emptyAc 0 ([] :: [Symbol]) e +renderPattern env e = Pr.renderUnbroken . Pr.syntaxToColor . fst $ TermPrinter.prettyPattern env TermPrinter.emptyAc Precedence.Annotation ([] :: [Symbol]) e -- | renders a type with no special styling renderType' :: (IsString s, Var v) => Env -> Type v loc -> s @@ -1774,21 +1775,6 @@ renderParseErrors s = \case tokenAsErrorSite s tok ] in (msg, [rangeForToken tok]) - go (Parser.EmptyMatch tok) = - let msg = - Pr.indentN 2 . Pr.callout "😶" $ - Pr.lines - [ Pr.wrap - ( "I expected some patterns after a " - <> style ErrorSite "match" - <> "/" - <> style ErrorSite "with" - <> " or cases but I didn't find any." - ), - "", - tokenAsErrorSite s tok - ] - in (msg, [rangeForToken tok]) go (Parser.EmptyWatch tok) = let msg = Pr.lines @@ -1797,8 +1783,6 @@ renderParseErrors s = \case annotatedAsErrorSite s tok ] in (msg, maybeToList $ rangeForAnnotated tok) - go (Parser.UnknownAbilityConstructor tok _referents) = (unknownConstructor "ability" tok, [rangeForToken tok]) - go (Parser.UnknownDataConstructor tok _referents) = (unknownConstructor "data" tok, [rangeForToken tok]) go (Parser.UnknownId tok referents references) = let msg = Pr.lines @@ -1870,24 +1854,6 @@ renderParseErrors s = \case ] in (msg, [rangeForToken tok]) - unknownConstructor :: - String -> L.Token (HashQualified Name) -> Pretty ColorText - unknownConstructor ctorType tok = - Pr.lines - [ (Pr.wrap . mconcat) - [ "I don't know about any ", - fromString ctorType, - " constructor named ", - Pr.group - ( stylePretty ErrorSite (prettyHashQualified0 (L.payload tok)) - <> "." - ), - "Maybe make sure it's correctly spelled and that you've imported it:" - ], - "", - tokenAsErrorSite s tok - ] - annotatedAsErrorSite :: (Annotated a) => String -> a -> Pretty ColorText annotatedAsErrorSite = annotatedAsStyle ErrorSite @@ -1968,11 +1934,11 @@ intLiteralSyntaxTip term expectedType = case (term, expectedType) of -- | Pretty prints resolution failure annotations, including a table of disambiguation -- suggestions. prettyResolutionFailures :: - forall v a. - (Annotated a, Var v, Ord a) => + forall a. + (Annotated a, Ord a) => -- | src String -> - [Names.ResolutionFailure v a] -> + [Names.ResolutionFailure a] -> Pretty ColorText prettyResolutionFailures s allFailures = Pr.callout "❓" $ @@ -1987,39 +1953,39 @@ prettyResolutionFailures s allFailures = where -- Collapses identical failures which may have multiple annotations into a single failure. -- uniqueFailures - ambiguitiesToTable :: [Names.ResolutionFailure v a] -> Pretty ColorText + ambiguitiesToTable :: [Names.ResolutionFailure a] -> Pretty ColorText ambiguitiesToTable failures = - let pairs :: ([(v, Maybe (NESet String))]) + let pairs :: ([(HQ.HashQualified Name, Maybe (NESet String))]) pairs = nubOrd . fmap toAmbiguityPair $ failures spacerRow = ("", "") in Pr.column2Header "Symbol" "Suggestions" $ spacerRow : (intercalateMap [spacerRow] prettyRow pairs) - toAmbiguityPair :: Names.ResolutionFailure v annotation -> (v, Maybe (NESet String)) + toAmbiguityPair :: Names.ResolutionFailure annotation -> (HQ.HashQualified Name, Maybe (NESet String)) toAmbiguityPair = \case - (Names.TermResolutionFailure v _ (Names.Ambiguous names refs localNames)) -> do + (Names.TermResolutionFailure name _ (Names.Ambiguous names refs localNames)) -> do let ppe = ppeFromNames names - in ( v, + in ( name, Just $ NES.unsafeFromSet (Set.map (showTermRef ppe) refs <> Set.map (Text.unpack . Name.toText) localNames) ) - (Names.TypeResolutionFailure v _ (Names.Ambiguous names refs localNames)) -> do + (Names.TypeResolutionFailure name _ (Names.Ambiguous names refs localNames)) -> do let ppe = ppeFromNames names - in ( v, + in ( name, Just $ NES.unsafeFromSet (Set.map (showTypeRef ppe) refs <> Set.map (Text.unpack . Name.toText) localNames) ) - (Names.TermResolutionFailure v _ Names.NotFound) -> (v, Nothing) - (Names.TypeResolutionFailure v _ Names.NotFound) -> (v, Nothing) + (Names.TermResolutionFailure name _ Names.NotFound) -> (name, Nothing) + (Names.TypeResolutionFailure name _ Names.NotFound) -> (name, Nothing) ppeFromNames :: Names.Names -> PPE.PrettyPrintEnv ppeFromNames names = PPE.makePPE (PPE.hqNamer PPE.todoHashLength names) PPE.dontSuffixify - prettyRow :: (v, Maybe (NESet String)) -> [(Pretty ColorText, Pretty ColorText)] - prettyRow (v, mSet) = case mSet of - Nothing -> [(prettyVar v, Pr.hiBlack "No matches")] - Just suggestions -> zip ([prettyVar v] ++ repeat "") (Pr.string <$> toList suggestions) + prettyRow :: (HQ.HashQualified Name, Maybe (NESet String)) -> [(Pretty ColorText, Pretty ColorText)] + prettyRow (name, mSet) = case mSet of + Nothing -> [(prettyHashQualified0 name, Pr.hiBlack "No matches")] + Just suggestions -> zip ([prettyHashQualified0 name] ++ repeat "") (Pr.string <$> toList suggestions) useExamples :: Pretty ColorText useExamples = diff --git a/parser-typechecker/src/Unison/Result.hs b/parser-typechecker/src/Unison/Result.hs index 63df0a99e0..1c542c524f 100644 --- a/parser-typechecker/src/Unison/Result.hs +++ b/parser-typechecker/src/Unison/Result.hs @@ -18,7 +18,7 @@ type ResultT notes f = MaybeT (WriterT notes f) data Note v loc = Parsing (Parser.Err v) - | NameResolutionFailures [Names.ResolutionFailure v loc] + | NameResolutionFailures [Names.ResolutionFailure loc] | UnknownSymbol v loc | TypeError (Context.ErrorNote v loc) | TypeInfo (Context.InfoNote v loc) diff --git a/parser-typechecker/src/Unison/Runtime/IOSource.hs b/parser-typechecker/src/Unison/Runtime/IOSource.hs index 4848851f89..a589c9ae06 100644 --- a/parser-typechecker/src/Unison/Runtime/IOSource.hs +++ b/parser-typechecker/src/Unison/Runtime/IOSource.hs @@ -41,7 +41,8 @@ parsingEnv = Parser.ParsingEnv { uniqueNames = mempty, uniqueTypeGuid = \_ -> pure Nothing, - names = Builtin.names + names = Builtin.names, + maybeNamespace = Nothing } typecheckingEnv :: Typechecker.Env Symbol Ann @@ -543,8 +544,8 @@ d1 Doc.++ d2 = use Doc2 match (d1,d2) with (Join ds, Join ds2) -> Join (ds List.++ ds2) - (Join ds, _) -> Join (List.snoc ds d2) - (_, Join ds) -> Join (List.cons d1 ds) + (Join ds, _) -> Join (ds List.:+ d2) + (_, Join ds) -> Join (d1 List.+: ds) _ -> Join [d1,d2] unique[q1905679b27a97a4098bc965574da880c1074183a2c55ff1d481619c7fb8a1e1] type diff --git a/parser-typechecker/src/Unison/Syntax/FileParser.hs b/parser-typechecker/src/Unison/Syntax/FileParser.hs index f2e0da2592..ce3d01382c 100644 --- a/parser-typechecker/src/Unison/Syntax/FileParser.hs +++ b/parser-typechecker/src/Unison/Syntax/FileParser.hs @@ -44,7 +44,7 @@ import Unison.WatchKind (WatchKind) import Unison.WatchKind qualified as UF import Prelude hiding (readFile) -resolutionFailures :: (Ord v) => [Names.ResolutionFailure v Ann] -> P v m x +resolutionFailures :: (Ord v) => [Names.ResolutionFailure Ann] -> P v m x resolutionFailures es = P.customFailure (ResolutionFailures es) file :: forall m v. (Monad m, Var v) => P v m (UnisonFile v Ann) @@ -52,10 +52,11 @@ file = do _ <- openBlock -- Parse an optional directive like "namespace foo.bar" - maybeNamespace :: Maybe v <- + maybeNamespace :: Maybe Name.Name <- optional (reserved "namespace") >>= \case Nothing -> pure Nothing - Just _ -> Just . Name.toVar . L.payload <$> (importWordyId <|> importSymbolyId) + Just _ -> Just . L.payload <$> (importWordyId <|> importSymbolyId) + let maybeNamespaceVar = Name.toVar <$> maybeNamespace -- The file may optionally contain top-level imports, -- which are parsed and applied to the type decls and term stanzas @@ -65,7 +66,7 @@ file = do env <- let applyNamespaceToDecls :: forall decl. Iso' decl (DataDeclaration v Ann) -> Map v decl -> Map v decl applyNamespaceToDecls dataDeclL = - case maybeNamespace of + case maybeNamespaceVar of Nothing -> id Just namespace -> Map.fromList . map f . Map.toList where @@ -90,7 +91,7 @@ file = do (typ, fields) <- parsedAccessors -- The parsed accessor has an un-namespaced type, so apply the namespace directive (if necessary) before -- looking up in the environment computed by `environmentFor`. - let typ1 = maybe id Var.namespaced2 maybeNamespace (L.payload typ) + let typ1 = maybe id Var.namespaced2 maybeNamespaceVar (L.payload typ) Just (r, _) <- [Map.lookup typ1 (UF.datas env)] -- Generate the record accessors with *un-namespaced* names (passing `typ` rather than `typ1`) below, because we -- need to know these names in order to perform rewriting. As an example, @@ -107,26 +108,19 @@ file = do let accessors :: [(v, Ann, Term v Ann)] accessors = unNamespacedAccessors - & case maybeNamespace of + & case maybeNamespaceVar of Nothing -> id Just namespace -> over (mapped . _1) (Var.namespaced2 namespace) - let importNames = [(Name.unsafeParseVar v, Name.unsafeParseVar v2) | (v, v2) <- imports] - let locals = Names.importing importNames (UF.names env) -- At this stage of the file parser, we've parsed all the type and ability - -- declarations. The `push locals` here has the effect - -- of making suffix-based name resolution prefer type and constructor names coming - -- from the local file. - -- - -- There's some more complicated logic below to have suffix-based name resolution - -- make use of _terms_ from the local file. - local (\e -> e {names = Names.push locals namesStart}) do + -- declarations. + local (\e -> e {names = Names.shadowing (UF.names env) namesStart, maybeNamespace}) do names <- asks names stanzas <- do unNamespacedStanzas0 <- sepBy semi stanza let unNamespacedStanzas = fmap (TermParser.substImports names imports) <$> unNamespacedStanzas0 pure $ unNamespacedStanzas - & case maybeNamespace of + & case maybeNamespaceVar of Nothing -> id Just namespace -> let unNamespacedTermNamespaceNames :: Set v @@ -155,27 +149,12 @@ file = do -- [foo.alice, bar.alice, zonk.bob] fqLocalTerms :: [v] fqLocalTerms = (stanzas >>= getVars) <> (view _1 <$> accessors) - -- suffixified local term bindings shadow any same-named thing from the outer codebase scope - -- example: `foo.bar` in local file scope will shadow `foo.bar` and `bar` in codebase scope - let (curNames, resolveLocals) = - ( Names.shadowTerms locals names, - resolveLocals - ) - where - -- Each unique suffix mapped to its fully qualified name - canonicalVars :: Map v v - canonicalVars = UFN.variableCanonicalizer fqLocalTerms - - -- All unique local term name suffixes - these we want to - -- avoid resolving to a term that's in the codebase - locals :: [Name.Name] - locals = (Name.unsafeParseVar <$> Map.keys canonicalVars) - - -- A function to replace unique local term suffixes with their - -- fully qualified name - replacements = [(v, Term.var () v2) | (v, v2) <- Map.toList canonicalVars, v /= v2] - resolveLocals = ABT.substsInheritAnnotation replacements - let bindNames = Term.bindSomeNames Name.unsafeParseVar (Set.fromList fqLocalTerms) curNames . resolveLocals + let bindNames = + Term.bindNames + Name.unsafeParseVar + Name.toVar + (Set.fromList fqLocalTerms) + (Names.shadowTerms (map Name.unsafeParseVar fqLocalTerms) names) terms <- case List.validate (traverseOf _3 bindNames) terms of Left es -> resolutionFailures (toList es) Right terms -> pure terms diff --git a/parser-typechecker/src/Unison/Syntax/Precedence.hs b/parser-typechecker/src/Unison/Syntax/Precedence.hs new file mode 100644 index 0000000000..2a74b1181f --- /dev/null +++ b/parser-typechecker/src/Unison/Syntax/Precedence.hs @@ -0,0 +1,71 @@ +module Unison.Syntax.Precedence where + +import Data.Map qualified as Map +import Unison.Prelude + +-- Precedence rules for infix operators. +-- Lower number means higher precedence (tighter binding). +-- Operators not in this list have no precedence and will simply be parsed +-- left-to-right. +infixRules :: Map Text Precedence +infixRules = + Map.fromList do + (ops, prec) <- zip infixLevels (map (InfixOp . Level) [0 ..]) + map (,prec) ops + +-- | Indicates this is the RHS of a top-level definition. +isTopLevelPrecedence :: Precedence -> Bool +isTopLevelPrecedence i = i == Basement + +increment :: Precedence -> Precedence +increment = \case + Basement -> Bottom + Bottom -> Annotation + Annotation -> Statement + Statement -> Control + Control -> InfixOp Lowest + InfixOp Lowest -> InfixOp (Level 0) + InfixOp (Level n) -> InfixOp (Level (n + 1)) + InfixOp Highest -> Application + Application -> Prefix + Prefix -> Top + Top -> Top + +data Precedence + = -- | The lowest precedence, used for top-level bindings + Basement + | -- | Used for terms that never need parentheses + Bottom + | -- | Type annotations + Annotation + | -- | A statement in a block + Statement + | -- | Control flow constructs like `if`, `match`, `case` + Control + | -- | Infix operators + InfixOp InfixPrecedence + | -- | Function application + Application + | -- | Prefix operators like `'`, `!` + Prefix + | -- | The highest precedence, used for let bindings and blocks + Top + deriving (Eq, Ord, Show) + +data InfixPrecedence = Lowest | Level Int | Highest + deriving (Eq, Ord, Show) + +infixLevels :: [[Text]] +infixLevels = + [ ["||", "|"], + ["&&", "&"], + ["==", "!==", "!=", "==="], + ["<", ">", ">=", "<="], + ["+", "-"], + ["*", "/", "%"], + ["^", "^^", "**"] + ] + +-- | Returns the precedence of an infix operator, if it has one. +operatorPrecedence :: Text -> Maybe Precedence +operatorPrecedence op = Map.lookup op infixRules diff --git a/parser-typechecker/src/Unison/Syntax/TermParser.hs b/parser-typechecker/src/Unison/Syntax/TermParser.hs index 85406c84bf..9141a723e5 100644 --- a/parser-typechecker/src/Unison/Syntax/TermParser.hs +++ b/parser-typechecker/src/Unison/Syntax/TermParser.hs @@ -40,6 +40,7 @@ import Unison.Name qualified as Name import Unison.NameSegment qualified as NameSegment import Unison.Names (Names) import Unison.Names qualified as Names +import Unison.Names.ResolutionResult (ResolutionError (..), ResolutionFailure (..)) import Unison.NamesWithHistory qualified as Names import Unison.Parser.Ann (Ann (Ann)) import Unison.Parser.Ann qualified as Ann @@ -48,12 +49,14 @@ import Unison.Pattern qualified as Pattern import Unison.Prelude import Unison.Reference (Reference) import Unison.Referent (Referent) +import Unison.Referent qualified as Referent import Unison.Syntax.Lexer.Unison qualified as L import Unison.Syntax.Name qualified as Name (toText, toVar, unsafeParseVar) import Unison.Syntax.NameSegment qualified as NameSegment import Unison.Syntax.Parser hiding (seq) import Unison.Syntax.Parser qualified as Parser (seq, uniqueName) import Unison.Syntax.Parser.Doc.Data qualified as Doc +import Unison.Syntax.Precedence (operatorPrecedence) import Unison.Syntax.TypeParser qualified as TypeParser import Unison.Term (IsTop, Term) import Unison.Term qualified as Term @@ -69,9 +72,9 @@ import Prelude hiding (and, or, seq) {- Precedence of language constructs is identical to Haskell, except that all operators (like +, <*>, or any sequence of non-alphanumeric characters) are -left-associative and equal precedence, and operators must have surrounding -whitespace (a + b, not a+b) to distinguish from identifiers that may contain -operator characters (like empty? or fold-left). +left-associative and equal precedence (with a few exceptions), and operators +must have surrounding whitespace (a + b, not a+b) to distinguish from +identifiers that may contain operator characters (like empty? or fold-left). Sections / partial application of infix operators is not implemented. -} @@ -120,7 +123,8 @@ typeLink' :: (Monad m, Var v) => P v m (L.Token Reference) typeLink' = findUniqueType =<< hqPrefixId findUniqueType :: (Monad m, Var v) => L.Token (HQ.HashQualified Name) -> P v m (L.Token Reference) -findUniqueType id = do +findUniqueType id0 = do + id <- applyNamespaceToToken id0 ns <- asks names case Names.lookupHQType Names.IncludeSuffixes (L.payload id) ns of s @@ -129,7 +133,7 @@ findUniqueType id = do termLink' :: (Monad m, Var v) => P v m (L.Token Referent) termLink' = do - id <- hqPrefixId + id <- applyNamespaceToToken =<< hqPrefixId ns <- asks names case Names.lookupHQTerm Names.IncludeSuffixes (L.payload id) ns of s @@ -138,7 +142,7 @@ termLink' = do link' :: (Monad m, Var v) => P v m (Either (L.Token Reference) (L.Token Referent)) link' = do - id <- hqPrefixId + id <- applyNamespaceToToken =<< hqPrefixId ns <- asks names case (Names.lookupHQTerm Names.IncludeSuffixes (L.payload id) ns, Names.lookupHQType Names.IncludeSuffixes (L.payload id) ns) of (s, s2) | Set.size s == 1 && Set.null s2 -> pure . Right $ const (Set.findMin s) <$> id @@ -171,22 +175,13 @@ match = do P.try (openBlockWith "with") <|> do t <- anyToken P.customFailure (ExpectedBlockOpen "with" t) - (_arities, cases) <- NonEmpty.unzip <$> matchCases1 start + (_arities, cases) <- unzip <$> matchCases _ <- optionalCloseBlock - pure $ - Term.match - (ann start <> ann (NonEmpty.last cases)) - scrutinee - (toList cases) - -matchCases1 :: (Monad m, Var v) => L.Token () -> P v m (NonEmpty (Int, Term.MatchCase Ann (Term v Ann))) -matchCases1 start = do - cases <- - (sepBy semi matchCase) - <&> \cases_ -> [(n, c) | (n, cs) <- cases_, c <- cs] - case cases of - [] -> P.customFailure (EmptyMatch start) - (c : cs) -> pure (c NonEmpty.:| cs) + let anns = foldr ((<>) . ann) (ann start) $ lastMay cases + pure $ Term.match anns scrutinee cases + +matchCases :: (Monad m, Var v) => P v m [(Int, Term.MatchCase Ann (Term v Ann))] +matchCases = sepBy semi matchCase <&> \cases_ -> [(n, c) | (n, cs) <- cases_, c <- cs] -- Returns the arity of the pattern and the `MatchCase`. Examples: -- @@ -285,36 +280,47 @@ parsePattern = label "pattern" root else pure (Pattern.Var (ann v), [tokenToPair v]) unbound :: P v m (Pattern Ann, [(Ann, v)]) unbound = (\tok -> (Pattern.Unbound (ann tok), [])) <$> blank - ctor :: CT.ConstructorType -> (L.Token (HQ.HashQualified Name) -> Set ConstructorReference -> Error v) -> P v m (L.Token ConstructorReference) - ctor ct err = do + ctor :: CT.ConstructorType -> P v m (L.Token ConstructorReference) + ctor ct = do -- this might be a var, so we avoid consuming it at first - tok <- P.try (P.lookAhead hqPrefixId) + tok <- applyNamespaceToToken =<< P.try (P.lookAhead hqPrefixId) names <- asks names -- probably should avoid looking up in `names` if `L.payload tok` -- starts with a lowercase case Names.lookupHQPattern Names.IncludeSuffixes (L.payload tok) ct names of s - | Set.null s -> die tok s - | Set.size s > 1 -> die tok s - | otherwise -> -- matched ctor name, consume the token - do _ <- anyToken; pure (Set.findMin s <$ tok) + | Set.null s -> die names tok s + | Set.size s > 1 -> die names tok s + | otherwise -> do + -- matched ctor name, consume the token + _ <- anyToken + pure (Set.findMin s <$ tok) where isLower = Text.all Char.isLower . Text.take 1 . Name.toText isIgnored n = Text.take 1 (Name.toText n) == "_" - die hq s = case L.payload hq of - -- if token not hash qualified or uppercase, + die :: Names -> L.Token (HQ.HashQualified Name) -> Set ConstructorReference -> P v m a + die names hq s = case L.payload hq of + -- if token not hash qualified and not uppercase, -- fail w/out consuming it to allow backtracking HQ.NameOnly n | Set.null s && (isLower n || isIgnored n) -> fail $ "not a constructor name: " <> show n - -- it was hash qualified, and wasn't found in the env, that's a failure! - _ -> failCommitted $ err hq s - + -- it was hash qualified and/or uppercase, and wasn't found in the env, that's a failure! + _ -> + failCommitted $ + ResolutionFailures + [ TermResolutionFailure + (L.payload hq) + (ann hq) + if Set.null s + then NotFound + else Ambiguous names (Set.map (\ref -> Referent.Con ref ct) s) Set.empty + ] unzipPatterns f elems = case unzip elems of (patterns, vs) -> f patterns (join vs) effectBind0 = do - tok <- ctor CT.Effect UnknownAbilityConstructor + tok <- ctor CT.Effect leaves <- many leaf _ <- reserved "->" pure (tok, leaves) @@ -338,11 +344,11 @@ parsePattern = label "pattern" root -- ex: unique type Day = Mon | Tue | ... nullaryCtor = P.try do - tok <- ctor CT.Data UnknownDataConstructor + tok <- ctor CT.Data pure (Pattern.Constructor (ann tok) (L.payload tok) [], []) constructor = do - tok <- ctor CT.Data UnknownDataConstructor + tok <- ctor CT.Data let f patterns vs = let loc = foldl (<>) (ann tok) $ map ann patterns in (Pattern.Constructor loc (L.payload tok) patterns, vs) @@ -369,16 +375,17 @@ handle = label "handle" do -- Meaning the newline gets overwritten when pretty-printing and it messes things up. pure $ Term.handle (handleSpan <> ann handler) handler b -checkCasesArities :: (Ord v, Annotated a) => NonEmpty (Int, a) -> P v m (Int, NonEmpty a) -checkCasesArities cases@((i, _) NonEmpty.:| rest) = - case List.find (\(j, _) -> j /= i) rest of +checkCasesArities :: (Ord v, Annotated a) => [(Int, a)] -> P v m (Int, [a]) +checkCasesArities = \case + [] -> pure (1, []) + cases@((i, _) : rest) -> case List.find (\(j, _) -> j /= i) rest of Nothing -> pure (i, snd <$> cases) Just (j, a) -> P.customFailure $ PatternArityMismatch i j (ann a) lamCase :: (Monad m, Var v) => TermP v m lamCase = do start <- openBlockWith "cases" - cases <- matchCases1 start + cases <- matchCases (arity, cases) <- checkCasesArities cases _ <- optionalCloseBlock lamvars <- replicateM arity (Parser.uniqueName 10) @@ -390,8 +397,8 @@ lamCase = do lamvarTerm = case lamvarTerms of [e] -> e es -> DD.tupleTerm es - anns = ann start <> ann (NonEmpty.last cases) - matchTerm = Term.match anns lamvarTerm (toList cases) + anns = foldr ((<>) . ann) (ann start) $ lastMay cases + matchTerm = Term.match anns lamvarTerm cases let annotatedVars = (Ann.GeneratedFrom $ ann start,) <$> vars pure $ Term.lam' anns annotatedVars matchTerm @@ -419,9 +426,6 @@ list = Parser.seq Term.list hashQualifiedPrefixTerm :: (Monad m, Var v) => TermP v m hashQualifiedPrefixTerm = resolveHashQualified =<< hqPrefixId -hashQualifiedInfixTerm :: (Monad m, Var v) => TermP v m -hashQualifiedInfixTerm = resolveHashQualified =<< hqInfixId - quasikeyword :: (Ord v) => Text -> P v m (L.Token ()) quasikeyword kw = queryToken \case L.WordyId (HQ'.NameOnly n) | nameIsKeyword n kw -> Just () @@ -437,15 +441,23 @@ nameIsKeyword name keyword = -- has a short hash, we resolve that short hash immediately and fail -- committed if that short hash can't be found in the current environment resolveHashQualified :: (Monad m, Var v) => L.Token (HQ.HashQualified Name) -> TermP v m -resolveHashQualified tok = do +resolveHashQualified tok0 = do names <- asks names - case L.payload tok of - HQ.NameOnly n -> pure $ Term.var (ann tok) (Name.toVar n) - hqn -> case Names.lookupHQTerm Names.IncludeSuffixes hqn names of - s - | Set.null s -> failCommitted $ UnknownTerm tok s - | Set.size s > 1 -> failCommitted $ UnknownTerm tok s - | otherwise -> pure $ Term.fromReferent (ann tok) (Set.findMin s) + case L.payload tok0 of + HQ.NameOnly n -> pure $ Term.var (ann tok0) (Name.toVar n) + _ -> do + tok <- applyNamespaceToToken tok0 + case Names.lookupHQTerm Names.IncludeSuffixes (L.payload tok) names of + s + | Set.null s -> failCommitted $ UnknownTerm tok s + | Set.size s > 1 -> failCommitted $ UnknownTerm tok s + | otherwise -> pure $ Term.fromReferent (ann tok) (Set.findMin s) + +applyNamespaceToToken :: (Monad m) => L.Token (HQ.HashQualified Name) -> P v m (L.Token (HQ.HashQualified Name)) +applyNamespaceToToken tok = + asks maybeNamespace <&> \case + Nothing -> tok + Just namespace -> fmap (fmap (Name.joinDot namespace)) tok termLeaf :: forall m v. (Monad m, Var v) => TermP v m termLeaf = @@ -1041,17 +1053,85 @@ term4 = f <$> some termLeaf f (func : args) = Term.apps func ((\a -> (ann func <> ann a, a)) <$> args) f [] = error "'some' shouldn't produce an empty list" +data InfixParse v + = InfixOp (L.Token (HQ.HashQualified Name)) (Term v Ann) (InfixParse v) (InfixParse v) + | InfixAnd (L.Token String) (InfixParse v) (InfixParse v) + | InfixOr (L.Token String) (InfixParse v) (InfixParse v) + | InfixOperand (Term v Ann) + deriving (Show, Eq, Ord) + -- e.g. term4 + term4 - term4 -- or term4 || term4 && term4 -infixAppOrBooleanOp :: (Monad m, Var v) => TermP v m -infixAppOrBooleanOp = chainl1 term4 (or <|> and <|> infixApp) +-- The algorithm works as follows: +-- 1. Parse the expression left-associated +-- 2. Starting at the leftmost operator subexpression, see if the next operator +-- has higher precedence. If so, rotate the expression to the right. +-- e.g. in `a + b * c`, we first parse `(a + b) * c` then rotate to `a + (b * c)`. +-- 3. Perform the algorithm on the right-hand side if necessary, as `b` might be +-- an infix expression with lower precedence than `*`. +-- 4. Proceed to the next operator to the right in the original expression and +-- repeat steps 2-3 until we reach the end. +infixAppOrBooleanOp :: forall m v. (Monad m, Var v) => TermP v m +infixAppOrBooleanOp = do + (p, ps) <- prelimParse + -- traceShowM ("orig" :: String, foldl' (flip ($)) p ps) + let p' = reassociate (p, ps) + -- traceShowM ("reassoc" :: String, p') + return (applyInfixOps p') where - or = orf <$> label "or" (reserved "||") - orf op lhs rhs = Term.or (ann lhs <> ann op <> ann rhs) lhs rhs - and = andf <$> label "and" (reserved "&&") - andf op lhs rhs = Term.and (ann lhs <> ann op <> ann rhs) lhs rhs - infixApp = infixAppf <$> label "infixApp" (hashQualifiedInfixTerm <* optional semi) - infixAppf op lhs rhs = Term.apps' op [lhs, rhs] + -- To handle a mix of infix operators with and without precedence rules, + -- we first parse the expression left-associated, then reassociate it + -- according to the precedence rules. + prelimParse = + chainl1Accum (InfixOperand <$> term4) genericInfixApp + genericInfixApp = + (InfixAnd <$> (label "and" (reserved "&&"))) + <|> (InfixOr <$> (label "or" (reserved "||"))) + <|> (uncurry InfixOp <$> parseInfix) + shouldRotate child parent = case (child, parent) of + (Just p1, Just p2) -> p1 < p2 + _ -> False + parseInfix = label "infixApp" do + op <- hqInfixId <* optional semi + resolved <- resolveHashQualified op + pure (op, resolved) + reassociate (exp, ops) = + foldl' checkOp exp ops + checkOp exp op = fixUp (op exp) + fixUp = \case + InfixOp op tm lhs rhs -> + rotate (unqualified op) (InfixOp op tm) lhs rhs + InfixAnd op lhs rhs -> + rotate "&&" (InfixAnd op) lhs rhs + InfixOr op lhs rhs -> + rotate "||" (InfixOr op) lhs rhs + x -> x + rotate op ctor lhs rhs = + case lhs of + InfixOp lop ltm ll lr + | shouldRotate (operatorPrecedence (unqualified lop)) (operatorPrecedence op) -> + InfixOp lop ltm ll (fixUp (ctor lr rhs)) + InfixAnd lop ll lr + | shouldRotate (operatorPrecedence "&&") (operatorPrecedence op) -> + InfixAnd lop ll (fixUp (ctor lr rhs)) + InfixOr lop ll lr + | shouldRotate (operatorPrecedence "||") (operatorPrecedence op) -> + InfixOr lop ll (fixUp (ctor lr rhs)) + _ -> ctor lhs rhs + unqualified t = Maybe.fromJust $ NameSegment.toEscapedText . Name.lastSegment <$> (HQ.toName $ L.payload t) + applyInfixOps :: InfixParse v -> Term v Ann + applyInfixOps t = case t of + InfixOp _ tm lhs rhs -> + Term.apps' tm [applyInfixOps lhs, applyInfixOps rhs] + InfixOperand tm -> tm + InfixAnd op lhs rhs -> + let lhs' = applyInfixOps lhs + rhs' = applyInfixOps rhs + in Term.and (ann lhs' <> ann op <> ann rhs') lhs' rhs' + InfixOr op lhs rhs -> + let lhs' = applyInfixOps lhs + rhs' = applyInfixOps rhs + in Term.or (ann lhs' <> ann op <> ann rhs') lhs' rhs' typedecl :: (Monad m, Var v) => P v m (L.Token v, Type v Ann) typedecl = @@ -1249,14 +1329,14 @@ block' isTop implicitUnitAtEnd s openBlock closeBlock = do open <- openBlock (names, imports) <- imports _ <- optional semi - statements <- local (\e -> e {names = names}) $ sepBy semi statement + statements <- local (\e -> e {names}) $ sepBy semi statement end <- closeBlock body <- substImports names imports <$> go open statements pure (ann open <> ann end, body) where statement = asum [Binding <$> binding, DestructuringBind <$> destructuringBind, Action <$> blockTerm] go :: L.Token () -> [BlockElement v] -> P v m (Term v Ann) - go open bs = + go open = let finish :: Term.Term v Ann -> TermP v m finish tm = case Components.minimize' tm of Left dups -> customFailure $ DuplicateTermNames (toList dups) @@ -1296,7 +1376,7 @@ block' isTop implicitUnitAtEnd s openBlock closeBlock = do if implicitUnitAtEnd then (toList bs, DD.unitTerm a) else (toList bs, Term.var a (positionalVar a Var.missingResult)) - in toTm bs + in toTm number :: (Var v) => TermP v m number = number' (tok Term.int) (tok Term.nat) (tok Term.float) diff --git a/parser-typechecker/src/Unison/Syntax/TermPrinter.hs b/parser-typechecker/src/Unison/Syntax/TermPrinter.hs index 5c41701bf8..cddc64399a 100644 --- a/parser-typechecker/src/Unison/Syntax/TermPrinter.hs +++ b/parser-typechecker/src/Unison/Syntax/TermPrinter.hs @@ -55,6 +55,7 @@ import Unison.Syntax.Lexer.Unison (showEscapeChar) import Unison.Syntax.Name qualified as Name (isSymboly, parseText, parseTextEither, toText, unsafeParseText) import Unison.Syntax.NamePrinter (styleHashQualified'') import Unison.Syntax.NameSegment qualified as NameSegment (toEscapedText) +import Unison.Syntax.Precedence (InfixPrecedence (..), Precedence (..), increment, isTopLevelPrecedence, operatorPrecedence) import Unison.Syntax.TypePrinter qualified as TypePrinter import Unison.Term import Unison.Type (Type, pattern ForallsNamed') @@ -92,7 +93,7 @@ data AmbientContext = AmbientContext { -- The operator precedence of the enclosing context (a number from 0 to 11, -- or -1 to render without outer parentheses unconditionally). -- Function application has precedence 10. - precedence :: !Int, -- -2 indicates top level binding, this is occasionally useful + precedence :: !Precedence, blockContext :: !BlockContext, infixContext :: !InfixContext, imports :: !Imports, @@ -125,50 +126,58 @@ data DocLiteralContext We illustrate precedence rules as follows. - >=10 - 10f 10x + >=Application + (Application)f (Application)x This example shows that a function application f x is enclosed in - parentheses whenever the ambient precedence around it is >= 10, and that - when printing its two components, an ambient precedence of 10 is used in + parentheses whenever the ambient precedence around it is >= Application, and that + when printing its two components, an ambient precedence of Application is used in both places. The pretty-printer uses the following rules for printing terms. - >=12 - let x = (-1)y - 1z + >=Top + let x = (Bottom)y + (Statement)z - >=11 - ! 11x - ' 11x - 11x ? + >=Prefix + ! (Prefix)x + ' (Prefix)x + (Prefix)x ? - >=10 - 10f 10x 10y ... + >=(Application) + (Application)f (Application)x (Application)y ... termLink t typeLink t - >=3 - x -> 2y - 3x + 3y + ... 3z + >=(Infix +) + (Infix +)x + (Infix +)y + ... (Infix +)z - >=2 - if 0a then 0b else 0c - handle 0b with 0h - case 2x of - a | 2g -> 0b + Printing an infix operator in infix position has the following additional + rule: If the operator has a lower precedence than the ambient precedence, + it is enclosed in parentheses. If the operator has no precedence rule, + its precedence is assumed to be higher than any operator to its right, and + lower than any operator to its left. - >=0 - 10a : 0Int + >(Control) + x -> (Control)y + + >=(Control) + if (Annotation)a then (Annotation)b else (Annotation)c + handle (Annoration)b with (Annotation)h + case (Control)x of + a | (Control)g -> (Control)b + + >=(Annotation) + (Application)a : (Annotation)Int And the following for patterns. - >=11 - x@11p + >=Prefix + x@(Prefix)p - >=10 - Con 10p 10q ... + >=Application + Con (Application)p (Application)q ... -- never any external parens added around the following { p } @@ -191,7 +200,7 @@ pretty0 a tm | isTopLevelPrecedence (precedence a) && not (isBindingSoftHangable -- we allow use clause insertion here even when it otherwise wouldn't be -- (as long as the tm isn't soft hangable, if it gets soft hung then -- adding use clauses beforehand will mess things up) - tmp <- pretty0 (a {imports = im, precedence = -1}) tm + tmp <- pretty0 (a {imports = im, precedence = Bottom}) tm pure $ PP.lines (uses <> [tmp]) where (im, uses) = calcImports (imports a) tm @@ -217,19 +226,19 @@ pretty0 TermLink' r -> do n <- getPPE let name = elideFQN im $ PrettyPrintEnv.termName n r - pure . paren (p >= 10) $ + pure . paren (p >= Application) $ fmt S.LinkKeyword "termLink " <> parenIfInfix name ic (styleHashQualified'' (fmt $ S.TermReference r) name) TypeLink' r -> do n <- getPPE let name = elideFQN im $ PrettyPrintEnv.typeName n r - pure . paren (p >= 10) $ + pure . paren (p >= Application) $ fmt S.LinkKeyword "typeLink " <> parenIfInfix name ic (styleHashQualified'' (fmt $ S.TypeReference r) name) Ann' tm t -> do - tm' <- pretty0 (ac 10 Normal im doc) tm + tm' <- pretty0 (ac Application Normal im doc) tm tp' <- TypePrinter.pretty0 im 0 t - pure . paren (p >= 0) $ tm' <> PP.hang (fmt S.TypeAscriptionColon " :") tp' + pure . paren (p >= Annotation) $ tm' <> PP.hang (fmt S.TypeAscriptionColon " :") tp' Int' i -> pure . fmt S.NumericLiteral . l $ (if i >= 0 then ("+" ++ show i) else (show i)) Nat' u -> pure . fmt S.NumericLiteral . l $ show u Float' f -> pure . fmt S.NumericLiteral . l $ show f @@ -247,7 +256,7 @@ pretty0 where -- we only use this syntax if we're not wrapped in something else, -- to avoid possible round trip issues if the text ends at an odd column - useRaw _ | p >= 0 = Nothing + useRaw _ | p >= Annotation = Nothing useRaw s | Text.find (== '\n') s == Just '\n' && Text.all ok s = n 3 useRaw _ = Nothing ok ch = isPrint ch || ch == '\n' || ch == '\r' @@ -278,13 +287,13 @@ pretty0 conRef = Referent.Con ref CT.Effect pure $ styleHashQualified'' (fmt $ S.TermReference conRef) name Handle' h body -> do - pb <- pretty0 (ac 0 Block im doc) body - ph <- pretty0 (ac 0 Block im doc) h + pb <- pretty0 (ac Annotation Block im doc) body + ph <- pretty0 (ac Annotation Block im doc) h let hangHandler = case h of -- handle ... with cases LamsNamedMatch' [] _ -> \a b -> a <> " " <> b _ -> PP.hang - pure . paren (p >= 2) $ + pure . paren (p >= Control) $ if PP.isMultiLine pb || PP.isMultiLine ph then PP.lines @@ -301,36 +310,36 @@ pretty0 ] Delay' x | Match' _ _ <- x -> do - px <- pretty0 (ac 0 Block im doc) x + px <- pretty0 (ac Annotation Block im doc) x let hang = if isSoftHangable x then PP.softHang else PP.hang - pure . paren (p >= 3) $ + pure . paren (p > Control) $ fmt S.ControlKeyword "do" `hang` px | otherwise -> do let (im0', uses0) = calcImports im x - let allowUses = isLet x || p < 0 + let allowUses = isLet x || (p == Bottom) let im' = if allowUses then im0' else im let uses = if allowUses then uses0 else [] - let soft = isSoftHangable x && null uses && p < 3 + let soft = isSoftHangable x && null uses && p < Annotation let hang = if soft then PP.softHang else PP.hang - px <- pretty0 (ac 0 Block im' doc) x + px <- pretty0 (ac Annotation Block im' doc) x -- this makes sure we get proper indentation if `px` spills onto -- multiple lines, since `do` introduces layout block - let indent = PP.Width (if soft then 2 else 0) + (if soft && p < 3 then 1 else 0) - pure . paren (p >= 3) $ + let indent = PP.Width (if soft then 2 else 0) + (if soft && p < Application then 1 else 0) + pure . paren (p > Control) $ fmt S.ControlKeyword "do" `hang` PP.lines (uses <> [PP.indentNAfterNewline indent px]) List' xs -> do let listLink p = fmt (S.TypeReference Type.listRef) p let comma = listLink ", " `PP.orElse` ("\n" <> listLink ", ") - pelems <- traverse (fmap (PP.indentNAfterNewline 2) . pretty0 (ac 0 Normal im doc)) xs + pelems <- traverse (fmap (PP.indentNAfterNewline 2) . pretty0 (ac Annotation Normal im doc)) xs let open = listLink "[" `PP.orElse` listLink "[ " let close = listLink "]" `PP.orElse` ("\n" <> listLink "]") pure $ PP.group (open <> PP.sep comma pelems <> close) If' cond t f -> do - pcond <- pretty0 (ac 2 Block im doc) cond - pt <- pretty0 (ac 0 Block im doc) t - pf <- pretty0 (ac 0 Block im doc) f - pure . paren (p >= 2) $ + pcond <- pretty0 (ac Control Block im doc) cond + pt <- pretty0 (ac Annotation Block im doc) t + pf <- pretty0 (ac Annotation Block im doc) f + pure . paren (p >= Control) $ if PP.isMultiLine pcond then PP.lines @@ -360,19 +369,19 @@ pretty0 -- blah -- See `isDestructuringBind` definition. Match' scrutinee cs@[MatchCase pat guard (AbsN' vs body)] - | p <= 2 && isDestructuringBind scrutinee cs -> do + | p <= Control && isDestructuringBind scrutinee cs -> do n <- getPPE let letIntro = case bc of Block -> id Normal -> \x -> fmt S.ControlKeyword "let" `PP.hang` x lhs <- do - let (lhs, _) = prettyPattern n (ac 0 Block im doc) 10 vs pat + let (lhs, _) = prettyPattern n (ac Annotation Block im doc) Application vs pat guard' <- printGuard guard pure $ PP.group lhs `PP.hang` guard' let eq = fmt S.BindingEquals "=" - rhs <- pretty0 (ac (-1) Block im doc) scrutinee + rhs <- pretty0 (ac Bottom Block im doc) scrutinee letIntro <$> do - prettyBody <- pretty0 (ac (-1) Block im doc) body + prettyBody <- pretty0 (ac Bottom Block im doc) body pure $ PP.lines [ (lhs <> eq) `PP.hang` rhs, @@ -382,13 +391,13 @@ pretty0 printGuard Nothing = pure mempty printGuard (Just g') = do let (_, g) = ABT.unabs g' - prettyg <- pretty0 (ac 2 Normal im doc) g + prettyg <- pretty0 (ac Control Normal im doc) g pure $ fmt S.DelimiterChar "| " <> prettyg Match' scrutinee branches -> do - ps <- pretty0 (ac 2 Normal im doc) scrutinee + ps <- pretty0 (ac Control Normal im doc) scrutinee pbs <- printCase im doc (arity1Branches branches) -- don't print with `cases` syntax - pure . paren (p >= 2) $ + pure . paren (p >= Control) $ if PP.isMultiLine ps then PP.lines @@ -396,7 +405,7 @@ pretty0 fmt S.ControlKeyword " with" `PP.hang` pbs ] else (fmt S.ControlKeyword "match " <> ps <> fmt S.ControlKeyword " with") `PP.hang` pbs - Apps' f args -> paren (p >= 10) <$> (PP.hang <$> goNormal 9 f <*> PP.spacedTraverse (goNormal 10) args) + Apps' f args -> paren (p >= Application) <$> (PP.hang <$> goNormal (InfixOp Highest) f <*> PP.spacedTraverse (goNormal Application) args) t -> pure $ l "error: " <> l (show t) where goNormal prec tm = pretty0 (ac prec Normal im doc) tm @@ -416,6 +425,20 @@ pretty0 Ref' r -> isSymbolic $ PrettyPrintEnv.termName n (Referent.Ref r) Var' v -> isSymbolic $ HQ.unsafeFromVar v _ -> False + -- Gets the precedence of an infix operator, if it has one. + termPrecedence :: Term3 v PrintAnnotation -> Maybe Precedence + termPrecedence = \case + Ref' r -> + HQ.toName (PrettyPrintEnv.termName n (Referent.Ref r)) + >>= operatorPrecedence + . NameSegment.toEscapedText + . Name.lastSegment + Var' v -> + HQ.toName (HQ.unsafeFromVar v) + >>= operatorPrecedence + . NameSegment.toEscapedText + . Name.lastSegment + _ -> Nothing case (term, binaryOpsPred) of (DD.Doc, _) | doc == MaybeDoc -> @@ -426,27 +449,27 @@ pretty0 let conRef = DD.pairCtorRef name <- elideFQN im <$> applyPPE2 PrettyPrintEnv.termName conRef let pair = parenIfInfix name ic $ styleHashQualified'' (fmt (S.TermReference conRef)) name - x' <- pretty0 (ac 10 Normal im doc) x - pure . paren (p >= 10) $ + x' <- pretty0 (ac Application Normal im doc) x + pure . paren (p >= Application) $ pair `PP.hang` PP.spaced [x', fmt (S.TermReference DD.unitCtorRef) "()"] (TupleTerm' xs, _) -> do let tupleLink p = fmt (S.TypeReference DD.pairRef) p let comma = tupleLink ", " `PP.orElse` ("\n" <> tupleLink ", ") - pelems <- traverse (fmap (PP.indentNAfterNewline 2) . goNormal 0) xs + pelems <- traverse (fmap (PP.indentNAfterNewline 2) . goNormal Annotation) xs let clist = PP.sep comma pelems let open = tupleLink "(" `PP.orElse` tupleLink "( " let close = tupleLink ")" `PP.orElse` ("\n" <> tupleLink ")") pure $ PP.group (open <> clist <> close) (App' f@(Builtin' "Any.Any") arg, _) -> - paren (p >= 10) <$> (PP.hang <$> goNormal 9 f <*> goNormal 10 arg) + paren (p >= Application) <$> (PP.hang <$> goNormal (InfixOp Highest) f <*> goNormal Application arg) (DD.Rewrites' rs, _) -> do let kw = fmt S.ControlKeyword "@rewrite" arr = fmt S.ControlKeyword "==>" control = fmt S.ControlKeyword - sub kw lhs = PP.sep " " <$> sequence [pure $ control kw, goNormal 0 lhs, pure arr] - go (DD.RewriteTerm' lhs rhs) = PP.hang <$> sub "term" lhs <*> goNormal 0 rhs - go (DD.RewriteCase' lhs rhs) = PP.hang <$> sub "case" lhs <*> goNormal 0 rhs + sub kw lhs = PP.sep " " <$> sequence [pure $ control kw, goNormal Annotation lhs, pure arr] + go (DD.RewriteTerm' lhs rhs) = PP.hang <$> sub "term" lhs <*> goNormal Annotation rhs + go (DD.RewriteCase' lhs rhs) = PP.hang <$> sub "case" lhs <*> goNormal Annotation rhs go (DD.RewriteSignature' vs lhs rhs) = do lhs <- TypePrinter.pretty0 im 0 lhs PP.hang (PP.sep " " (stuff lhs)) <$> TypePrinter.pretty0 im 0 rhs @@ -456,17 +479,32 @@ pretty0 <> [fmt S.Var (PP.text (Var.name v)) | v <- vs] <> (if null vs then [] else [fmt S.TypeOperator "."]) <> [lhs, arr] - go tm = goNormal 10 tm + go tm = goNormal Application tm PP.hang kw <$> fmap PP.lines (traverse go rs) (Bytes' bs, _) -> pure $ PP.group $ fmt S.BytesLiteral "0xs" <> PP.shown (Bytes.fromWord8s (map fromIntegral bs)) - BinaryAppsPred' apps lastArg -> do - prettyLast <- pretty0 (ac 3 Normal im doc) lastArg - prettyApps <- binaryApps apps prettyLast - pure $ paren (p >= 3) prettyApps - -- Note that && and || are at the same precedence, which can cause - -- confusion, so for clarity we do not want to elide the parentheses in a - -- case like `(x || y) && z`. + BinaryAppPred' f a b -> do + let prec = termPrecedence f + prettyF <- pretty0 (AmbientContext Application Normal Infix im doc False) f + prettyA <- pretty0 (ac (fromMaybe (InfixOp Lowest) prec) Normal im doc) a + prettyB <- pretty0 (ac (fromMaybe (InfixOp Highest) prec) Normal im doc) b + pure . parenNoGroup (p > fromMaybe (InfixOp Lowest) prec) $ + (prettyA <> " " <> prettyF <> " " <> prettyB) `PP.orElse` (prettyA `PP.hangUngrouped` (PP.column2 [(prettyF, prettyB)])) + (And' a b, _) -> do + let prec = operatorPrecedence "&&" + prettyF = fmt S.ControlKeyword "&&" + prettyA <- pretty0 (ac (fromMaybe (InfixOp Lowest) prec) Normal im doc) a + prettyB <- pretty0 (ac (fromMaybe (InfixOp Highest) prec) Normal im doc) b + pure . parenNoGroup (p > fromMaybe (InfixOp Lowest) prec) $ + (prettyA <> " " <> prettyF <> " " <> prettyB) `PP.orElse` (prettyA `PP.hangUngrouped` (PP.column2 [(prettyF, prettyB)])) + (Or' a b, _) -> do + let prec = operatorPrecedence "||" + prettyF = fmt S.ControlKeyword "||" + prettyA <- pretty0 (ac (fromMaybe (InfixOp Lowest) prec) Normal im doc) a + prettyB <- pretty0 (ac (fromMaybe (InfixOp Highest) prec) Normal im doc) b + pure . parenNoGroup (p > fromMaybe (InfixOp Lowest) prec) $ + PP.group (prettyA <> " " <> prettyF <> " " <> prettyB) + `PP.orElse` (prettyA `PP.hangUngrouped` prettyF <> " " <> prettyB) {- When a delayed computation block is passed to a function as the last argument in a context where the ambient precedence is low enough, we can elide parentheses @@ -488,52 +526,36 @@ pretty0 ...) -} (App' x (Constructor' (ConstructorReference DD.UnitRef 0)), _) | isLeaf x -> do - px <- pretty0 (ac (if isBlock x then 0 else 9) Normal im doc) x - pure . paren (p >= 11 || isBlock x && p >= 3) $ + px <- pretty0 (ac (if isBlock x then Annotation else InfixOp Highest) Normal im doc) x + pure . paren (p >= Prefix || isBlock x && p >= (InfixOp Lowest)) $ px <> fmt S.Unit (l "()") (Apps' f (unsnoc -> Just (args, lastArg)), _) | isSoftHangable lastArg -> do - fun <- goNormal 9 f - args' <- traverse (goNormal 10) args - lastArg' <- goNormal 0 lastArg + fun <- goNormal (InfixOp Highest) f + args' <- traverse (goNormal Application) args + lastArg' <- goNormal Annotation lastArg let softTab = PP.softbreak <> ("" `PP.orElse` " ") - pure . paren (p >= 3) $ + pure . paren (p >= (InfixOp Lowest)) $ PP.group (PP.group (PP.group (PP.sep softTab (fun : args') <> softTab)) <> lastArg') - (Ands' xs lastArg, _) -> - paren (p >= 10) <$> do - lastArg' <- pretty0 (ac 10 Normal im doc) lastArg - booleanOps (fmt S.ControlKeyword "&&") xs lastArg' - (Ors' xs lastArg, _) -> - paren (p >= 10) <$> do - lastArg' <- pretty0 (ac 10 Normal im doc) lastArg - booleanOps (fmt S.ControlKeyword "||") xs lastArg' _other -> case (term, nonForcePred) of - OverappliedBinaryAppPred' f a b r - | binaryOpsPred f -> - -- Special case for overapplied binary op - do - prettyB <- pretty0 (ac 3 Normal im doc) b - prettyR <- PP.spacedTraverse (pretty0 (ac 10 Normal im doc)) r - prettyA <- binaryApps [(f, a)] prettyB - pure $ paren True $ PP.hang prettyA prettyR AppsPred' f args -> - paren (p >= 10) <$> do - f' <- pretty0 (ac 10 Normal im doc) f - args' <- PP.spacedTraverse (pretty0 (ac 10 Normal im doc)) args + paren (p >= Application) <$> do + f' <- pretty0 (ac Application Normal im doc) f + args' <- PP.spacedTraverse (pretty0 (ac Application Normal im doc)) args pure $ f' `PP.hang` args' _other -> case (term, \v -> nonUnitArgPred v && not (isDelay term)) of (LamsNamedMatch' [] branches, _) -> do pbs <- printCase im doc branches - pure . paren (p >= 3) $ + pure . paren (p >= InfixOp Lowest) $ PP.group (fmt S.ControlKeyword "cases") `PP.hang` pbs LamsNamedPred' vs body -> do - prettyBody <- pretty0 (ac 2 Normal im doc) body + prettyBody <- pretty0 (ac Control Normal im doc) body let hang = case body of Delay' (Lets' _ _) -> PP.softHang Lets' _ _ -> PP.softHang Match' _ _ -> PP.softHang _ -> PP.hang - pure . paren (p >= 3) $ + pure . paren (p >= InfixOp Lowest) $ PP.group (varList vs <> fmt S.ControlKeyword " ->") `hang` prettyBody _other -> go term @@ -553,14 +575,14 @@ pretty0 printLet elideUnit sc bs e im uses = do bs <- traverse printBinding bs body <- body e - pure . paren (sc /= Block && p >= 12) . letIntro $ PP.lines (uses <> bs <> body) + pure . paren (sc /= Block && p >= Top) . letIntro $ PP.lines (uses <> bs <> body) where body (Constructor' (ConstructorReference DD.UnitRef 0)) | elideUnit = pure [] - body e = (: []) <$> pretty0 (ac 0 Normal im doc) e + body e = (: []) <$> pretty0 (ac Annotation Normal im doc) e printBinding (v, binding) = if Var.isAction v - then pretty0 (ac (-1) Normal im doc) binding - else renderPrettyBinding <$> prettyBinding0' (ac (-1) Normal im doc) (HQ.unsafeFromVar v) binding + then pretty0 (ac Bottom Normal im doc) binding + else renderPrettyBinding <$> prettyBinding0' (ac Bottom Normal im doc) (HQ.unsafeFromVar v) binding letIntro = case sc of Block -> id Normal -> \x -> fmt S.ControlKeyword "let" `PP.hang` x @@ -573,64 +595,12 @@ pretty0 nonUnitArgPred :: (Var v) => v -> Bool nonUnitArgPred v = Var.name v /= "()" - -- Render a binary infix operator sequence, like [(a2, f2), (a1, f1)], - -- meaning (a1 `f1` a2) `f2` (a3 rendered by the caller), producing - -- "a1 `f1` a2 `f2`". Except the operators are all symbolic, so we won't - -- produce any backticks. We build the result out from the right, - -- starting at `f2`. - binaryApps :: - [(Term3 v PrintAnnotation, Term3 v PrintAnnotation)] -> - Pretty SyntaxText -> - m (Pretty SyntaxText) - binaryApps xs last = - do - ps <- join <$> traverse (uncurry r) (reverse xs) - let unbroken = PP.spaced (ps <> [last]) - broken = PP.hang (head ps) . PP.column2 . psCols $ tail ps <> [last] - pure (unbroken `PP.orElse` broken) - where - psCols ps = case take 2 ps of - [x, y] -> (x, y) : psCols (drop 2 ps) - [x] -> [(x, "")] - [] -> [] - _ -> undefined - r a f = - sequenceA - [ pretty0 (ac (if isBlock a then 12 else 3) Normal im doc) a, - pretty0 (AmbientContext 10 Normal Infix im doc False) f - ] - - -- Render sequence of infix &&s or ||s, like [x2, x1], - -- meaning (x1 && x2) && (x3 rendered by the caller), producing - -- "x1 && x2 &&". The result is built from the right. - booleanOps :: - Pretty SyntaxText -> - [Term3 v PrintAnnotation] -> - Pretty SyntaxText -> - m (Pretty SyntaxText) - booleanOps op xs last = do - ps <- join <$> traverse r (reverse xs) - let unbroken = PP.spaced (ps <> [last]) - broken = PP.hang (head ps) . PP.column2 . psCols $ tail ps <> [last] - pure (unbroken `PP.orElse` broken) - where - psCols ps = case take 2 ps of - [x, y] -> (x, y) : psCols (drop 2 ps) - [x] -> [(x, "")] - [] -> [] - _ -> undefined - r a = - sequence - [ pretty0 (ac (if isBlock a then 12 else 10) Normal im doc) a, - pure op - ] - prettyPattern :: forall v loc. (Var v) => PrettyPrintEnv -> AmbientContext -> - Int -> + Precedence -> [v] -> Pattern loc -> (Pretty SyntaxText, [v]) @@ -657,7 +627,7 @@ prettyPattern n c@AmbientContext {imports = im} p vs patt = case patt of Pattern.Text _ t -> (fmt S.TextLiteral $ l $ show t, vs) TuplePattern pats | length pats /= 1 -> - let (pats_printed, tail_vs) = patterns (-1) vs pats + let (pats_printed, tail_vs) = patterns Bottom vs pats in (PP.parenthesizeCommas pats_printed, tail_vs) Pattern.Constructor _ ref [] -> (styleHashQualified'' (fmt $ S.TermReference conRef) name, vs) @@ -665,10 +635,10 @@ prettyPattern n c@AmbientContext {imports = im} p vs patt = case patt of name = elideFQN im $ PrettyPrintEnv.termName n conRef conRef = Referent.Con ref CT.Data Pattern.Constructor _ ref pats -> - let (pats_printed, tail_vs) = patternsSep 10 PP.softbreak vs pats + let (pats_printed, tail_vs) = patternsSep Application PP.softbreak vs pats name = elideFQN im $ PrettyPrintEnv.termName n conRef conRef = Referent.Con ref CT.Data - in ( paren (p >= 10) $ + in ( paren (p >= Application) $ styleHashQualified'' (fmt $ S.TermReference conRef) name `PP.hang` pats_printed, tail_vs @@ -676,15 +646,15 @@ prettyPattern n c@AmbientContext {imports = im} p vs patt = case patt of Pattern.As _ pat -> case vs of (v : tail_vs) -> - let (printed, eventual_tail) = prettyPattern n c 11 tail_vs pat - in (paren (p >= 11) (fmt S.Var (l $ Var.nameStr v) <> fmt S.DelimiterChar (l "@") <> printed), eventual_tail) + let (printed, eventual_tail) = prettyPattern n c Prefix tail_vs pat + in (paren (p >= Prefix) (fmt S.Var (l $ Var.nameStr v) <> fmt S.DelimiterChar (l "@") <> printed), eventual_tail) _ -> error "prettyPattern: Expected at least one var" Pattern.EffectPure _ pat -> - let (printed, eventual_tail) = prettyPattern n c (-1) vs pat + let (printed, eventual_tail) = prettyPattern n c Bottom vs pat in (PP.sep " " [fmt S.DelimiterChar "{", printed, fmt S.DelimiterChar "}"], eventual_tail) Pattern.EffectBind _ ref pats k_pat -> - let (pats_printed, tail_vs) = patternsSep 10 PP.softbreak vs pats - (k_pat_printed, eventual_tail) = prettyPattern n c 0 tail_vs k_pat + let (pats_printed, tail_vs) = patternsSep Application PP.softbreak vs pats + (k_pat_printed, eventual_tail) = prettyPattern n c Annotation tail_vs k_pat name = elideFQN im $ PrettyPrintEnv.termName n conRef conRef = Referent.Con ref CT.Effect in ( PP.group @@ -700,16 +670,16 @@ prettyPattern n c@AmbientContext {imports = im} p vs patt = case patt of eventual_tail ) Pattern.SequenceLiteral _ pats -> - let (pats_printed, tail_vs) = patternsSep (-1) (fmt S.DelimiterChar ", ") vs pats + let (pats_printed, tail_vs) = patternsSep Bottom (fmt S.DelimiterChar ", ") vs pats in (fmt S.DelimiterChar "[" <> pats_printed <> fmt S.DelimiterChar "]", tail_vs) Pattern.SequenceOp _ l op r -> let (pl, lvs) = prettyPattern n c p vs l - (pr, rvs) = prettyPattern n c (p + 1) lvs r + (pr, rvs) = prettyPattern n c (increment p) lvs r f i s = (paren (p >= i) (pl <> " " <> fmt (S.Op op) s <> " " <> pr), rvs) in case op of - Pattern.Cons -> f 0 "+:" - Pattern.Snoc -> f 0 ":+" - Pattern.Concat -> f 0 "++" + Pattern.Cons -> f Annotation "+:" + Pattern.Snoc -> f Annotation ":+" + Pattern.Concat -> f Annotation "++" where l :: (IsString s) => String -> s l = fromString @@ -792,14 +762,14 @@ printCase im doc ms0 = grid = traverse go ms patLhs env vs pats = case pats of - [pat] -> PP.group (fst (prettyPattern env (ac 0 Block im doc) (-1) vs pat)) + [pat] -> PP.group (fst (prettyPattern env (ac Annotation Block im doc) Bottom vs pat)) pats -> PP.group . PP.sep (PP.indentAfterNewline " " $ "," <> PP.softbreak) . (`evalState` vs) . for pats $ \pat -> do vs <- State.get - let (p, rem) = prettyPattern env (ac 0 Block im doc) (-1) vs pat + let (p, rem) = prettyPattern env (ac Annotation Block im doc) Bottom vs pat State.put rem pure p arrow = fmt S.ControlKeyword "->" @@ -822,8 +792,8 @@ printCase im doc ms0 = -- strip off any Abs-chain around the guard, guard variables are rendered -- like any other variable, ex: case Foo x y | x < y -> ... PP.spaceIfNeeded (fmt S.DelimiterChar "|") - <$> pretty0 (ac 2 Normal im doc) g - printBody = pretty0 (ac 0 Block im doc) + <$> pretty0 (ac Control Normal im doc) g + printBody = pretty0 (ac Annotation Block im doc) -- A pretty term binding, split into the type signature (possibly empty) and the term. data PrettyBinding = PrettyBinding @@ -882,7 +852,7 @@ prettyBinding_ :: Term2 v at ap v a -> Pretty SyntaxText prettyBinding_ go ppe n tm = - runPretty (avoidShadowing tm ppe) . fmap go $ prettyBinding0 (ac (-2) Block Map.empty MaybeDoc) n tm + runPretty (avoidShadowing tm ppe) . fmap go $ prettyBinding0 (ac Basement Block Map.empty MaybeDoc) n tm prettyBinding' :: (Var v) => @@ -1062,8 +1032,11 @@ prettyDoc n im term = spaceUnlessBroken = PP.orElse " " "" paren :: Bool -> Pretty SyntaxText -> Pretty SyntaxText -paren True s = PP.group $ fmt S.Parenthesis "(" <> s <> fmt S.Parenthesis ")" -paren False s = PP.group s +paren b s = PP.group $ parenNoGroup b s + +parenNoGroup :: Bool -> Pretty SyntaxText -> Pretty SyntaxText +parenNoGroup True s = fmt S.Parenthesis "(" <> s <> fmt S.Parenthesis ")" +parenNoGroup False s = s parenIfInfix :: HQ.HashQualified Name -> @@ -1080,12 +1053,12 @@ isSymbolic = maybe False Name.isSymboly . HQ.toName emptyAc :: AmbientContext -emptyAc = ac (-1) Normal Map.empty MaybeDoc +emptyAc = ac Bottom Normal Map.empty MaybeDoc emptyBlockAc :: AmbientContext -emptyBlockAc = ac (-1) Block Map.empty MaybeDoc +emptyBlockAc = ac Bottom Block Map.empty MaybeDoc -ac :: Int -> BlockContext -> Imports -> DocLiteralContext -> AmbientContext +ac :: Precedence -> BlockContext -> Imports -> DocLiteralContext -> AmbientContext ac prec bc im doc = AmbientContext prec bc NonInfix im doc False fmt :: S.Element r -> Pretty (S.SyntaxText' r) -> Pretty (S.SyntaxText' r) @@ -1578,13 +1551,15 @@ isDestructuringBind scrutinee [MatchCase pat _ (ABT.AbsN' vs _)] = Pattern.Unbound _ -> False isDestructuringBind _ _ = False -isBlock :: (Ord v) => Term2 vt at ap v a -> Bool +isBlock :: (Var v, Ord v) => Term2 vt at ap v a -> Bool isBlock tm = case tm of If' {} -> True Handle' _ _ -> True Match' _ _ -> True LetBlock _ _ -> True + DDelay' _ -> True + Delay' _ -> True _ -> False pattern LetBlock :: @@ -2169,7 +2144,3 @@ isLeaf (Constructor' {}) = True isLeaf (Request' {}) = True isLeaf (Ref' {}) = True isLeaf _ = False - --- | Indicates this is the RHS of a top-level definition. -isTopLevelPrecedence :: Int -> Bool -isTopLevelPrecedence i = i == -2 diff --git a/parser-typechecker/src/Unison/Typechecker/Context.hs b/parser-typechecker/src/Unison/Typechecker/Context.hs index 214fe95a0c..767fa37316 100644 --- a/parser-typechecker/src/Unison/Typechecker/Context.hs +++ b/parser-typechecker/src/Unison/Typechecker/Context.hs @@ -1526,10 +1526,8 @@ ensurePatternCoverage theMatch _theMatchType _scrutinee scrutineeType cases = do } (redundant, _inaccessible, uncovered) <- flip evalStateT pmcState do checkMatch scrutineeType cases - let checkUncovered = case Nel.nonEmpty uncovered of - Nothing -> pure () - Just xs -> failWith (UncoveredPatterns matchLoc xs) - checkRedundant = foldr (\a b -> failWith (RedundantPattern a) *> b) (pure ()) redundant + let checkUncovered = maybe (pure ()) (failWith . UncoveredPatterns matchLoc) $ Nel.nonEmpty uncovered + checkRedundant = foldr ((*>) . failWith . RedundantPattern) (pure ()) redundant checkUncovered *> checkRedundant checkCases :: diff --git a/parser-typechecker/src/Unison/UnisonFile/Names.hs b/parser-typechecker/src/Unison/UnisonFile/Names.hs index 6a0d77ff12..e0991c1c16 100644 --- a/parser-typechecker/src/Unison/UnisonFile/Names.hs +++ b/parser-typechecker/src/Unison/UnisonFile/Names.hs @@ -1,7 +1,11 @@ -module Unison.UnisonFile.Names where +module Unison.UnisonFile.Names + ( addNamesFromTypeCheckedUnisonFile, + environmentFor, + toNames, + typecheckedToNames, + ) +where -import Control.Lens -import Data.List.Extra (nubOrd) import Data.Map qualified as Map import Data.Set qualified as Set import Unison.ABT qualified as ABT @@ -9,7 +13,6 @@ import Unison.DataDeclaration (DataDeclaration, EffectDeclaration (..)) import Unison.DataDeclaration qualified as DD import Unison.DataDeclaration.Names qualified as DD.Names import Unison.Hashing.V2.Convert qualified as Hashing -import Unison.Name qualified as Name import Unison.Names (Names (..)) import Unison.Names.ResolutionResult qualified as Names import Unison.NamesWithHistory qualified as Names @@ -17,15 +20,12 @@ import Unison.Prelude import Unison.Reference qualified as Reference import Unison.Referent qualified as Referent import Unison.Syntax.Name qualified as Name -import Unison.Term qualified as Term import Unison.UnisonFile qualified as UF import Unison.UnisonFile.Env (Env (..)) import Unison.UnisonFile.Error (Error (DupDataAndAbility, UnknownType)) -import Unison.UnisonFile.Type (TypecheckedUnisonFile (TypecheckedUnisonFileId), UnisonFile (UnisonFileId)) -import Unison.Util.List qualified as List +import Unison.UnisonFile.Type (TypecheckedUnisonFile, UnisonFile) import Unison.Util.Relation qualified as Relation import Unison.Var (Var) -import Unison.Var qualified as Var import Unison.WatchKind qualified as WK toNames :: (Var v) => UnisonFile v a -> Names @@ -34,9 +34,6 @@ toNames uf = datas <> effects datas = foldMap (DD.Names.dataDeclToNames' Name.unsafeParseVar) (Map.toList (UF.dataDeclarationsId uf)) effects = foldMap (DD.Names.effectDeclToNames' Name.unsafeParseVar) (Map.toList (UF.effectDeclarationsId uf)) -addNamesFromUnisonFile :: (Var v) => UnisonFile v a -> Names -> Names -addNamesFromUnisonFile unisonFile names = Names.shadowing (toNames unisonFile) names - typecheckedToNames :: (Var v) => TypecheckedUnisonFile v a -> Names typecheckedToNames uf = Names (terms <> ctors) types where @@ -64,58 +61,6 @@ typecheckedToNames uf = Names (terms <> ctors) types addNamesFromTypeCheckedUnisonFile :: (Var v) => TypecheckedUnisonFile v a -> Names -> Names addNamesFromTypeCheckedUnisonFile unisonFile names = Names.shadowing (typecheckedToNames unisonFile) names -typecheckedUnisonFile0 :: (Ord v) => TypecheckedUnisonFile v a -typecheckedUnisonFile0 = TypecheckedUnisonFileId Map.empty Map.empty mempty mempty mempty - --- Substitutes free type and term variables occurring in the terms of this --- `UnisonFile` using `externalNames`. --- --- Hash-qualified names are substituted during parsing, but non-HQ names are --- substituted at the end of parsing, since they can be locally bound. Example, in --- `x -> x + math.sqrt 2`, we don't know if `math.sqrt` is locally bound until --- we are done parsing, whereas `math.sqrt#abc` can be resolved immediately --- as it can't refer to a local definition. -bindNames :: - (Var v) => - Names -> - UnisonFile v a -> - Names.ResolutionResult v a (UnisonFile v a) -bindNames names (UnisonFileId d e ts ws) = do - -- todo: consider having some kind of binding structure for terms & watches - -- so that you don't weirdly have free vars to tiptoe around. - -- The free vars should just be the things that need to be bound externally. - let termVarsSet = Map.keysSet ts <> Set.fromList (Map.elems ws >>= map (view _1)) - -- todo: can we clean up this lambda using something like `second` - ts' <- traverse (\(a, t) -> (a,) <$> Term.bindNames Name.unsafeParseVar termVarsSet names t) ts - ws' <- traverse (traverse (\(v, a, t) -> (v,a,) <$> Term.bindNames Name.unsafeParseVar termVarsSet names t)) ws - pure $ UnisonFileId d e ts' ws' - --- | Given the set of fully-qualified variable names, this computes --- a Map from unique suffixes to the fully qualified name. --- --- Example, given [foo.bar, qux.bar, baz.quaffle], this returns: --- --- Map [ foo.bar -> foo.bar --- , qux.bar -> qux.bar --- , baz.quaffle -> baz.quaffle --- , quaffle -> baz.quaffle --- ] --- --- This is used to replace variable references with their canonical --- fully qualified variables. --- --- It's used below in `environmentFor` and also during the term resolution --- process. -variableCanonicalizer :: forall v. (Var v) => [v] -> Map v v -variableCanonicalizer vs = - done $ List.multimap do - v <- vs - let n = Name.unsafeParseVar v - suffix <- Name.suffixes n - pure (Var.named (Name.toText suffix), v) - where - done xs = Map.fromList [(k, v) | (k, nubOrd -> [v]) <- Map.toList xs] <> Map.fromList [(v, v) | v <- vs] - -- This function computes hashes for data and effect declarations, and -- also returns a function for resolving strings to (Reference, ConstructorId) -- for parsing of pattern matching @@ -128,7 +73,7 @@ environmentFor :: Names -> Map v (DataDeclaration v a) -> Map v (EffectDeclaration v a) -> - Names.ResolutionResult v a (Either [Error v a] (Env v a)) + Names.ResolutionResult a (Either [Error v a] (Env v a)) environmentFor names dataDecls0 effectDecls0 = do let locallyBoundTypes = Map.keysSet dataDecls0 <> Map.keysSet effectDecls0 diff --git a/parser-typechecker/tests/Unison/Test/Syntax/FileParser.hs b/parser-typechecker/tests/Unison/Test/Syntax/FileParser.hs index f436e5efe3..7896d75fd9 100644 --- a/parser-typechecker/tests/Unison/Test/Syntax/FileParser.hs +++ b/parser-typechecker/tests/Unison/Test/Syntax/FileParser.hs @@ -60,9 +60,7 @@ test = emptyWatchTest, signatureNeedsAccompanyingBodyTest, emptyBlockTest, - expectedBlockOpenTest, - unknownDataConstructorTest, - unknownAbilityConstructorTest + expectedBlockOpenTest ] expectFileParseFailure :: String -> (P.Error Symbol -> Test ()) -> Test () @@ -117,26 +115,6 @@ expectedBlockOpenTest = P.ExpectedBlockOpen _ _ -> ok _ -> crash "Error wasn't ExpectedBlockOpen" -unknownDataConstructorTest :: Test () -unknownDataConstructorTest = - scope "unknownDataConstructorTest" $ - expectFileParseFailure "m a = match a with A -> 1" expectation - where - expectation :: (Var e) => P.Error e -> Test () - expectation e = case e of - P.UnknownDataConstructor _ _ -> ok - _ -> crash "Error wasn't UnknownDataConstructor" - -unknownAbilityConstructorTest :: Test () -unknownAbilityConstructorTest = - scope "unknownAbilityConstructorTest" $ - expectFileParseFailure "f e = match e with {E t -> u} -> 1" expectation - where - expectation :: (Var e) => P.Error e -> Test () - expectation e = case e of - P.UnknownAbilityConstructor _ _ -> ok - _ -> crash "Error wasn't UnknownAbilityConstructor" - parses :: String -> Test () parses s = scope s $ do let p :: UnisonFile Symbol P.Ann diff --git a/parser-typechecker/unison-parser-typechecker.cabal b/parser-typechecker/unison-parser-typechecker.cabal index b97cc70bb1..edc3182a5e 100644 --- a/parser-typechecker/unison-parser-typechecker.cabal +++ b/parser-typechecker/unison-parser-typechecker.cabal @@ -242,7 +242,6 @@ library , cereal , clock , concurrent-output - , configurator , containers >=0.6.3 , crypton-x509 , crypton-x509-store @@ -437,7 +436,6 @@ test-suite parser-typechecker-tests , clock , code-page , concurrent-output - , configurator , containers >=0.6.3 , crypton-x509 , crypton-x509-store diff --git a/stack.yaml b/stack.yaml index 19bccd7774..1eb80fdd2c 100644 --- a/stack.yaml +++ b/stack.yaml @@ -51,9 +51,6 @@ packages: resolver: lts-22.26 extra-deps: - # broken version in snapshot - - github: unisonweb/configurator - commit: e47e9e9fe1f576f8c835183b9def52d73c01327a # This custom Haskeline alters ANSI rendering on Windows. # If changing the haskeline dependency, please ensure color renders properly in a # Windows terminal. diff --git a/stack.yaml.lock b/stack.yaml.lock index 61c24795ea..316b017f48 100644 --- a/stack.yaml.lock +++ b/stack.yaml.lock @@ -4,17 +4,6 @@ # https://docs.haskellstack.org/en/stable/lock_files packages: -- completed: - name: configurator - pantry-tree: - sha256: 90547cd983fd15ebdc803e057d3ef8735fe93a75e29a00f8a74eadc13ee0f6e9 - size: 955 - sha256: d4fd87fb7bfc5d8e9fbc3e4ee7302c6b1500cdc00fdb9b659d0f4849b6ebe2d5 - size: 15989 - url: https://github.com/unisonweb/configurator/archive/e47e9e9fe1f576f8c835183b9def52d73c01327a.tar.gz - version: 0.3.0.0 - original: - url: https://github.com/unisonweb/configurator/archive/e47e9e9fe1f576f8c835183b9def52d73c01327a.tar.gz - completed: name: haskeline pantry-tree: diff --git a/unison-cli/package.yaml b/unison-cli/package.yaml index 23b18fa9d9..ac5c0053be 100644 --- a/unison-cli/package.yaml +++ b/unison-cli/package.yaml @@ -23,7 +23,6 @@ dependencies: - co-log-core - code-page - concurrent-output - - configurator - containers >= 0.6.3 - cryptonite - directory diff --git a/unison-cli/src/Unison/Cli/Monad.hs b/unison-cli/src/Unison/Cli/Monad.hs index f712907fab..500a015a9a 100644 --- a/unison-cli/src/Unison/Cli/Monad.hs +++ b/unison-cli/src/Unison/Cli/Monad.hs @@ -62,7 +62,6 @@ import Control.Lens import Control.Monad.Reader (MonadReader (..)) import Control.Monad.State.Strict (MonadState) import Control.Monad.State.Strict qualified as State -import Data.Configurator.Types qualified as Configurator import Data.List.NonEmpty qualified as List (NonEmpty) import Data.List.NonEmpty qualified as List.NonEmpty import Data.List.NonEmpty qualified as NonEmpty @@ -160,7 +159,6 @@ type SourceName = Text data Env = Env { authHTTPClient :: AuthenticatedHttpClient, codebase :: Codebase IO Symbol Ann, - config :: Configurator.Config, credentialManager :: CredentialManager, -- | Generate a unique name. generateUniqueName :: IO Parser.UniqueName, diff --git a/unison-cli/src/Unison/Cli/MonadUtils.hs b/unison-cli/src/Unison/Cli/MonadUtils.hs index 8ea64f0694..4546be1e84 100644 --- a/unison-cli/src/Unison/Cli/MonadUtils.hs +++ b/unison-cli/src/Unison/Cli/MonadUtils.hs @@ -1,10 +1,7 @@ -- | This module contains miscellaneous helper utils for rote actions in the Cli monad, like resolving a relative path -- to an absolute path, per the current path. module Unison.Cli.MonadUtils - ( -- * @.unisonConfig@ things - getConfig, - - -- * Paths + ( -- * Paths getCurrentPath, getCurrentProjectName, getCurrentProjectBranchName, @@ -88,8 +85,6 @@ where import Control.Lens import Control.Monad.Reader (ask) import Control.Monad.State -import Data.Configurator qualified as Configurator -import Data.Configurator.Types qualified as Configurator import Data.Foldable import Data.Set qualified as Set import U.Codebase.Branch qualified as V2 (Branch) @@ -138,15 +133,6 @@ import Unison.UnisonFile.Names qualified as UFN import Unison.Util.Set qualified as Set import Unison.Var qualified as Var ------------------------------------------------------------------------------------------------------------------------- --- .unisonConfig things - --- | Lookup a config value by key. -getConfig :: (Configurator.Configured a) => Text -> Cli (Maybe a) -getConfig key = do - Cli.Env {config} <- ask - liftIO (Configurator.lookup config key) - ------------------------------------------------------------------------------------------------------------------------ -- Getting paths, path resolution, etc. diff --git a/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs b/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs index 7e00fe534c..068231d077 100644 --- a/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs +++ b/unison-cli/src/Unison/Codebase/Editor/HandleInput.hs @@ -1672,7 +1672,8 @@ parseType input src = do Parser.ParsingEnv { uniqueNames = mempty, uniqueTypeGuid = \_ -> pure Nothing, - names + names, + maybeNamespace = Nothing } typ <- Parsers.parseType (Text.unpack (fst lexed)) parsingEnv & onLeftM \err -> diff --git a/unison-cli/src/Unison/Codebase/Editor/HandleInput/Load.hs b/unison-cli/src/Unison/Codebase/Editor/HandleInput/Load.hs index ce5e1aa993..3694354d76 100644 --- a/unison-cli/src/Unison/Codebase/Editor/HandleInput/Load.hs +++ b/unison-cli/src/Unison/Codebase/Editor/HandleInput/Load.hs @@ -10,6 +10,7 @@ import Control.Lens ((.=)) import Control.Monad.Reader (ask) import Control.Monad.State.Strict qualified as State import Data.Map.Strict qualified as Map +import Data.Set qualified as Set import Data.Text qualified as Text import System.Environment (withArgs) import Unison.Cli.Monad (Cli) @@ -26,16 +27,20 @@ import Unison.Codebase.Editor.Slurp qualified as Slurp import Unison.Codebase.Runtime qualified as Runtime import Unison.FileParsers qualified as FileParsers import Unison.Names (Names) +import Unison.NamesWithHistory qualified as Names (shadowing) import Unison.Parser.Ann (Ann) import Unison.Parser.Ann qualified as Ann import Unison.Parsers qualified as Parsers import Unison.Prelude import Unison.PrettyPrintEnv qualified as PPE +import Unison.PrettyPrintEnv.Names qualified as PPE import Unison.PrettyPrintEnvDecl qualified as PPE import Unison.PrettyPrintEnvDecl qualified as PPED +import Unison.PrettyPrintEnvDecl.Names qualified as PPED import Unison.Reference qualified as Reference import Unison.Result qualified as Result import Unison.Symbol (Symbol) +import Unison.Syntax.Name qualified as Name import Unison.Syntax.Parser qualified as Parser import Unison.Term (Term) import Unison.Term qualified as Term @@ -43,6 +48,7 @@ import Unison.UnisonFile (TypecheckedUnisonFile) import Unison.UnisonFile qualified as UF import Unison.UnisonFile.Names qualified as UF import Unison.Util.Timing qualified as Timing +import Unison.Var qualified as Var import Unison.WatchKind qualified as WK handleLoad :: Maybe FilePath -> Cli () @@ -94,7 +100,8 @@ loadUnisonFile sourceName text = do Parser.ParsingEnv { uniqueNames = uniqueName, uniqueTypeGuid = Cli.loadUniqueTypeGuid pp, - names + names, + maybeNamespace = Nothing } unisonFile <- Cli.runTransaction (Parsers.parseFile (Text.unpack sourceName) (Text.unpack text) parsingEnv) @@ -106,8 +113,29 @@ loadUnisonFile sourceName text = do computeTypecheckingEnvironment (FileParsers.ShouldUseTndr'Yes parsingEnv) codebase [] unisonFile let Result.Result notes maybeTypecheckedUnisonFile = FileParsers.synthesizeFile typecheckingEnv unisonFile maybeTypecheckedUnisonFile & onNothing do - let namesWithFileDefinitions = UF.addNamesFromUnisonFile unisonFile names - pped <- Cli.prettyPrintEnvDeclFromNames namesWithFileDefinitions + let pped = + let ns = + names + -- Shadow just the type decl and constructor names (because the unison file didn't typecheck so we + -- don't have term `Names`) + & Names.shadowing (UF.toNames unisonFile) + in PPED.makePPED + (PPE.hqNamer 10 ns) + ( PPE.suffixifyByHashWithUnhashedTermsInScope + ( Set.union + (Set.map Name.unsafeParseVar (Map.keysSet (UF.terms unisonFile))) + ( foldMap + ( foldMap \case + (v, _, _) -> + case Var.typeOf v of + Var.User _ -> Set.singleton (Name.unsafeParseVar v) + _ -> Set.empty + ) + (UF.watches unisonFile) + ) + ) + ns + ) let suffixifiedPPE = PPED.suffixifiedPPE pped let tes = [err | Result.TypeError err <- toList notes] cbs = diff --git a/unison-cli/src/Unison/Codebase/Editor/Output.hs b/unison-cli/src/Unison/Codebase/Editor/Output.hs index 1750c5f3a0..d51bcd4b89 100644 --- a/unison-cli/src/Unison/Codebase/Editor/Output.hs +++ b/unison-cli/src/Unison/Codebase/Editor/Output.hs @@ -218,7 +218,7 @@ data Output | NoExactTypeMatches | TypeAlreadyExists Path.Split' (Set Reference) | TypeParseError String (Parser.Err Symbol) - | ParseResolutionFailures String [Names.ResolutionFailure Symbol Ann] + | ParseResolutionFailures String [Names.ResolutionFailure Ann] | TypeHasFreeVars (Type Symbol Ann) | TermAlreadyExists Path.Split' (Set Referent) | LabeledReferenceAmbiguous Int (HQ.HashQualified Name) (Set LabeledDependency) diff --git a/unison-cli/src/Unison/Codebase/Transcript/Runner.hs b/unison-cli/src/Unison/Codebase/Transcript/Runner.hs index 6e084a2eba..7ba207298e 100644 --- a/unison-cli/src/Unison/Codebase/Transcript/Runner.hs +++ b/unison-cli/src/Unison/Codebase/Transcript/Runner.hs @@ -13,8 +13,6 @@ import Crypto.Random qualified as Random import Data.Aeson qualified as Aeson import Data.Aeson.Encode.Pretty qualified as Aeson import Data.ByteString.Lazy.Char8 qualified as BL -import Data.Configurator qualified as Configurator -import Data.Configurator.Types (Config) import Data.IORef import Data.List (isSubsequenceOf) import Data.List.NonEmpty qualified as NonEmpty @@ -24,9 +22,7 @@ import Data.These (These (..)) import Data.UUID.V4 qualified as UUID import Network.HTTP.Client qualified as HTTP import System.Environment (lookupEnv) -import System.Exit (die) import System.IO qualified as IO -import System.IO.Error (catchIOError) import Text.Megaparsec qualified as P import U.Codebase.Sqlite.DbId qualified as Db import U.Codebase.Sqlite.Project (Project (..)) @@ -96,16 +92,15 @@ withRunner :: Verbosity -> UCMVersion -> FilePath -> - Maybe FilePath -> (Runner -> m r) -> m r -withRunner isTest verbosity ucmVersion nrtp configFile action = do - withRuntimes nrtp \runtime sbRuntime nRuntime -> withConfig \config -> do +withRunner isTest verbosity ucmVersion nrtp action = do + withRuntimes nrtp \runtime sbRuntime nRuntime -> do action \transcriptName transcriptSrc (codebaseDir, codebase) -> do Server.startServer (Backend.BackendEnv {Backend.useNamesIndex = False}) Server.defaultCodebaseServerOpts runtime codebase \baseUrl -> do let parsed = Transcript.stanzas transcriptName transcriptSrc result <- for parsed \stanzas -> do - liftIO $ run isTest verbosity codebaseDir stanzas codebase runtime sbRuntime nRuntime config ucmVersion (tShow baseUrl) + liftIO $ run isTest verbosity codebaseDir stanzas codebase runtime sbRuntime nRuntime ucmVersion (tShow baseUrl) pure . join $ first ParseError result where withRuntimes :: @@ -115,19 +110,6 @@ withRunner isTest verbosity ucmVersion nrtp configFile action = do RTI.withRuntime True RTI.Persistent ucmVersion \sbRuntime -> do action runtime sbRuntime =<< liftIO (RTI.startNativeRuntime ucmVersion nrtp) - withConfig :: forall a. ((Maybe Config -> m a) -> m a) - withConfig action = do - case configFile of - Nothing -> action Nothing - Just configFilePath -> do - let loadConfig = liftIO do - catchIOError - (watchConfig configFilePath) - \_ -> die "Your .unisonConfig could not be loaded. Check that it's correct!" - UnliftIO.bracket - loadConfig - (\(_config, cancelConfig) -> liftIO cancelConfig) - (\(config, _cancelConfig) -> action (Just config)) run :: -- | Whether to treat this transcript run as a transcript test, which will try to make output deterministic @@ -139,11 +121,10 @@ run :: Runtime.Runtime Symbol -> Runtime.Runtime Symbol -> Runtime.Runtime Symbol -> - Maybe Config -> UCMVersion -> Text -> IO (Either Error Text) -run isTest verbosity dir stanzas codebase runtime sbRuntime nRuntime config ucmVersion baseURL = UnliftIO.try do +run isTest verbosity dir stanzas codebase runtime sbRuntime nRuntime ucmVersion baseURL = UnliftIO.try do httpManager <- HTTP.newManager HTTP.defaultManagerSettings (initialPP, emptyCausalHashId) <- Codebase.runTransaction codebase do (_, emptyCausalHashId) <- Codebase.emptyCausalHash @@ -427,7 +408,6 @@ run isTest verbosity dir stanzas codebase runtime sbRuntime nRuntime config ucmV Cli.Env { authHTTPClient = authenticatedHTTPClient, codebase, - config = fromMaybe Configurator.empty config, credentialManager = credMan, generateUniqueName = do i <- atomicModifyIORef' seedRef \i -> let !i' = i + 1 in (i', i) diff --git a/unison-cli/src/Unison/CommandLine.hs b/unison-cli/src/Unison/CommandLine.hs index 168e264894..99ac5799d9 100644 --- a/unison-cli/src/Unison/CommandLine.hs +++ b/unison-cli/src/Unison/CommandLine.hs @@ -6,7 +6,6 @@ module Unison.CommandLine ( allow, parseInput, prompt, - watchConfig, watchFileSystem, ) where @@ -15,9 +14,6 @@ import Control.Concurrent (forkIO, killThread) import Control.Lens hiding (aside) import Control.Monad.Except import Control.Monad.Trans.Except -import Data.Configurator (autoConfig, autoReload) -import Data.Configurator qualified as Config -import Data.Configurator.Types (Config, Worth (..)) import Data.List (isPrefixOf, isSuffixOf) import Data.Map qualified as Map import Data.Semialign qualified as Align @@ -50,23 +46,12 @@ import Unison.Util.TQueue qualified as Q import UnliftIO.STM import Prelude hiding (readFile, writeFile) -disableWatchConfig :: Bool -disableWatchConfig = False - allow :: FilePath -> Bool allow p = -- ignore Emacs .# prefixed files, see https://github.com/unisonweb/unison/issues/457 not (".#" `isPrefixOf` takeFileName p) && (isSuffixOf ".u" p || isSuffixOf ".uu" p) -watchConfig :: FilePath -> IO (Config, IO ()) -watchConfig path = - if disableWatchConfig - then pure (Config.empty, pure ()) - else do - (config, t) <- autoReload autoConfig [Optional path] - pure (config, killThread t) - watchFileSystem :: Q.TQueue Event -> FilePath -> IO (IO ()) watchFileSystem q dir = do (cancel, watcher) <- Watch.watchDirectory dir allow diff --git a/unison-cli/src/Unison/CommandLine/Main.hs b/unison-cli/src/Unison/CommandLine/Main.hs index 914581664b..cfefd666c0 100644 --- a/unison-cli/src/Unison/CommandLine/Main.hs +++ b/unison-cli/src/Unison/CommandLine/Main.hs @@ -9,7 +9,6 @@ import Control.Exception (catch, displayException, finally, mask) import Control.Lens ((?~)) import Control.Lens.Lens import Crypto.Random qualified as Random -import Data.Configurator.Types (Config) import Data.IORef import Data.List.NonEmpty qualified as NEL import Data.List.NonEmpty qualified as NonEmpty @@ -124,7 +123,6 @@ main :: FilePath -> Welcome.Welcome -> PP.ProjectPathIds -> - Config -> [Either Event Input] -> Runtime.Runtime Symbol -> Runtime.Runtime Symbol -> @@ -135,7 +133,7 @@ main :: (PP.ProjectPathIds -> IO ()) -> ShouldWatchFiles -> IO () -main dir welcome ppIds config initialInputs runtime sbRuntime nRuntime codebase serverBaseUrl ucmVersion lspCheckForChanges shouldWatchFiles = Ki.scoped \scope -> do +main dir welcome ppIds initialInputs runtime sbRuntime nRuntime codebase serverBaseUrl ucmVersion lspCheckForChanges shouldWatchFiles = Ki.scoped \scope -> do _ <- Ki.fork scope do -- Pre-load the project root in the background so it'll be ready when a command needs it. projectRoot <- Codebase.expectProjectBranchRoot codebase ppIds.project ppIds.branch @@ -221,7 +219,6 @@ main dir welcome ppIds config initialInputs runtime sbRuntime nRuntime codebase Cli.Env { authHTTPClient, codebase, - config, credentialManager, loadSource = loadSourceFile, writeSource = writeSourceFile, diff --git a/unison-cli/src/Unison/LSP/FileAnalysis.hs b/unison-cli/src/Unison/LSP/FileAnalysis.hs index bec9f8bf9f..5f647be8d4 100644 --- a/unison-cli/src/Unison/LSP/FileAnalysis.hs +++ b/unison-cli/src/Unison/LSP/FileAnalysis.hs @@ -92,7 +92,8 @@ checkFile doc = runMaybeT do Parser.ParsingEnv { uniqueNames = uniqueName, uniqueTypeGuid = Cli.loadUniqueTypeGuid pp, - names = parseNames + names = parseNames, + maybeNamespace = Nothing } (notes, parsedFile, typecheckedFile) <- do liftIO do diff --git a/unison-cli/src/Unison/Main.hs b/unison-cli/src/Unison/Main.hs index 990f11354f..498f2b6218 100644 --- a/unison-cli/src/Unison/Main.hs +++ b/unison-cli/src/Unison/Main.hs @@ -27,7 +27,6 @@ import Compat (defaultInterruptHandler, withInterruptHandler) import Control.Concurrent (newEmptyMVar, runInUnboundThread, takeMVar) import Control.Exception (displayException, evaluate) import Data.ByteString.Lazy qualified as BL -import Data.Configurator.Types (Config) import Data.Either.Validation (Validation (..)) import Data.List.NonEmpty (NonEmpty) import Data.Text qualified as Text @@ -57,7 +56,6 @@ import System.FilePath ) import System.IO (stderr) import System.IO.CodePage (withCP65001) -import System.IO.Error (catchIOError) import System.IO.Temp qualified as Temp import System.Path qualified as Path import Text.Megaparsec qualified as MP @@ -76,7 +74,6 @@ import Unison.Codebase.Runtime qualified as Rt import Unison.Codebase.SqliteCodebase qualified as SC import Unison.Codebase.Transcript.Runner qualified as Transcript import Unison.Codebase.Verbosity qualified as Verbosity -import Unison.CommandLine (watchConfig) import Unison.CommandLine.Helpers (plural') import Unison.CommandLine.Main qualified as CommandLine import Unison.CommandLine.Types qualified as CommandLine @@ -96,7 +93,6 @@ import Unison.Symbol (Symbol) import Unison.Util.Pretty qualified as P import Unison.Version (Version) import Unison.Version qualified as Version -import UnliftIO qualified import UnliftIO.Directory (getHomeDirectory) type Runtimes = @@ -143,220 +139,216 @@ main version = do (renderUsageInfo, globalOptions, command) <- parseCLIArgs progName (Text.unpack (Version.gitDescribeWithDate version)) nrtp <- fixNativeRuntimePath (nativeRuntimePath globalOptions) let GlobalOptions {codebasePathOption = mCodePathOption, exitOption, lspFormattingConfig} = globalOptions - withConfig mCodePathOption \config -> do - currentDir <- getCurrentDirectory - case command of - PrintVersion -> - Text.putStrLn $ Text.pack progName <> " version: " <> Version.gitDescribeWithDate version - Init -> do - exitError - ( P.lines - [ "The Init command has been removed", - P.newline, - P.wrap "Use --codebase-create to create a codebase at a specified location and open it:", - P.indentN 2 (P.hiBlue "$ ucm --codebase-create myNewCodebase"), - "Running UCM without the --codebase-create flag: ", - P.indentN 2 (P.hiBlue "$ ucm"), - P.wrap ("will " <> P.bold "always" <> " create a codebase in your home directory if one does not already exist.") + currentDir <- getCurrentDirectory + case command of + PrintVersion -> + Text.putStrLn $ Text.pack progName <> " version: " <> Version.gitDescribeWithDate version + Init -> do + exitError + ( P.lines + [ "The Init command has been removed", + P.newline, + P.wrap "Use --codebase-create to create a codebase at a specified location and open it:", + P.indentN 2 (P.hiBlue "$ ucm --codebase-create myNewCodebase"), + "Running UCM without the --codebase-create flag: ", + P.indentN 2 (P.hiBlue "$ ucm"), + P.wrap ("will " <> P.bold "always" <> " create a codebase in your home directory if one does not already exist.") + ] + ) + Run (RunFromSymbol mainName) args -> do + getCodebaseOrExit mCodePathOption (SC.MigrateAutomatically SC.Backup SC.Vacuum) \(_, _, theCodebase) -> do + RTI.withRuntime False RTI.OneOff (Version.gitDescribeWithDate version) \runtime -> do + withArgs args (execute theCodebase runtime mainName) >>= \case + Left err -> exitError err + Right () -> pure () + Run (RunFromFile file mainName) args + | not (isDotU file) -> exitError "Files must have a .u extension." + | otherwise -> do + e <- safeReadUtf8 file + case e of + Left _ -> exitError "I couldn't find that file or it is for some reason unreadable." + Right contents -> do + getCodebaseOrExit mCodePathOption (SC.MigrateAutomatically SC.Backup SC.Vacuum) \(initRes, _, theCodebase) -> do + withRuntimes nrtp RTI.OneOff \(rt, sbrt, nrt) -> do + let fileEvent = Input.UnisonFileChanged (Text.pack file) contents + let noOpCheckForChanges _ = pure () + let serverUrl = Nothing + startProjectPath <- Codebase.runTransaction theCodebase Codebase.expectCurrentProjectPath + launch + version + currentDir + rt + sbrt + nrt + theCodebase + [Left fileEvent, Right $ Input.ExecuteI mainName args, Right Input.QuitI] + serverUrl + (PP.toIds startProjectPath) + initRes + noOpCheckForChanges + CommandLine.ShouldNotWatchFiles + Run (RunFromPipe mainName) args -> do + e <- safeReadUtf8StdIn + case e of + Left _ -> exitError "I had trouble reading this input." + Right contents -> do + getCodebaseOrExit mCodePathOption (SC.MigrateAutomatically SC.Backup SC.Vacuum) \(initRes, _, theCodebase) -> do + withRuntimes nrtp RTI.OneOff \(rt, sbrt, nrt) -> do + let fileEvent = Input.UnisonFileChanged (Text.pack "") contents + let noOpCheckForChanges _ = pure () + let serverUrl = Nothing + startProjectPath <- Codebase.runTransaction theCodebase Codebase.expectCurrentProjectPath + launch + version + currentDir + rt + sbrt + nrt + theCodebase + [Left fileEvent, Right $ Input.ExecuteI mainName args, Right Input.QuitI] + serverUrl + (PP.toIds startProjectPath) + initRes + noOpCheckForChanges + CommandLine.ShouldNotWatchFiles + Run (RunCompiled file) args -> + BL.readFile file >>= \bs -> + try (evaluate $ RTI.decodeStandalone bs) >>= \case + Left (PE _cs err) -> do + exitError . P.lines $ + [ P.wrap . P.text $ + "I was unable to parse this file as a compiled\ + \ program. The parser generated the following error:", + "", + P.indentN 2 $ err ] - ) - Run (RunFromSymbol mainName) args -> do - getCodebaseOrExit mCodePathOption (SC.MigrateAutomatically SC.Backup SC.Vacuum) \(_, _, theCodebase) -> do - RTI.withRuntime False RTI.OneOff (Version.gitDescribeWithDate version) \runtime -> do - withArgs args (execute theCodebase runtime mainName) >>= \case - Left err -> exitError err - Right () -> pure () - Run (RunFromFile file mainName) args - | not (isDotU file) -> exitError "Files must have a .u extension." - | otherwise -> do - e <- safeReadUtf8 file - case e of - Left _ -> exitError "I couldn't find that file or it is for some reason unreadable." - Right contents -> do - getCodebaseOrExit mCodePathOption (SC.MigrateAutomatically SC.Backup SC.Vacuum) \(initRes, _, theCodebase) -> do - withRuntimes nrtp RTI.OneOff \(rt, sbrt, nrt) -> do - let fileEvent = Input.UnisonFileChanged (Text.pack file) contents - let noOpCheckForChanges _ = pure () - let serverUrl = Nothing - startProjectPath <- Codebase.runTransaction theCodebase Codebase.expectCurrentProjectPath + Right (Left err) -> + exitError . P.lines $ + [ P.wrap . P.text $ + "I was unable to parse this file as a compiled\ + \ program. The parser generated the following error:", + "", + P.indentN 2 . P.wrap $ P.string err + ] + Left _ -> do + exitError . P.wrap . P.text $ + "I was unable to parse this file as a compiled\ + \ program. The parser generated an unrecognized error." + Right (Right (v, rf, w, sto)) + | not vmatch -> mismatchMsg + | otherwise -> + withArgs args (RTI.runStandalone sto w) >>= \case + Left err -> exitError err + Right () -> pure () + where + vmatch = v == Version.gitDescribeWithDate version + ws s = P.wrap (P.text s) + ifile + | 'c' : 'u' : '.' : rest <- reverse file = reverse rest + | otherwise = file + mismatchMsg = + PT.putPrettyLn . P.lines $ + [ ws + "I can't run this compiled program since \ + \it works with a different version of Unison \ + \than the one you're running.", + "", + "Compiled file version", + P.indentN 4 $ P.text v, + "", + "Your version", + P.indentN 4 $ P.text $ Version.gitDescribeWithDate version, + "", + P.wrap $ + "The program was compiled from hash " + <> (P.text $ "`" <> rf <> "`.") + <> "If you have that hash in your codebase," + <> "you can do:", + "", + P.indentN 4 $ + ".> compile " + <> P.text rf + <> " " + <> P.string ifile, + "", + P.wrap + "to produce a new compiled program \ + \that matches your version of Unison." + ] + Transcript shouldFork shouldSaveCodebase mrtsStatsFp transcriptFiles -> do + let action = runTranscripts version Verbosity.Verbose renderUsageInfo shouldFork shouldSaveCodebase mCodePathOption nrtp transcriptFiles + case mrtsStatsFp of + Nothing -> action + Just fp -> recordRtsStats fp action + Launch isHeadless codebaseServerOpts mayStartingProject shouldWatchFiles -> do + getCodebaseOrExit mCodePathOption (SC.MigrateAfterPrompt SC.Backup SC.Vacuum) \(initRes, _, theCodebase) -> do + withRuntimes nrtp RTI.Persistent \(runtime, sbRuntime, nRuntime) -> do + startingProjectPath <- do + -- If the user didn't provide a starting path on the command line, put them in the most recent + -- path they cd'd to + case mayStartingProject of + Just startingProject -> do + Codebase.runTransaction theCodebase (ProjectUtils.getProjectAndBranchByNames startingProject) >>= \case + Nothing -> do + PT.putPrettyLn $ + P.callout + "❓" + ( P.lines + [ P.indentN 2 "I couldn't find the project branch: " <> P.text (into @Text startingProject) + ] + ) + System.exitFailure + Just pab -> do + pure $ PP.fromProjectAndBranch pab Path.absoluteEmpty + Nothing -> do + Codebase.runTransaction theCodebase Codebase.expectCurrentProjectPath + currentPP <- Codebase.runTransaction theCodebase do + PP.toIds <$> Codebase.expectCurrentProjectPath + changeSignal <- Signal.newSignalIO (Just currentPP) + let lspCheckForChanges pp = Signal.writeSignalIO changeSignal pp + -- Unfortunately, the windows IO manager on GHC 8.* is prone to just hanging forever + -- when waiting for input on handles, so if we listen for LSP connections it will + -- prevent UCM from shutting down properly. Hopefully we can re-enable LSP on + -- Windows when we move to GHC 9.* + -- https://gitlab.haskell.org/ghc/ghc/-/merge_requests/1224 + void . Ki.fork scope $ LSP.spawnLsp lspFormattingConfig theCodebase runtime changeSignal + Server.startServer (Backend.BackendEnv {Backend.useNamesIndex = False}) codebaseServerOpts sbRuntime theCodebase $ \baseUrl -> do + case exitOption of + DoNotExit -> do + case isHeadless of + Headless -> do + PT.putPrettyLn $ + P.lines + [ "I've started the Codebase API server at", + P.text $ Server.urlFor Server.Api baseUrl, + "and the Codebase UI at", + P.text $ Server.urlFor (Server.ProjectBranchUI (ProjectAndBranch (UnsafeProjectName "scratch") (UnsafeProjectBranchName "main")) Path.absoluteEmpty Nothing) baseUrl + ] + PT.putPrettyLn $ + P.string "Running the codebase manager headless with " + <> P.shown GHC.Conc.numCapabilities + <> " " + <> plural' GHC.Conc.numCapabilities "cpu" "cpus" + <> "." + mvar <- newEmptyMVar + takeMVar mvar + WithCLI -> do + PT.putPrettyLn $ P.string "Now starting the Unison Codebase Manager (UCM)..." + launch version currentDir - config - rt - sbrt - nrt + runtime + sbRuntime + nRuntime theCodebase - [Left fileEvent, Right $ Input.ExecuteI mainName args, Right Input.QuitI] - serverUrl - (PP.toIds startProjectPath) + [] + (Just baseUrl) + (PP.toIds startingProjectPath) initRes - noOpCheckForChanges - CommandLine.ShouldNotWatchFiles - Run (RunFromPipe mainName) args -> do - e <- safeReadUtf8StdIn - case e of - Left _ -> exitError "I had trouble reading this input." - Right contents -> do - getCodebaseOrExit mCodePathOption (SC.MigrateAutomatically SC.Backup SC.Vacuum) \(initRes, _, theCodebase) -> do - withRuntimes nrtp RTI.OneOff \(rt, sbrt, nrt) -> do - let fileEvent = Input.UnisonFileChanged (Text.pack "") contents - let noOpCheckForChanges _ = pure () - let serverUrl = Nothing - startProjectPath <- Codebase.runTransaction theCodebase Codebase.expectCurrentProjectPath - launch - version - currentDir - config - rt - sbrt - nrt - theCodebase - [Left fileEvent, Right $ Input.ExecuteI mainName args, Right Input.QuitI] - serverUrl - (PP.toIds startProjectPath) - initRes - noOpCheckForChanges - CommandLine.ShouldNotWatchFiles - Run (RunCompiled file) args -> - BL.readFile file >>= \bs -> - try (evaluate $ RTI.decodeStandalone bs) >>= \case - Left (PE _cs err) -> do - exitError . P.lines $ - [ P.wrap . P.text $ - "I was unable to parse this file as a compiled\ - \ program. The parser generated the following error:", - "", - P.indentN 2 $ err - ] - Right (Left err) -> - exitError . P.lines $ - [ P.wrap . P.text $ - "I was unable to parse this file as a compiled\ - \ program. The parser generated the following error:", - "", - P.indentN 2 . P.wrap $ P.string err - ] - Left _ -> do - exitError . P.wrap . P.text $ - "I was unable to parse this file as a compiled\ - \ program. The parser generated an unrecognized error." - Right (Right (v, rf, w, sto)) - | not vmatch -> mismatchMsg - | otherwise -> - withArgs args (RTI.runStandalone sto w) >>= \case - Left err -> exitError err - Right () -> pure () - where - vmatch = v == Version.gitDescribeWithDate version - ws s = P.wrap (P.text s) - ifile - | 'c' : 'u' : '.' : rest <- reverse file = reverse rest - | otherwise = file - mismatchMsg = - PT.putPrettyLn . P.lines $ - [ ws - "I can't run this compiled program since \ - \it works with a different version of Unison \ - \than the one you're running.", - "", - "Compiled file version", - P.indentN 4 $ P.text v, - "", - "Your version", - P.indentN 4 $ P.text $ Version.gitDescribeWithDate version, - "", - P.wrap $ - "The program was compiled from hash " - <> (P.text $ "`" <> rf <> "`.") - <> "If you have that hash in your codebase," - <> "you can do:", - "", - P.indentN 4 $ - ".> compile " - <> P.text rf - <> " " - <> P.string ifile, - "", - P.wrap - "to produce a new compiled program \ - \that matches your version of Unison." - ] - Transcript shouldFork shouldSaveCodebase mrtsStatsFp transcriptFiles -> do - let action = runTranscripts version Verbosity.Verbose renderUsageInfo shouldFork shouldSaveCodebase mCodePathOption nrtp transcriptFiles - case mrtsStatsFp of - Nothing -> action - Just fp -> recordRtsStats fp action - Launch isHeadless codebaseServerOpts mayStartingProject shouldWatchFiles -> do - getCodebaseOrExit mCodePathOption (SC.MigrateAfterPrompt SC.Backup SC.Vacuum) \(initRes, _, theCodebase) -> do - withRuntimes nrtp RTI.Persistent \(runtime, sbRuntime, nRuntime) -> do - startingProjectPath <- do - -- If the user didn't provide a starting path on the command line, put them in the most recent - -- path they cd'd to - case mayStartingProject of - Just startingProject -> do - Codebase.runTransaction theCodebase (ProjectUtils.getProjectAndBranchByNames startingProject) >>= \case - Nothing -> do - PT.putPrettyLn $ - P.callout - "❓" - ( P.lines - [ P.indentN 2 "I couldn't find the project branch: " <> P.text (into @Text startingProject) - ] - ) - System.exitFailure - Just pab -> do - pure $ PP.fromProjectAndBranch pab Path.absoluteEmpty - Nothing -> do - Codebase.runTransaction theCodebase Codebase.expectCurrentProjectPath - currentPP <- Codebase.runTransaction theCodebase do - PP.toIds <$> Codebase.expectCurrentProjectPath - changeSignal <- Signal.newSignalIO (Just currentPP) - let lspCheckForChanges pp = Signal.writeSignalIO changeSignal pp - -- Unfortunately, the windows IO manager on GHC 8.* is prone to just hanging forever - -- when waiting for input on handles, so if we listen for LSP connections it will - -- prevent UCM from shutting down properly. Hopefully we can re-enable LSP on - -- Windows when we move to GHC 9.* - -- https://gitlab.haskell.org/ghc/ghc/-/merge_requests/1224 - void . Ki.fork scope $ LSP.spawnLsp lspFormattingConfig theCodebase runtime changeSignal - Server.startServer (Backend.BackendEnv {Backend.useNamesIndex = False}) codebaseServerOpts sbRuntime theCodebase $ \baseUrl -> do - case exitOption of - DoNotExit -> do - case isHeadless of - Headless -> do - PT.putPrettyLn $ - P.lines - [ "I've started the Codebase API server at", - P.text $ Server.urlFor Server.Api baseUrl, - "and the Codebase UI at", - P.text $ Server.urlFor (Server.ProjectBranchUI (ProjectAndBranch (UnsafeProjectName "scratch") (UnsafeProjectBranchName "main")) Path.absoluteEmpty Nothing) baseUrl - ] - PT.putPrettyLn $ - P.string "Running the codebase manager headless with " - <> P.shown GHC.Conc.numCapabilities - <> " " - <> plural' GHC.Conc.numCapabilities "cpu" "cpus" - <> "." - mvar <- newEmptyMVar - takeMVar mvar - WithCLI -> do - PT.putPrettyLn $ P.string "Now starting the Unison Codebase Manager (UCM)..." - - launch - version - currentDir - config - runtime - sbRuntime - nRuntime - theCodebase - [] - (Just baseUrl) - (PP.toIds startingProjectPath) - initRes - lspCheckForChanges - shouldWatchFiles - Exit -> do Exit.exitSuccess + lspCheckForChanges + shouldWatchFiles + Exit -> do Exit.exitSuccess where -- (runtime, sandboxed runtime) withRuntimes :: FilePath -> RTI.RuntimeHost -> (Runtimes -> IO a) -> IO a @@ -366,17 +358,6 @@ main version = do action . (runtime,sbRuntime,) -- startNativeRuntime saves the path to `unison-runtime` =<< RTI.startNativeRuntime (Version.gitDescribeWithDate version) nrtp - withConfig :: Maybe CodebasePathOption -> (Config -> IO a) -> IO a - withConfig mCodePathOption action = do - UnliftIO.bracket - ( do - let mcodepath = fmap codebasePathOptionToPath mCodePathOption - configFilePath <- getConfigFilePath mcodepath - catchIOError (watchConfig configFilePath) $ \_ -> - exitError "Your .unisonConfig could not be loaded. Check that it's correct!" - ) - (\(_config, cancel) -> cancel) - (\(config, _cancel) -> action config) -- | Set user agent and configure TLS on global http client. -- Note that the authorized http client is distinct from the global http client. @@ -416,14 +397,12 @@ prepareTranscriptDir verbosity shouldFork mCodePathOption shouldSaveCodebase = d runTranscripts' :: Version -> String -> - Maybe FilePath -> FilePath -> FilePath -> NonEmpty MarkdownFile -> IO Bool -runTranscripts' version progName mcodepath nativeRtp transcriptDir markdownFiles = do +runTranscripts' version progName nativeRtp transcriptDir markdownFiles = do currentDir <- getCurrentDirectory - configFilePath <- getConfigFilePath mcodepath -- We don't need to create a codebase through `getCodebaseOrExit` as we've already done so previously. and <$> getCodebaseOrExit @@ -436,7 +415,6 @@ runTranscripts' version progName mcodepath nativeRtp transcriptDir markdownFiles Verbosity.Verbose (Version.gitDescribeWithDate version) nativeRtp - (Just configFilePath) \runTranscript -> do for markdownFiles $ \(MarkdownFile fileName) -> do transcriptSrc <- readUtf8 fileName @@ -503,7 +481,7 @@ runTranscripts version verbosity renderUsageInfo shouldFork shouldSaveTempCodeba progName <- getProgName transcriptDir <- prepareTranscriptDir verbosity shouldFork mCodePathOption shouldSaveTempCodebase completed <- - runTranscripts' version progName (Just transcriptDir) nativeRtp transcriptDir markdownFiles + runTranscripts' version progName nativeRtp transcriptDir markdownFiles case shouldSaveTempCodebase of DontSaveCodebase -> removeDirectoryRecursive transcriptDir SaveCodebase _ -> @@ -527,7 +505,6 @@ runTranscripts version verbosity renderUsageInfo shouldFork shouldSaveTempCodeba launch :: Version -> FilePath -> - Config -> Rt.Runtime Symbol -> Rt.Runtime Symbol -> Rt.Runtime Symbol -> @@ -539,7 +516,7 @@ launch :: (PP.ProjectPathIds -> IO ()) -> CommandLine.ShouldWatchFiles -> IO () -launch version dir config runtime sbRuntime nRuntime codebase inputs serverBaseUrl startingPath initResult lspCheckForChanges shouldWatchFiles = do +launch version dir runtime sbRuntime nRuntime codebase inputs serverBaseUrl startingPath initResult lspCheckForChanges shouldWatchFiles = do showWelcomeHint <- Codebase.runTransaction codebase Queries.doProjectsExist let isNewCodebase = case initResult of CreatedCodebase -> NewlyCreatedCodebase @@ -550,7 +527,6 @@ launch version dir config runtime sbRuntime nRuntime codebase inputs serverBaseU dir welcome startingPath - config inputs runtime sbRuntime @@ -572,9 +548,6 @@ markdownFile md = case takeExtension md of isDotU :: String -> Bool isDotU file = takeExtension file == ".u" -getConfigFilePath :: Maybe FilePath -> IO FilePath -getConfigFilePath mcodepath = ( ".unisonConfig") <$> Codebase.getCodebaseDir mcodepath - getCodebaseOrExit :: Maybe CodebasePathOption -> SC.MigrationStrategy -> ((InitResult, CodebasePath, Codebase IO Symbol Ann) -> IO r) -> IO r getCodebaseOrExit codebasePathOption migrationStrategy action = do initOptions <- argsToCodebaseInitOptions codebasePathOption diff --git a/unison-cli/tests/Unison/Test/Ucm.hs b/unison-cli/tests/Unison/Test/Ucm.hs index 1a8033c52b..c0d2cb0977 100644 --- a/unison-cli/tests/Unison/Test/Ucm.hs +++ b/unison-cli/tests/Unison/Test/Ucm.hs @@ -67,7 +67,7 @@ runTranscript (Codebase codebasePath fmt) transcript = do let err e = fail $ "Parse error: \n" <> show e cbInit = case fmt of CodebaseFormat2 -> SC.init isTest = True - Transcript.withRunner isTest Verbosity.Silent "Unison.Test.Ucm.runTranscript Invalid Version String" rtp configFile $ + Transcript.withRunner isTest Verbosity.Silent "Unison.Test.Ucm.runTranscript Invalid Version String" rtp $ \runner -> do result <- Codebase.Init.withOpenCodebase cbInit "transcript" codebasePath SC.DoLock SC.DontMigrate \codebase -> do Codebase.runTransaction codebase (Codebase.installUcmDependencies codebase) @@ -77,7 +77,6 @@ runTranscript (Codebase codebasePath fmt) transcript = do pure output either (fail . P.toANSI 80 . P.shown) pure result where - configFile = Nothing -- Note: this needs to be properly configured if these tests ever -- need to do native compiles. But I suspect they won't. rtp = "native-compiler/bin" diff --git a/unison-cli/transcripts/Transcripts.hs b/unison-cli/transcripts/Transcripts.hs index 77220a3061..2b7d7677d0 100644 --- a/unison-cli/transcripts/Transcripts.hs +++ b/unison-cli/transcripts/Transcripts.hs @@ -49,7 +49,7 @@ testBuilder :: testBuilder expectFailure recordFailure runtimePath dir prelude transcript = scope transcript $ do outputs <- io . withTemporaryUcmCodebase SC.init Verbosity.Silent "transcript" SC.DoLock $ \(codebasePath, codebase) -> do let isTest = True - Transcript.withRunner isTest Verbosity.Silent "TODO: pass version here" runtimePath Nothing \runTranscript -> do + Transcript.withRunner isTest Verbosity.Silent "TODO: pass version here" runtimePath \runTranscript -> do for files \filePath -> do transcriptSrc <- readUtf8 filePath out <- silence $ runTranscript filePath transcriptSrc (codebasePath, codebase) diff --git a/unison-cli/unison-cli.cabal b/unison-cli/unison-cli.cabal index 6a3df61e73..a12b033231 100644 --- a/unison-cli/unison-cli.cabal +++ b/unison-cli/unison-cli.cabal @@ -205,7 +205,6 @@ library , co-log-core , code-page , concurrent-output - , configurator , containers >=0.6.3 , cryptonite , directory @@ -347,7 +346,6 @@ executable transcripts , co-log-core , code-page , concurrent-output - , configurator , containers >=0.6.3 , cryptonite , directory @@ -496,7 +494,6 @@ test-suite cli-tests , co-log-core , code-page , concurrent-output - , configurator , containers >=0.6.3 , cryptonite , directory diff --git a/unison-core/src/Unison/DataDeclaration.hs b/unison-core/src/Unison/DataDeclaration.hs index 6090406ae7..5972bd9abe 100644 --- a/unison-core/src/Unison/DataDeclaration.hs +++ b/unison-core/src/Unison/DataDeclaration.hs @@ -211,7 +211,7 @@ bindReferences :: Set v -> Map Name.Name Reference -> DataDeclaration v a -> - Names.ResolutionResult v a (DataDeclaration v a) + Names.ResolutionResult a (DataDeclaration v a) bindReferences unsafeVarToName keepFree names (DataDeclaration m a bound constructors) = do constructors <- for constructors $ \(a, v, ty) -> (a,v,) <$> Type.bindReferences unsafeVarToName keepFree names ty diff --git a/unison-core/src/Unison/DataDeclaration/Names.hs b/unison-core/src/Unison/DataDeclaration/Names.hs index 5aba864f3f..5cc2c297f1 100644 --- a/unison-core/src/Unison/DataDeclaration/Names.hs +++ b/unison-core/src/Unison/DataDeclaration/Names.hs @@ -53,6 +53,6 @@ bindNames :: Set v -> Names -> DataDeclaration v a -> - Names.ResolutionResult v a (DataDeclaration v a) + Names.ResolutionResult a (DataDeclaration v a) bindNames unsafeVarToName nameToVar localNames namespaceNames = traverseOf (#constructors' . traverse . _3) (Type.Names.bindNames unsafeVarToName nameToVar localNames namespaceNames) diff --git a/unison-core/src/Unison/Name.hs b/unison-core/src/Unison/Name.hs index 0bbe9ba4a8..9b8aaa5275 100644 --- a/unison-core/src/Unison/Name.hs +++ b/unison-core/src/Unison/Name.hs @@ -36,6 +36,7 @@ module Unison.Name -- * To organize later commonPrefix, + isBlank, preferShallowLibDepth, searchByRankedSuffix, searchBySuffix, @@ -72,6 +73,7 @@ import Unison.Prelude import Unison.Util.Alphabetical (Alphabetical, compareAlphabetical) import Unison.Util.List qualified as List import Unison.Util.Relation qualified as R +import qualified Data.Text as Text -- | @compareSuffix x y@ compares the suffix of @y@ (in reverse segment order) that is as long as @x@ to @x@ (in reverse -- segment order). @@ -545,6 +547,10 @@ suffixifyByHash fqn rel = refs = R.searchDom (compareSuffix suffix) rel +-- | A `Name` is blank when it is unqualified and begins with a `_` (also implying that it is wordy) +isBlank :: Name -> Bool +isBlank n = isUnqualified n && Text.isPrefixOf "_" (NameSegment.toUnescapedText $ lastSegment n) + -- | Returns the common prefix of two names as segments -- -- Note: the returned segments are NOT reversed. diff --git a/unison-core/src/Unison/Names/ResolutionResult.hs b/unison-core/src/Unison/Names/ResolutionResult.hs index 0359ce57ad..3b7246a35e 100644 --- a/unison-core/src/Unison/Names/ResolutionResult.hs +++ b/unison-core/src/Unison/Names/ResolutionResult.hs @@ -3,7 +3,6 @@ module Unison.Names.ResolutionResult ResolutionFailure (..), ResolutionResult, getAnnotation, - getVar, ) where @@ -12,6 +11,7 @@ import Unison.Names (Names) import Unison.Prelude import Unison.Reference (TypeReference) import Unison.Referent (Referent) +import Unison.HashQualified (HashQualified) data ResolutionError ref = NotFound @@ -25,20 +25,15 @@ data ResolutionError ref Ambiguous Names (Set ref) (Set Name) deriving (Eq, Ord, Show) --- | ResolutionFailure represents the failure to resolve a given variable. -data ResolutionFailure var annotation - = TypeResolutionFailure var annotation (ResolutionError TypeReference) - | TermResolutionFailure var annotation (ResolutionError Referent) +-- | ResolutionFailure represents the failure to resolve a given name. +data ResolutionFailure annotation + = TypeResolutionFailure (HashQualified Name) annotation (ResolutionError TypeReference) + | TermResolutionFailure (HashQualified Name) annotation (ResolutionError Referent) deriving (Eq, Ord, Show, Functor, Foldable, Traversable) -getAnnotation :: ResolutionFailure v a -> a +getAnnotation :: ResolutionFailure a -> a getAnnotation = \case TypeResolutionFailure _ a _ -> a TermResolutionFailure _ a _ -> a -getVar :: ResolutionFailure v a -> v -getVar = \case - TypeResolutionFailure v _ _ -> v - TermResolutionFailure v _ _ -> v - -type ResolutionResult v a r = Either (Seq (ResolutionFailure v a)) r +type ResolutionResult a r = Either (Seq (ResolutionFailure a)) r diff --git a/unison-core/src/Unison/Names/ResolvesTo.hs b/unison-core/src/Unison/Names/ResolvesTo.hs new file mode 100644 index 0000000000..378b4af486 --- /dev/null +++ b/unison-core/src/Unison/Names/ResolvesTo.hs @@ -0,0 +1,21 @@ +module Unison.Names.ResolvesTo + ( ResolvesTo (..), + partitionResolutions, + ) +where + +import Unison.Name (Name) +import Unison.Prelude + +data ResolvesTo ref + = ResolvesToNamespace ref + | ResolvesToLocal Name + deriving stock (Eq, Ord, Show) + +partitionResolutions :: [(v, ResolvesTo ref)] -> ([(v, ref)], [(v, Name)]) +partitionResolutions = + partitionEithers . map f + where + f = \case + (v, ResolvesToNamespace ref) -> Left (v, ref) + (v, ResolvesToLocal name) -> Right (v, name) diff --git a/unison-core/src/Unison/NamesWithHistory.hs b/unison-core/src/Unison/NamesWithHistory.hs index 561fa557f8..d578eddad2 100644 --- a/unison-core/src/Unison/NamesWithHistory.hs +++ b/unison-core/src/Unison/NamesWithHistory.hs @@ -108,7 +108,7 @@ push n0 ns = unionLeft0 n1 ns -- This can be used to shadow names in the codebase with names in a unison file for instance: -- e.g. @shadowing scratchFileNames codebaseNames@ shadowing :: Names -> Names -> Names -shadowing = Names.unionLeft +shadowing = Names.unionLeftName -- Find all types whose name has a suffix matching the provided `HashQualified`, -- returning types with relative names if they exist, and otherwise @@ -236,10 +236,6 @@ termName length r names = hq n = HQ'.take length (HQ'.fromNamedReferent n r) isConflicted n = R.manyDom n (Names.terms names) --- Set HashQualified -> Branch m -> Action' m v Names --- Set HashQualified -> Branch m -> Free (Command m i v) Names --- Set HashQualified -> Branch m -> Command m i v Names --- populate historical names lookupHQPattern :: SearchType -> HQ.HashQualified Name -> diff --git a/unison-core/src/Unison/Term.hs b/unison-core/src/Unison/Term.hs index 6d3ebebf76..6d0acc1cc3 100644 --- a/unison-core/src/Unison/Term.hs +++ b/unison-core/src/Unison/Term.hs @@ -10,6 +10,7 @@ import Control.Monad.Writer.Strict qualified as Writer import Data.Generics.Sum (_Ctor) import Data.List qualified as List import Data.Map qualified as Map +import Data.Sequence qualified as Seq import Data.Sequence qualified as Sequence import Data.Set qualified as Set import Data.Text qualified as Text @@ -27,6 +28,7 @@ import Unison.Name qualified as Name import Unison.Names (Names) import Unison.Names qualified as Names import Unison.Names.ResolutionResult qualified as Names +import Unison.Names.ResolvesTo (ResolvesTo (..), partitionResolutions) import Unison.NamesWithHistory qualified as Names import Unison.Pattern (Pattern) import Unison.Pattern qualified as Pattern @@ -39,6 +41,7 @@ import Unison.Type (Type) import Unison.Type qualified as Type import Unison.Util.Defns (Defns (..), DefnsF) import Unison.Util.List (multimap, validate) +import Unison.Util.Relation qualified as Relation import Unison.Var (Var) import Unison.Var qualified as Var import Unsafe.Coerce (unsafeCoerce) @@ -149,67 +152,59 @@ bindNames :: forall v a. (Var v) => (v -> Name.Name) -> + (Name.Name -> v) -> Set v -> Names -> Term v a -> - Names.ResolutionResult v a (Term v a) -bindNames unsafeVarToName keepFreeTerms ns e = do - let freeTmVars = [(v, a) | (v, a) <- ABT.freeVarOccurrences keepFreeTerms e] - -- !_ = trace "bindNames.free term vars: " () - -- !_ = traceShow $ fst <$> freeTmVars + Names.ResolutionResult a (Term v a) +bindNames unsafeVarToName nameToVar localVars ns term = do + let freeTmVars = ABT.freeVarOccurrences localVars term freeTyVars = - [ (v, a) | (v, as) <- Map.toList (freeTypeVarAnnotations e), a <- as + [ (v, a) | (v, as) <- Map.toList (freeTypeVarAnnotations term), a <- as ] - -- !_ = trace "bindNames.free type vars: " () - -- !_ = traceShow $ fst <$> freeTyVars - okTm :: (v, a) -> Names.ResolutionResult v a (v, Term v a) - okTm (v, a) = case Names.lookupHQTerm Names.IncludeSuffixes (HQ.NameOnly $ unsafeVarToName v) ns of - rs - | Set.size rs == 1 -> - pure (v, fromReferent a $ Set.findMin rs) - | Set.size rs == 0 -> Left (pure (Names.TermResolutionFailure v a Names.NotFound)) - | otherwise -> Left (pure (Names.TermResolutionFailure v a (Names.Ambiguous ns rs Set.empty))) - okTy (v, a) = case Names.lookupHQType Names.IncludeSuffixes (HQ.NameOnly $ unsafeVarToName v) ns of + localNames = map unsafeVarToName (Set.toList localVars) + + okTm :: (v, a) -> Names.ResolutionResult a (Maybe (v, ResolvesTo Referent)) + okTm (v, _) = + let exactNamespaceMatches = Names.lookupHQTerm Names.ExactName (HQ.NameOnly name) ns + suffixNamespaceMatches = Name.searchByRankedSuffix name (Names.terms ns) + localMatches = + Name.searchBySuffix name (Relation.fromList (map (\name -> (name, name)) localNames)) + in case (Set.size exactNamespaceMatches, Set.size suffixNamespaceMatches, Set.size localMatches) of + (1, _, _) -> good (ResolvesToNamespace (Set.findMin exactNamespaceMatches)) + (n, _, _) | n > 1 -> leaveFreeForTdnr + (_, 0, 0) -> + if Name.isBlank name + then leaveFreeForHoleSuggestions + else leaveFreeForTellingUserAboutExpectedType + (_, 1, 0) -> good (ResolvesToNamespace (Set.findMin suffixNamespaceMatches)) + (_, 0, 1) -> good (ResolvesToLocal (Set.findMin localMatches)) + _ -> leaveFreeForTdnr + where + name = unsafeVarToName v + good = Right . Just . (v,) + leaveFreeForHoleSuggestions = Right Nothing + leaveFreeForTdnr = Right Nothing + leaveFreeForTellingUserAboutExpectedType = Right Nothing + + okTy :: (v, a) -> Names.ResolutionResult a (v, Type v a) + okTy (v, a) = case Names.lookupHQType Names.IncludeSuffixes hqName ns of rs | Set.size rs == 1 -> pure (v, Type.ref a $ Set.findMin rs) - | Set.size rs == 0 -> Left (pure (Names.TypeResolutionFailure v a Names.NotFound)) - | otherwise -> Left (pure (Names.TypeResolutionFailure v a (Names.Ambiguous ns rs Set.empty))) - termSubsts <- validate okTm freeTmVars + | Set.size rs == 0 -> Left (Seq.singleton (Names.TypeResolutionFailure hqName a Names.NotFound)) + | otherwise -> Left (Seq.singleton (Names.TypeResolutionFailure hqName a (Names.Ambiguous ns rs Set.empty))) + where + hqName = HQ.NameOnly (unsafeVarToName v) + (namespaceTermResolutions, localTermResolutions) <- + partitionResolutions . catMaybes <$> validate okTm freeTmVars + let termSubsts = + [(v, fromReferent () ref) | (v, ref) <- namespaceTermResolutions] + ++ [(v, var () (nameToVar name)) | (v, name) <- localTermResolutions] typeSubsts <- validate okTy freeTyVars - pure . substTypeVars typeSubsts . ABT.substsInheritAnnotation termSubsts $ e - --- This function replaces free term and type variables with --- hashes found in the provided `Names`, using suffix-based --- lookup. Any terms not found in the `Names` are kept free. -bindSomeNames :: - forall v a. - (Var v) => - (v -> Name.Name) -> - Set v -> - Names -> - Term v a -> - Names.ResolutionResult v a (Term v a) --- bindSomeNames ns e | trace "Term.bindSome" False --- || trace "Names =" False --- || traceShow ns False --- || trace "Free type vars:" False --- || traceShow (freeTypeVars e) False --- || trace "Free term vars:" False --- || traceShow (freeVars e) False --- || traceShow e False --- = undefined -bindSomeNames unsafeVarToName avoid ns e = bindNames unsafeVarToName (avoid <> varsToTDNR) ns e - where - -- `Term.bindNames` takes a set of variables that are not substituted. - -- These should be the variables that will be subject to TDNR, which - -- we compute as the set of variables whose names cannot be found in `ns`. - -- - -- This allows TDNR to disambiguate those names (if multiple definitions - -- share the same suffix) or to report the type expected for that name - -- (if a free variable is being used as a typed hole). - varsToTDNR = Set.filter notFound (freeVars e) - notFound var = - Set.size (Name.searchByRankedSuffix (unsafeVarToName var) (Names.terms ns)) /= 1 + pure $ + term + & ABT.substsInheritAnnotation termSubsts + & substTypeVars typeSubsts -- Prepare a term for type-directed name resolution by replacing -- any remaining free variables with blanks to be resolved by TDNR @@ -599,6 +594,13 @@ pattern BinaryAppsPred' :: (Term2 vt at ap v a, Term2 vt at ap v a -> Bool) pattern BinaryAppsPred' apps lastArg <- (unBinaryAppsPred -> Just (apps, lastArg)) +pattern BinaryAppPred' :: + Term2 vt at ap v a -> + Term2 vt at ap v a -> + Term2 vt at ap v a -> + (Term2 vt at ap v a, Term2 vt at ap v a -> Bool) +pattern BinaryAppPred' f arg1 arg2 <- (unBinaryAppPred -> Just (f, arg1, arg2)) + pattern OverappliedBinaryAppPred' :: Term2 vt at ap v a -> Term2 vt at ap v a -> @@ -1165,12 +1167,23 @@ unBinaryAppsPred :: ], Term2 vt at ap v a ) -unBinaryAppsPred (t, pred) = case unBinaryApp t of - Just (f, x, y) | pred f -> case unBinaryAppsPred (x, pred) of +unBinaryAppsPred (t, pred) = case unBinaryAppPred (t, pred) of + Just (f, x, y) -> case unBinaryAppsPred (x, pred) of Just (as, xLast) -> Just ((xLast, f) : as, y) Nothing -> Just ([(x, f)], y) _ -> Nothing +unBinaryAppPred :: + (Term2 vt at ap v a, Term2 vt at ap v a -> Bool) -> + Maybe + ( Term2 vt at ap v a, + Term2 vt at ap v a, + Term2 vt at ap v a + ) +unBinaryAppPred (t, pred) = case unBinaryApp t of + Just (f, x, y) | pred f -> Just (f, x, y) + _ -> Nothing + unLams' :: Term2 vt at ap v a -> Maybe ([v], Term2 vt at ap v a) unLams' t = unLamsPred' (t, const True) diff --git a/unison-core/src/Unison/Type.hs b/unison-core/src/Unison/Type.hs index d779aa7ce1..a1fd4fec52 100644 --- a/unison-core/src/Unison/Type.hs +++ b/unison-core/src/Unison/Type.hs @@ -8,8 +8,10 @@ import Data.Generics.Sum (_Ctor) import Data.List.Extra (nubOrd) import Data.Map qualified as Map import Data.Monoid (Any (..)) +import Data.Sequence qualified as Seq import Data.Set qualified as Set import Unison.ABT qualified as ABT +import Unison.HashQualified qualified as HQ import Unison.Kind qualified as K import Unison.LabeledDependency qualified as LD import Unison.Name qualified as Name @@ -71,12 +73,14 @@ bindReferences :: Set v -> Map Name.Name TypeReference -> Type v a -> - Names.ResolutionResult v a (Type v a) + Names.ResolutionResult a (Type v a) bindReferences unsafeVarToName keepFree ns t = let fvs = ABT.freeVarOccurrences keepFree t rs = [(v, a, Map.lookup (unsafeVarToName v) ns) | (v, a) <- fvs] ok (v, _a, Just r) = pure (v, r) - ok (v, a, Nothing) = Left (pure (Names.TypeResolutionFailure v a Names.NotFound)) + ok (v, a, Nothing) = + Left $ + Seq.singleton (Names.TypeResolutionFailure (HQ.NameOnly (unsafeVarToName v)) a Names.NotFound) in List.validate ok rs <&> \es -> bindExternal es t newtype Monotype v a = Monotype {getPolytype :: Type v a} deriving (Eq) diff --git a/unison-core/src/Unison/Type/Names.hs b/unison-core/src/Unison/Type/Names.hs index f1afbb0bc5..0043e437a4 100644 --- a/unison-core/src/Unison/Type/Names.hs +++ b/unison-core/src/Unison/Type/Names.hs @@ -11,6 +11,7 @@ import Unison.Name (Name) import Unison.Name qualified as Name import Unison.Names (Names) import Unison.Names.ResolutionResult qualified as Names +import Unison.Names.ResolvesTo (ResolvesTo (..), partitionResolutions) import Unison.NamesWithHistory qualified as Names import Unison.Prelude import Unison.Reference (TypeReference) @@ -20,10 +21,6 @@ import Unison.Util.List qualified as List import Unison.Util.Relation qualified as Relation import Unison.Var (Var) -data ResolvesTo - = ResolvesToNamespace TypeReference - | ResolvesToLocal Name - bindNames :: forall a v. (Var v) => @@ -32,7 +29,7 @@ bindNames :: Set v -> Names -> Type v a -> - Names.ResolutionResult v a (Type v a) + Names.ResolutionResult a (Type v a) bindNames unsafeVarToName nameToVar localVars namespaceNames ty = let -- Identify the unresolved variables in the type: those whose names aren't an *exact* match for some locally-bound -- type. @@ -54,7 +51,7 @@ bindNames unsafeVarToName nameToVar localVars namespaceNames ty = -- -- 1. An exact match in the namespace. -- 2. A suffix match in the namespace. - -- 3. A suffix match in the local names.. + -- 3. A suffix match in the local names. resolvedVars :: [(v, a, (Set TypeReference, Set TypeReference), Set Name)] resolvedVars = map @@ -66,7 +63,7 @@ bindNames unsafeVarToName nameToVar localVars namespaceNames ty = checkAmbiguity :: (v, a, (Set TypeReference, Set TypeReference), Set Name) -> - Either (Seq (Names.ResolutionFailure v a)) (v, ResolvesTo) + Either (Seq (Names.ResolutionFailure a)) (v, ResolvesTo TypeReference) checkAmbiguity (v, a, (exactNamespaceMatches, suffixNamespaceMatches), localMatches) = case (Set.size exactNamespaceMatches, Set.size suffixNamespaceMatches, Set.size localMatches) of (1, _, _) -> good (ResolvesToNamespace (Set.findMin exactNamespaceMatches)) @@ -76,22 +73,10 @@ bindNames unsafeVarToName nameToVar localVars namespaceNames ty = (_, 0, 1) -> good (ResolvesToLocal (Set.findMin localMatches)) _ -> bad (Names.Ambiguous namespaceNames suffixNamespaceMatches localMatches) where - bad = Left . Seq.singleton . Names.TypeResolutionFailure v a + bad = Left . Seq.singleton . Names.TypeResolutionFailure (HQ.NameOnly (unsafeVarToName v)) a good = Right . (v,) in List.validate checkAmbiguity resolvedVars <&> \resolutions -> - let -- Partition the resolutions into external/local - namespaceResolutions :: [(v, TypeReference)] - localResolutions :: [(v, Name)] - (namespaceResolutions, localResolutions) = - resolutions - -- Cast our nice informative ResolvesTo type to an Either, just to use `partitionEithers` - -- Is there a `partitonWith :: (a -> Either b c) -> [a] -> ([b], [c])` somewhere? - & map - ( \case - (v, ResolvesToNamespace ref) -> Left (v, ref) - (v, ResolvesToLocal name) -> Right (v, name) - ) - & partitionEithers + let (namespaceResolutions, localResolutions) = partitionResolutions resolutions in ty -- Apply namespace resolutions (replacing "Foo" with #Foo where "Foo" refers to namespace) & bindExternal namespaceResolutions diff --git a/unison-core/unison-core1.cabal b/unison-core/unison-core1.cabal index f6cfed41d8..146a132d9c 100644 --- a/unison-core/unison-core1.cabal +++ b/unison-core/unison-core1.cabal @@ -43,6 +43,7 @@ library Unison.Name.Internal Unison.Names Unison.Names.ResolutionResult + Unison.Names.ResolvesTo Unison.NamesWithHistory Unison.Pattern Unison.Position diff --git a/unison-hashing-v2/src/Unison/Hashing/V2/DataDeclaration.hs b/unison-hashing-v2/src/Unison/Hashing/V2/DataDeclaration.hs index 7d1d67ce41..3dc7b4eba0 100644 --- a/unison-hashing-v2/src/Unison/Hashing/V2/DataDeclaration.hs +++ b/unison-hashing-v2/src/Unison/Hashing/V2/DataDeclaration.hs @@ -76,7 +76,7 @@ hashDecls :: (Eq v, Var v, Show v) => (v -> Name.Name) -> Map v (DataDeclaration v a) -> - Names.ResolutionResult v a [(v, ReferenceId, DataDeclaration v a)] + Names.ResolutionResult a [(v, ReferenceId, DataDeclaration v a)] hashDecls unsafeVarToName decls = do -- todo: make sure all other external references are resolved before calling this let varToRef = hashDecls0 (void <$> decls) @@ -96,7 +96,7 @@ bindReferences :: Set v -> Map Name.Name Reference -> DataDeclaration v a -> - Names.ResolutionResult v a (DataDeclaration v a) + Names.ResolutionResult a (DataDeclaration v a) bindReferences unsafeVarToName keepFree names (DataDeclaration m a bound constructors) = do constructors <- for constructors $ \(a, v, ty) -> (a,v,) <$> Type.bindReferences unsafeVarToName keepFree names ty diff --git a/unison-hashing-v2/src/Unison/Hashing/V2/Type.hs b/unison-hashing-v2/src/Unison/Hashing/V2/Type.hs index 14a5e0e809..b1397d0e81 100644 --- a/unison-hashing-v2/src/Unison/Hashing/V2/Type.hs +++ b/unison-hashing-v2/src/Unison/Hashing/V2/Type.hs @@ -23,6 +23,7 @@ where import Data.Map qualified as Map import Data.Set qualified as Set import Unison.ABT qualified as ABT +import Unison.HashQualified qualified as HQ import Unison.Hashing.V2.ABT qualified as ABT import Unison.Hashing.V2.Kind qualified as K import Unison.Hashing.V2.Reference (Reference (..), pattern ReferenceDerived) @@ -64,12 +65,12 @@ bindReferences :: Set v -> Map Name.Name Reference -> Type v a -> - Names.ResolutionResult v a (Type v a) + Names.ResolutionResult a (Type v a) bindReferences unsafeVarToName keepFree ns t = let fvs = ABT.freeVarOccurrences keepFree t rs = [(v, a, Map.lookup (unsafeVarToName v) ns) | (v, a) <- fvs] ok (v, _a, Just r) = pure (v, r) - ok (v, a, Nothing) = Left (pure (Names.TypeResolutionFailure v a Names.NotFound)) + ok (v, a, Nothing) = Left (pure (Names.TypeResolutionFailure (HQ.NameOnly (unsafeVarToName v)) a Names.NotFound)) in List.validate ok rs <&> \es -> bindExternal es t -- some smart patterns diff --git a/unison-src/transcripts-round-trip/main.output.md b/unison-src/transcripts-round-trip/main.output.md index def5266331..a2624eaf9d 100644 --- a/unison-src/transcripts-round-trip/main.output.md +++ b/unison-src/transcripts-round-trip/main.output.md @@ -24,7 +24,7 @@ scratch/a1> edit 1-1000 ☝️ - I added 110 definitions to the top of scratch.u + I added 111 definitions to the top of scratch.u You can edit them there, then run `update` to replace the definitions currently in this namespace. @@ -122,11 +122,48 @@ ex3a = a = do qux3 + qux3 () +fixity : '('()) +fixity = + do + use Nat * + + (===) = (==) + f <| x = f x + (<<) f g x = f (g x) + (>>) f g x = g (f x) + id x = x + (do + (%) = Nat.mod + ($) = (+) + c = 1 * (2 + 3) * 4 + d = true && (false || true) + z = true || false && true + e = 1 + 2 >= 3 + 4 + f = 9 % 2 === 0 + g = 0 == 9 % 2 + h = 2 * (10 $ 20) + i1 = 1 * 2 $ (3 * 4) $ 5 + i2 = (1 * 2 $ 3) * 4 $ 5 + oo = (2 * 10 $ 20) * 30 $ 40 + ffffffffffffffffffff x = x + 1 + gg x = x * 2 + j = 10 |> ffffffffffffffffffff |> gg |> gg |> gg |> gg |> gg + k = ffffffffffffffffffff << gg << ffffffffffffffffffff <| 10 + l = 10 |> (ffffffffffffffffffff >> gg >> ffffffffffffffffffff) + zzz = 1 + 2 * 3 < 4 + 5 * 6 && 7 + 8 * 9 > 10 + 11 * 12 + zz = + (1 * 2 + 3 * 3 < 4 + 5 * 6 && 7 + 8 * 9 > 10 + 11 * 12) + === (1 + 3 * 3 < 4 + 5 * 6 && 7 + 8 * 9 > 10 + 11 * 12) + zzzz = + 1 * 2 + 3 * 3 < 4 + 5 * 6 + && 7 + 8 * 9 > 10 + 11 * 12 === 1 + 3 * 3 < 4 + 5 * 6 + && 7 + 8 * 9 > 10 + 11 * 12 + ()) + |> id + fix_1035 : Text fix_1035 = use Text ++ - "aaaaaaaaaaaaaaaaaaaaaa" - ++ "bbbbbbbbbbbbbbbbbbbbbb" + "aaaaaaaaaaaaaaaaaaaaaa" ++ "bbbbbbbbbbbbbbbbbbbbbb" ++ "cccccccccccccccccccccc" ++ "dddddddddddddddddddddd" @@ -590,8 +627,8 @@ softhang22 = softhang2 [0, 1, 2, 3, 4, 5] cases softhang23 : 'Nat softhang23 = do - use Nat + catchAll do + use Nat + x = 1 y = 2 x + y @@ -627,15 +664,7 @@ softhang28 = n -> forkAt 0 - (n - Nat.+ n - Nat.+ n - Nat.+ n - Nat.+ n - Nat.+ n - Nat.+ n - Nat.+ n - Nat.+ n + (n Nat.+ n Nat.+ n Nat.+ n Nat.+ n Nat.+ n Nat.+ n Nat.+ n Nat.+ n Nat.+ n Nat.+ n) @@ -655,18 +684,7 @@ softhang_b x = a = 1 b = 2 softhang - (100 - + 200 - + 300 - + 400 - + 500 - + 600 - + 700 - + 800 - + 900 - + 1000 - + 1100 - + 1200 + (100 + 200 + 300 + 400 + 500 + 600 + 700 + 800 + 900 + 1000 + 1100 + 1200 + 1300 + 1400 + 1500) diff --git a/unison-src/transcripts-round-trip/reparses-with-same-hash.u b/unison-src/transcripts-round-trip/reparses-with-same-hash.u index 5d75eff442..8aac55c727 100644 --- a/unison-src/transcripts-round-trip/reparses-with-same-hash.u +++ b/unison-src/transcripts-round-trip/reparses-with-same-hash.u @@ -594,3 +594,33 @@ fix_4729c = {{ }}) {{ This is a callout with a title }} ``` }} + +fixity = do + (===) = (##Universal.==) + (<|) f x = f x + (<<) f g x = f (g x) + (>>) f g x = g (f x) + id x = x + (do + (%) = Nat.mod + ($) = (Nat.+) + c = 1 * (2 + 3) * 4 + d = true && let false || true + z = true || false && true + e = 1 + 2 >= (3 + 4) + f = 9 % 2 === 0 + g = 0 == (9 % 2) + h = 2 * (10 $ 20) + i1 = 1 * 2 $ (3 * 4) $ 5 + i2 = 1 * 2 $ 3 * 4 $ 5 + oo = (((2 * 10) $ 20) * 30) $ 40 + ffffffffffffffffffff x = x + 1 + gg x = x * 2 + j = 10 |> ffffffffffffffffffff |> gg |> gg |> gg |> gg |> gg + k = ffffffffffffffffffff << gg << ffffffffffffffffffff <| 10 + l = 10 |> (ffffffffffffffffffff >> gg >> ffffffffffffffffffff) + zzz = ((1 + (2 * 3)) < (4 + (5 * 6))) && ((((7 + (8 * 9)) > ((10 + (11 * 12)))))) + zz = (1 * 2 + 3 * 3 < (4 + 5 * 6) && ((7 + 8 * 9) > (10 + 11 * 12))) === (1 + 3 * 3 < (4 + 5 * 6) && (7 + 8 * 9 > (10 + 11 * 12))) + zzzz = 1 * 2 + 3 * 3 < 4 + 5 * 6 && 7 + 8 * 9 > 10 + 11 * 12 === 1 + 3 * 3 < 4 + 5 * 6 && 7 + 8 * 9 > 10 + 11 * 12 + () + ) |> id diff --git a/unison-src/transcripts-using-base/base.u b/unison-src/transcripts-using-base/base.u index b1023f558a..51d572aa1d 100644 --- a/unison-src/transcripts-using-base/base.u +++ b/unison-src/transcripts-using-base/base.u @@ -87,7 +87,7 @@ List.filter: (a -> Boolean) -> [a] -> [a] List.filter f all = go acc = cases [] -> acc - a +: as -> if (f a) then go (cons a acc) as else go acc as + a +: as -> if (f a) then go (a +: acc) as else go acc as go [] all List.forEach : [a] -> (a ->{e} ()) ->{e} () diff --git a/unison-src/transcripts/boolean-op-pretty-print-2819.output.md b/unison-src/transcripts/boolean-op-pretty-print-2819.output.md index b840f4bbc0..1609f89a39 100644 --- a/unison-src/transcripts/boolean-op-pretty-print-2819.output.md +++ b/unison-src/transcripts/boolean-op-pretty-print-2819.output.md @@ -31,7 +31,7 @@ scratch/main> view hangExample hangExample : Boolean hangExample = - ("a long piece of text to hang the line" == "") - && ("a long piece of text to hang the line" == "") + "a long piece of text to hang the line" == "" + && "a long piece of text to hang the line" == "" ``` diff --git a/unison-src/transcripts/builtins.md b/unison-src/transcripts/builtins.md index 6834b85eb1..5f6a154fac 100644 --- a/unison-src/transcripts/builtins.md +++ b/unison-src/transcripts/builtins.md @@ -170,17 +170,17 @@ scratch/main> add ```unison:hide test> Boolean.tests.orTable = checks [ - true || true == true, - true || false == true, - false || true == true, - false || false == false + (true || true) == true, + (true || false) == true, + (false || true) == true, + (false || false) == false ] test> Boolean.tests.andTable = checks [ - true && true == true, - false && true == false, - true && false == false, - false && false == false + (true && true) == true, + (false && true) == false, + (true && false) == false, + (false && false) == false ] test> Boolean.tests.notTable = checks [ diff --git a/unison-src/transcripts/builtins.output.md b/unison-src/transcripts/builtins.output.md index 3a4538f30a..efa1f53afa 100644 --- a/unison-src/transcripts/builtins.output.md +++ b/unison-src/transcripts/builtins.output.md @@ -157,17 +157,17 @@ test> Nat.tests.conversions = ``` unison test> Boolean.tests.orTable = checks [ - true || true == true, - true || false == true, - false || true == true, - false || false == false + (true || true) == true, + (true || false) == true, + (false || true) == true, + (false || false) == false ] test> Boolean.tests.andTable = checks [ - true && true == true, - false && true == false, - true && false == false, - false && false == false + (true && true) == true, + (false && true) == false, + (true && false) == false, + (false && false) == false ] test> Boolean.tests.notTable = checks [ diff --git a/unison-src/transcripts/delete.md b/unison-src/transcripts/delete.md index 9c1b8efd1a..ce934fd83a 100644 --- a/unison-src/transcripts/delete.md +++ b/unison-src/transcripts/delete.md @@ -147,7 +147,7 @@ structural type Foo = Foo Nat incrementFoo : Foo -> Nat incrementFoo = cases - (Foo n) -> n + 1 + (Foo.Foo n) -> n + 1 ``` ```ucm diff --git a/unison-src/transcripts/delete.output.md b/unison-src/transcripts/delete.output.md index 6107a7fd04..0a9139a6cf 100644 --- a/unison-src/transcripts/delete.output.md +++ b/unison-src/transcripts/delete.output.md @@ -346,7 +346,7 @@ structural type Foo = Foo Nat incrementFoo : Foo -> Nat incrementFoo = cases - (Foo n) -> n + 1 + (Foo.Foo n) -> n + 1 ``` ``` ucm diff --git a/unison-src/transcripts/dependents-dependencies-debugfile.output.md b/unison-src/transcripts/dependents-dependencies-debugfile.output.md index a02c491694..f7398fd480 100644 --- a/unison-src/transcripts/dependents-dependencies-debugfile.output.md +++ b/unison-src/transcripts/dependents-dependencies-debugfile.output.md @@ -23,7 +23,7 @@ scratch/main> debug.file type outside.A#6l6krl7n4l type outside.B#eo6rj0lj1b inside.p#htoo5rnb54 - inside.q#vtdbqaojv6 + inside.q#1mqcoh3tnk inside.r#nkgohbke6n outside.c#f3lgjvjqoo outside.d#ukd7tu6kds diff --git a/unison-src/transcripts/error-messages.output.md b/unison-src/transcripts/error-messages.output.md index 03e7e652ac..148218a759 100644 --- a/unison-src/transcripts/error-messages.output.md +++ b/unison-src/transcripts/error-messages.output.md @@ -191,13 +191,12 @@ foo = match 1 with Loading changes detected in scratch.u. - 😶 - - I expected some patterns after a match / with or cases but I - didn't find any. - + Pattern match doesn't cover all possible cases: 2 | foo = match 1 with + + Patterns not matched: + * _ ``` ``` unison diff --git a/unison-src/transcripts/fix1578.md b/unison-src/transcripts/fix1578.md deleted file mode 100644 index 809af6c161..0000000000 --- a/unison-src/transcripts/fix1578.md +++ /dev/null @@ -1,112 +0,0 @@ -This transcript shows how suffix-based name resolution works when definitions in the file share a suffix with definitions already in the codebase. - -## Setup - -```ucm:hide -scratch/main> builtins.merge -``` - -As setup, we'll add a data type `Day` and a definition `foo.bar : Nat`. - -```unison:hide -unique type Day = Sun | Mon | Tue | Wed | Thu | Fri | Sat - -foo.bar : Nat -foo.bar = 23 -``` - -```ucm:hide -scratch/main> add -``` - -Suffix-based name resolution prefers to use names locally defined in the current file, then checks for matches in the codebase. Here are the precise rules, which will be explained below with examples: - -* If a symbol, `s`, is a suffix of exactly one definition `d` in the file, then `s` refers to `d`. -* Otherwise, if `s` is a suffix of exactly one definition `d` in the codebase, then `s` refers to `d`. -* Otherwise, if `s` is a suffix of multiple definitions in the file or the codebase, then (at least for terms) type-directed name resolution will be attempted to figure out which definition `s` refers to. - -## Example 1: local file term definitions shadow codebase term definitions - -This should typecheck, using the file's `bar : Text` rather than the codebase `foo.bar : Nat`: - -```unison:hide -use Text ++ - -bar : Text -bar = "hello" - -baz = bar ++ ", world!" -``` - -## Example 2: any locally unique term suffix shadows codebase term definitions - -This should also typecheck, using the file's `oog.bar`. This shows you can refer to a definition in the file by any suffix that is unique to definitions in the file (even if that suffix may match other definitions in the _codebase_). See example 4 below for overriding this behavior. - -```unison:hide -use Text ++ - -oog.bar = "hello" - -baz = bar ++ ", world!" -``` - -This subtle test establishes that we aren't using type-directed name resolution (TDNR) for the local term references in the file. If this were using TDNR, it would fail with an ambiguity as there's nothing that pins down the expected type of `bar` here: - -```unison:hide -use Text ++ - -oog.bar = "hello" - -baz = (bar, 42) -``` - -This subtle test establishes that locally introduced variables (within a function, say) correctly shadow definitions introduced at the file top level: - -```unison:hide -use Text ++ - -oog.bar = "hello" - -baz : [Int] -> ([Int], Nat) -baz bar = (bar, 42) -- here, `bar` refers to the parameter -``` - -## Example 3: Local type and constructor definitions shadow codebase definitions - -This should also typecheck, using the local `Sun`, and not `Day.Sun` which exists in the codebase, and the local `Day`, not the codebase `Day`. - -```unison:hide -structural type Zoot = Zonk | Sun - -structural type Day = Day Int - -use Zoot Zonk - -flip : Zoot -> Zoot -flip = cases - Sun -> Zonk - Zonk -> Sun - -day1 : Day -day1 = Day +1 -``` - -## Example 4: Refering to codebase definitions via a unique suffix - -Even though local definitions are preferred, you can refer to definitions in the codebase via any unique suffix that doesn't also exist in the file. - -```unison:hide -structural type Zoot = Zonk | Sun - -use Zoot Zonk - -blah = cases - Day.Sun -> Day.Tue - day -> day - -blah2 = - -- imports work too if you get tired of typing Day.Sun over and over - use Day Sun - cases Sun -> Wed - day -> day -``` diff --git a/unison-src/transcripts/fix1578.output.md b/unison-src/transcripts/fix1578.output.md deleted file mode 100644 index 0645dae519..0000000000 --- a/unison-src/transcripts/fix1578.output.md +++ /dev/null @@ -1,105 +0,0 @@ -This transcript shows how suffix-based name resolution works when definitions in the file share a suffix with definitions already in the codebase. - -## Setup - -As setup, we'll add a data type `Day` and a definition `foo.bar : Nat`. - -``` unison -unique type Day = Sun | Mon | Tue | Wed | Thu | Fri | Sat - -foo.bar : Nat -foo.bar = 23 -``` - -Suffix-based name resolution prefers to use names locally defined in the current file, then checks for matches in the codebase. Here are the precise rules, which will be explained below with examples: - - - If a symbol, `s`, is a suffix of exactly one definition `d` in the file, then `s` refers to `d`. - - Otherwise, if `s` is a suffix of exactly one definition `d` in the codebase, then `s` refers to `d`. - - Otherwise, if `s` is a suffix of multiple definitions in the file or the codebase, then (at least for terms) type-directed name resolution will be attempted to figure out which definition `s` refers to. - -## Example 1: local file term definitions shadow codebase term definitions - -This should typecheck, using the file's `bar : Text` rather than the codebase `foo.bar : Nat`: - -``` unison -use Text ++ - -bar : Text -bar = "hello" - -baz = bar ++ ", world!" -``` - -## Example 2: any locally unique term suffix shadows codebase term definitions - -This should also typecheck, using the file's `oog.bar`. This shows you can refer to a definition in the file by any suffix that is unique to definitions in the file (even if that suffix may match other definitions in the *codebase*). See example 4 below for overriding this behavior. - -``` unison -use Text ++ - -oog.bar = "hello" - -baz = bar ++ ", world!" -``` - -This subtle test establishes that we aren't using type-directed name resolution (TDNR) for the local term references in the file. If this were using TDNR, it would fail with an ambiguity as there's nothing that pins down the expected type of `bar` here: - -``` unison -use Text ++ - -oog.bar = "hello" - -baz = (bar, 42) -``` - -This subtle test establishes that locally introduced variables (within a function, say) correctly shadow definitions introduced at the file top level: - -``` unison -use Text ++ - -oog.bar = "hello" - -baz : [Int] -> ([Int], Nat) -baz bar = (bar, 42) -- here, `bar` refers to the parameter -``` - -## Example 3: Local type and constructor definitions shadow codebase definitions - -This should also typecheck, using the local `Sun`, and not `Day.Sun` which exists in the codebase, and the local `Day`, not the codebase `Day`. - -``` unison -structural type Zoot = Zonk | Sun - -structural type Day = Day Int - -use Zoot Zonk - -flip : Zoot -> Zoot -flip = cases - Sun -> Zonk - Zonk -> Sun - -day1 : Day -day1 = Day +1 -``` - -## Example 4: Refering to codebase definitions via a unique suffix - -Even though local definitions are preferred, you can refer to definitions in the codebase via any unique suffix that doesn't also exist in the file. - -``` unison -structural type Zoot = Zonk | Sun - -use Zoot Zonk - -blah = cases - Day.Sun -> Day.Tue - day -> day - -blah2 = - -- imports work too if you get tired of typing Day.Sun over and over - use Day Sun - cases Sun -> Wed - day -> day -``` - diff --git a/unison-src/transcripts/fix4731.md b/unison-src/transcripts/fix4731.md new file mode 100644 index 0000000000..974a55db33 --- /dev/null +++ b/unison-src/transcripts/fix4731.md @@ -0,0 +1,33 @@ +```unison +structural type Void = +``` + +```ucm +scratch/main> add +``` + +We should be able to `match` on empty types like `Void`. + +```unison +Void.absurdly : '{e} Void ->{e} a +Void.absurdly v = match !v with +``` + +```unison +Void.absurdly : Void -> a +Void.absurdly v = match v with +``` + +And empty `cases` should also work. + +```unison +Void.absurdly : Void -> a +Void.absurdly = cases +``` + +But empty function bodies are not allowed. + +```unison:error +Void.absurd : Void -> a +Void.absurd x = +``` diff --git a/unison-src/transcripts/fix4731.output.md b/unison-src/transcripts/fix4731.output.md new file mode 100644 index 0000000000..89801fcfcd --- /dev/null +++ b/unison-src/transcripts/fix4731.output.md @@ -0,0 +1,99 @@ +``` unison +structural type Void = +``` + +``` ucm + + Loading changes detected in scratch.u. + + I found and typechecked these definitions in scratch.u. If you + do an `add` or `update`, here's how your codebase would + change: + + ⍟ These new definitions are ok to `add`: + + structural type Void + +``` +``` ucm +scratch/main> add + + ⍟ I've added these definitions: + + structural type Void + +``` +We should be able to `match` on empty types like `Void`. + +``` unison +Void.absurdly : '{e} Void ->{e} a +Void.absurdly v = match !v with +``` + +``` ucm + + Loading changes detected in scratch.u. + + I found and typechecked these definitions in scratch.u. If you + do an `add` or `update`, here's how your codebase would + change: + + ⍟ These new definitions are ok to `add`: + + Void.absurdly : '{e} Void ->{e} a + +``` +``` unison +Void.absurdly : Void -> a +Void.absurdly v = match v with +``` + +``` ucm + + Loading changes detected in scratch.u. + + I found and typechecked these definitions in scratch.u. If you + do an `add` or `update`, here's how your codebase would + change: + + ⍟ These new definitions are ok to `add`: + + Void.absurdly : Void -> a + +``` +And empty `cases` should also work. + +``` unison +Void.absurdly : Void -> a +Void.absurdly = cases +``` + +``` ucm + + Loading changes detected in scratch.u. + + I found and typechecked these definitions in scratch.u. If you + do an `add` or `update`, here's how your codebase would + change: + + ⍟ These new definitions are ok to `add`: + + Void.absurdly : Void -> a + +``` +But empty function bodies are not allowed. + +``` unison +Void.absurd : Void -> a +Void.absurd x = +``` + +``` ucm + + Loading changes detected in scratch.u. + + I expected a block after this (in red), but there wasn't one. Maybe check your indentation: + 2 | Void.absurd x = + + +``` diff --git a/unison-src/transcripts/io.md b/unison-src/transcripts/io.md index 7db903ebb4..cc27f12ca5 100644 --- a/unison-src/transcripts/io.md +++ b/unison-src/transcripts/io.md @@ -350,24 +350,24 @@ testGetArgs.runMeWithNoArgs = 'let args = reraise !getArgs.impl match args with [] -> printLine "called with no args" - _ -> raise (fail "called with args") + _ -> raise (testGetArgs.fail "called with args") testGetArgs.runMeWithOneArg : '{io2.IO, Exception} () testGetArgs.runMeWithOneArg = 'let args = reraise !getArgs.impl match args with - [] -> raise (fail "called with no args") + [] -> raise (testGetArgs.fail "called with no args") [_] -> printLine "called with one arg" - _ -> raise (fail "called with too many args") + _ -> raise (testGetArgs.fail "called with too many args") testGetArgs.runMeWithTwoArgs : '{io2.IO, Exception} () testGetArgs.runMeWithTwoArgs = 'let args = reraise !getArgs.impl match args with - [] -> raise (fail "called with no args") - [_] -> raise (fail "called with one arg") + [] -> raise (testGetArgs.fail "called with no args") + [_] -> raise (testGetArgs.fail "called with one arg") [_, _] -> printLine "called with two args" - _ -> raise (fail "called with too many args") + _ -> raise (testGetArgs.fail "called with too many args") ``` Test that they can be run with the right number of args. diff --git a/unison-src/transcripts/io.output.md b/unison-src/transcripts/io.output.md index 4ac673c76e..77c84aea6b 100644 --- a/unison-src/transcripts/io.output.md +++ b/unison-src/transcripts/io.output.md @@ -566,24 +566,24 @@ testGetArgs.runMeWithNoArgs = 'let args = reraise !getArgs.impl match args with [] -> printLine "called with no args" - _ -> raise (fail "called with args") + _ -> raise (testGetArgs.fail "called with args") testGetArgs.runMeWithOneArg : '{io2.IO, Exception} () testGetArgs.runMeWithOneArg = 'let args = reraise !getArgs.impl match args with - [] -> raise (fail "called with no args") + [] -> raise (testGetArgs.fail "called with no args") [_] -> printLine "called with one arg" - _ -> raise (fail "called with too many args") + _ -> raise (testGetArgs.fail "called with too many args") testGetArgs.runMeWithTwoArgs : '{io2.IO, Exception} () testGetArgs.runMeWithTwoArgs = 'let args = reraise !getArgs.impl match args with - [] -> raise (fail "called with no args") - [_] -> raise (fail "called with one arg") + [] -> raise (testGetArgs.fail "called with no args") + [_] -> raise (testGetArgs.fail "called with one arg") [_, _] -> printLine "called with two args" - _ -> raise (fail "called with too many args") + _ -> raise (testGetArgs.fail "called with too many args") ``` Test that they can be run with the right number of args. diff --git a/unison-src/transcripts/name-resolution.md b/unison-src/transcripts/name-resolution.md index 5dac5ee7c2..3e0ef716ec 100644 --- a/unison-src/transcripts/name-resolution.md +++ b/unison-src/transcripts/name-resolution.md @@ -93,16 +93,16 @@ scratch/main> project.delete scratch # Example 4 -We have a namespace term `Woot.state : Nat` and a file term `Something.state : Text -> Something`. A reference to the -term `state : Text` resolves to `Something.state`, which shadows `Woot.state`. (This behavior will change). +We have a namespace term `ns.foo : Nat` and a file term `file.foo : Text`. A reference to the term `foo` is ambiguous, +but resolves to `ns.foo` via TDNR. ```ucm scratch/main> builtins.mergeio lib.builtins ``` ```unison -Woot.state : Nat -Woot.state = 42 +ns.foo : Nat +ns.foo = 42 ``` ```ucm @@ -110,11 +110,84 @@ scratch/main> add ``` ```unison -type Something = { state : Text } +file.foo : Text +file.foo = "foo" -ex = do - s = Something "hello" - state s ++ " world!" +bar : Text +bar = foo ++ "bar" +``` + +```ucm +scratch/main> project.delete scratch +``` + +# Example 4 + +We have a namespace term `ns.foo : Nat` and a file term `file.foo : Text`. A reference to the term `foo` is ambiguous, +but resolves to `file.foo` via TDNR. + +```ucm +scratch/main> builtins.mergeio lib.builtins +``` + +```unison +ns.foo : Nat +ns.foo = 42 +``` + +```ucm +scratch/main> add +``` + +```unison +file.foo : Text +file.foo = "foo" + +bar : Nat +bar = foo + 42 +``` + +```ucm +scratch/main> project.delete scratch +``` + +# Example 4 + +We have a namespace term `ns.foo : Nat` and a file term `file.foo : Nat`. A reference to the term `foo` is ambiguous. +A reference to `ns.foo` or `file.foo` work fine. + +```ucm +scratch/main> builtins.mergeio lib.builtins +``` + +```unison +ns.foo : Nat +ns.foo = 42 +``` + +```ucm +scratch/main> add +``` + +```unison:error +file.foo : Nat +file.foo = 43 + +bar : Nat +bar = foo + 10 +``` + +```unison +file.foo : Nat +file.foo = 43 + +bar : Nat +bar = file.foo + ns.foo +``` + +```ucm +scratch/main> add +scratch/main> view bar ``` ```ucm diff --git a/unison-src/transcripts/name-resolution.output.md b/unison-src/transcripts/name-resolution.output.md index 0e636b96d6..0624a26a8e 100644 --- a/unison-src/transcripts/name-resolution.output.md +++ b/unison-src/transcripts/name-resolution.output.md @@ -227,8 +227,8 @@ scratch/main> project.delete scratch ``` # Example 4 -We have a namespace term `Woot.state : Nat` and a file term `Something.state : Text -> Something`. A reference to the -term `state : Text` resolves to `Something.state`, which shadows `Woot.state`. (This behavior will change). +We have a namespace term `ns.foo : Nat` and a file term `file.foo : Text`. A reference to the term `foo` is ambiguous, +but resolves to `ns.foo` via TDNR. ``` ucm scratch/main> builtins.mergeio lib.builtins @@ -237,8 +237,8 @@ scratch/main> builtins.mergeio lib.builtins ``` ``` unison -Woot.state : Nat -Woot.state = 42 +ns.foo : Nat +ns.foo = 42 ``` ``` ucm @@ -251,7 +251,7 @@ Woot.state = 42 ⍟ These new definitions are ok to `add`: - Woot.state : Nat + ns.foo : Nat ``` ``` ucm @@ -259,15 +259,15 @@ scratch/main> add ⍟ I've added these definitions: - Woot.state : Nat + ns.foo : Nat ``` ``` unison -type Something = { state : Text } +file.foo : Text +file.foo = "foo" -ex = do - s = Something "hello" - state s ++ " world!" +bar : Text +bar = foo ++ "bar" ``` ``` ucm @@ -280,13 +280,175 @@ ex = do ⍟ These new definitions are ok to `add`: - type Something - Something.state : Something -> Text - Something.state.modify : (Text ->{g} Text) - -> Something - ->{g} Something - Something.state.set : Text -> Something -> Something - ex : 'Text + bar : Text + file.foo : Text + +``` +``` ucm +scratch/main> project.delete scratch + +``` +# Example 4 + +We have a namespace term `ns.foo : Nat` and a file term `file.foo : Text`. A reference to the term `foo` is ambiguous, +but resolves to `file.foo` via TDNR. + +``` ucm +scratch/main> builtins.mergeio lib.builtins + + Done. + +``` +``` unison +ns.foo : Nat +ns.foo = 42 +``` + +``` ucm + + Loading changes detected in scratch.u. + + I found and typechecked these definitions in scratch.u. If you + do an `add` or `update`, here's how your codebase would + change: + + ⍟ These new definitions are ok to `add`: + + ns.foo : Nat + +``` +``` ucm +scratch/main> add + + ⍟ I've added these definitions: + + ns.foo : Nat + +``` +``` unison +file.foo : Text +file.foo = "foo" + +bar : Nat +bar = foo + 42 +``` + +``` ucm + + Loading changes detected in scratch.u. + + I found and typechecked these definitions in scratch.u. If you + do an `add` or `update`, here's how your codebase would + change: + + ⍟ These new definitions are ok to `add`: + + bar : Nat + file.foo : Text + +``` +``` ucm +scratch/main> project.delete scratch + +``` +# Example 4 + +We have a namespace term `ns.foo : Nat` and a file term `file.foo : Nat`. A reference to the term `foo` is ambiguous. +A reference to `ns.foo` or `file.foo` work fine. + +``` ucm +scratch/main> builtins.mergeio lib.builtins + + Done. + +``` +``` unison +ns.foo : Nat +ns.foo = 42 +``` + +``` ucm + + Loading changes detected in scratch.u. + + I found and typechecked these definitions in scratch.u. If you + do an `add` or `update`, here's how your codebase would + change: + + ⍟ These new definitions are ok to `add`: + + ns.foo : Nat + +``` +``` ucm +scratch/main> add + + ⍟ I've added these definitions: + + ns.foo : Nat + +``` +``` unison +file.foo : Nat +file.foo = 43 + +bar : Nat +bar = foo + 10 +``` + +``` ucm + + Loading changes detected in scratch.u. + + I couldn't figure out what foo refers to here: + + 5 | bar = foo + 10 + + The name foo is ambiguous. Its type should be: Nat + + I found some terms in scope that have matching names and + types. Maybe you meant one of these: + + file.foo : Nat + ns.foo : Nat + +``` +``` unison +file.foo : Nat +file.foo = 43 + +bar : Nat +bar = file.foo + ns.foo +``` + +``` ucm + + Loading changes detected in scratch.u. + + I found and typechecked these definitions in scratch.u. If you + do an `add` or `update`, here's how your codebase would + change: + + ⍟ These new definitions are ok to `add`: + + bar : Nat + file.foo : Nat + +``` +``` ucm +scratch/main> add + + ⍟ I've added these definitions: + + bar : Nat + file.foo : Nat + +scratch/main> view bar + + bar : Nat + bar = + use Nat + + file.foo + ns.foo ``` ``` ucm diff --git a/unison-src/transcripts/namespace-directive.md b/unison-src/transcripts/namespace-directive.md index 1d0ffddb25..6891461501 100644 --- a/unison-src/transcripts/namespace-directive.md +++ b/unison-src/transcripts/namespace-directive.md @@ -62,7 +62,7 @@ type Baz = { qux : Nat } type RefersToFoo = RefersToFoo Foo refersToBar = cases - Bar -> 17 + Foo.Bar -> 17 refersToQux baz = Baz.qux baz + Baz.qux baz diff --git a/unison-src/transcripts/namespace-directive.output.md b/unison-src/transcripts/namespace-directive.output.md index 90e568248a..63f7a5c2cb 100644 --- a/unison-src/transcripts/namespace-directive.output.md +++ b/unison-src/transcripts/namespace-directive.output.md @@ -132,7 +132,7 @@ type Baz = { qux : Nat } type RefersToFoo = RefersToFoo Foo refersToBar = cases - Bar -> 17 + Foo.Bar -> 17 refersToQux baz = Baz.qux baz + Baz.qux baz diff --git a/unison-src/transcripts/pattern-match-coverage.md b/unison-src/transcripts/pattern-match-coverage.md index e08ea269ab..5868bd7981 100644 --- a/unison-src/transcripts/pattern-match-coverage.md +++ b/unison-src/transcripts/pattern-match-coverage.md @@ -367,7 +367,7 @@ unique type T = A | B result : '{e, Abort} T -> {e} () result f = handle !f with cases - { A } -> () + { T.A } -> () { B } -> () { abort -> _ } -> bug "aborted" ``` @@ -421,7 +421,7 @@ unique type T = A | B result : '{e, Abort} T -> {e} () result f = handle !f with cases - { A } -> () + { T.A } -> () { abort -> _ } -> bug "aborted" ``` diff --git a/unison-src/transcripts/pattern-match-coverage.output.md b/unison-src/transcripts/pattern-match-coverage.output.md index 575c35cab0..2e761bf1ad 100644 --- a/unison-src/transcripts/pattern-match-coverage.output.md +++ b/unison-src/transcripts/pattern-match-coverage.output.md @@ -853,7 +853,7 @@ unique type T = A | B result : '{e, Abort} T -> {e} () result f = handle !f with cases - { A } -> () + { T.A } -> () { B } -> () { abort -> _ } -> bug "aborted" ``` @@ -970,7 +970,7 @@ unique type T = A | B result : '{e, Abort} T -> {e} () result f = handle !f with cases - { A } -> () + { T.A } -> () { abort -> _ } -> bug "aborted" ``` @@ -980,7 +980,7 @@ result f = handle !f with cases Pattern match doesn't cover all possible cases: 7 | result f = handle !f with cases - 8 | { A } -> () + 8 | { T.A } -> () 9 | { abort -> _ } -> bug "aborted" @@ -1004,14 +1004,19 @@ result f = handle !f with cases Loading changes detected in scratch.u. - Pattern match doesn't cover all possible cases: - 7 | result f = handle !f with cases - 8 | { x } -> x - 9 | { give A -> resume } -> result resume - + I found and typechecked these definitions in scratch.u. If you + do an `add` or `update`, here's how your codebase would + change: - Patterns not matched: - * { give B -> _ } + ⍟ These new definitions are ok to `add`: + + ability Give a + result : '{e, Give T} r ->{e} r + + ⍟ These names already exist. You can `update` them to your + new definition: + + type T ``` ``` unison diff --git a/unison-src/transcripts/suffixes.md b/unison-src/transcripts/suffixes.md index 7245b4cb31..24eeef17b9 100644 --- a/unison-src/transcripts/suffixes.md +++ b/unison-src/transcripts/suffixes.md @@ -73,35 +73,3 @@ Note that we can always still view indirect dependencies by using more name segm scratch/main> view distributed.abra.cadabra scratch/main> names distributed.lib.baz.qux ``` - -## Corner cases - -If a definition is given in a scratch file, its suffixes shadow existing definitions that exist in the codebase with the same suffixes. For example: - -```unison:hide -unique type A = Thing1 Nat | thing2 Nat - -foo.a = 23 -bar = 100 -``` - -```ucm -scratch/main> add -``` - -```unison -unique type B = Thing1 Text | thing2 Text | Thing3 Text - -zoink.a = "hi" - --- verifying that the `a` here references `zoink.a` -foo.baz.qux.bar : Text -foo.baz.qux.bar = a - --- verifying that the `bar` is resolving to `foo.baz.qux.bar` --- and that `Thing1` references `B.Thing1` from the current file -fn = cases - Thing1 msg -> msg Text.++ bar - thing2 msg -> msg Text.++ bar - _ -> todo "hmm" -``` diff --git a/unison-src/transcripts/suffixes.output.md b/unison-src/transcripts/suffixes.output.md index a4cd5e3b02..d8167704e4 100644 --- a/unison-src/transcripts/suffixes.output.md +++ b/unison-src/transcripts/suffixes.output.md @@ -167,57 +167,3 @@ scratch/main> names distributed.lib.baz.qux Names: lib.distributed.lib.baz.qux ``` -## Corner cases - -If a definition is given in a scratch file, its suffixes shadow existing definitions that exist in the codebase with the same suffixes. For example: - -``` unison -unique type A = Thing1 Nat | thing2 Nat - -foo.a = 23 -bar = 100 -``` - -``` ucm -scratch/main> add - - ⍟ I've added these definitions: - - type A - bar : Nat - foo.a : Nat - -``` -``` unison -unique type B = Thing1 Text | thing2 Text | Thing3 Text - -zoink.a = "hi" - --- verifying that the `a` here references `zoink.a` -foo.baz.qux.bar : Text -foo.baz.qux.bar = a - --- verifying that the `bar` is resolving to `foo.baz.qux.bar` --- and that `Thing1` references `B.Thing1` from the current file -fn = cases - Thing1 msg -> msg Text.++ bar - thing2 msg -> msg Text.++ bar - _ -> todo "hmm" -``` - -``` ucm - - Loading changes detected in scratch.u. - - I found and typechecked these definitions in scratch.u. If you - do an `add` or `update`, here's how your codebase would - change: - - ⍟ These new definitions are ok to `add`: - - type B - fn : B -> Text - foo.baz.qux.bar : Text - zoink.a : Text - -``` diff --git a/unison-syntax/src/Unison/Parser/Ann.hs b/unison-syntax/src/Unison/Parser/Ann.hs index e4b361d148..1b73adeaf6 100644 --- a/unison-syntax/src/Unison/Parser/Ann.hs +++ b/unison-syntax/src/Unison/Parser/Ann.hs @@ -29,6 +29,7 @@ startingLine _ = Nothing instance Monoid Ann where mempty = External +-- | This instance is commutative. instance Semigroup Ann where Ann s1 e1 <> Ann s2 e2 = Ann (min s1 s2) (max e1 e2) -- If we have a concrete location from a file, use it diff --git a/unison-syntax/src/Unison/Syntax/Lexer/Unison.hs b/unison-syntax/src/Unison/Syntax/Lexer/Unison.hs index 9c50e2731f..ac31fdcac4 100644 --- a/unison-syntax/src/Unison/Syntax/Lexer/Unison.hs +++ b/unison-syntax/src/Unison/Syntax/Lexer/Unison.hs @@ -203,7 +203,9 @@ token'' tok p = do else if column p < top l then S.put (env {layout = pop l}) >> ((Token Close p p :) <$> pops p) - else error "impossible" + else -- we hit this branch exactly when `token''` is given the state + -- `{layout = [], opening = Nothing, inLayout = True}` + fail "internal error: token''" -- don't emit virtual semis in (, {, or [ blocks topContainsVirtualSemis :: Layout -> Bool diff --git a/unison-syntax/src/Unison/Syntax/Parser.hs b/unison-syntax/src/Unison/Syntax/Parser.hs index bd243b0d3d..74d8d03537 100644 --- a/unison-syntax/src/Unison/Syntax/Parser.hs +++ b/unison-syntax/src/Unison/Syntax/Parser.hs @@ -15,6 +15,7 @@ module Unison.Syntax.Parser bytesToken, chainl1, chainr1, + chainl1Accum, character, closeBlock, optionalCloseBlock, @@ -75,14 +76,12 @@ import Text.Megaparsec qualified as P import U.Codebase.Reference (ReferenceType (..)) import U.Util.Base32Hex qualified as Base32Hex import Unison.ABT qualified as ABT -import Unison.ConstructorReference (ConstructorReference) import Unison.Hash qualified as Hash import Unison.HashQualified qualified as HQ import Unison.HashQualifiedPrime qualified as HQ' import Unison.Hashable qualified as Hashable import Unison.Name as Name import Unison.NameSegment (NameSegment) -import Unison.NameSegment.Internal qualified as INameSegment import Unison.Names (Names) import Unison.Names.ResolutionResult qualified as Names import Unison.Parser.Ann (Ann (..), Annotated (..)) @@ -117,7 +116,37 @@ data ParsingEnv (m :: Type -> Type) = ParsingEnv -- The name (e.g. `Foo` in `unique type Foo`) is passed in, and if the function returns a Just, that GUID is used; -- otherwise, a random one is generated from `uniqueNames`. uniqueTypeGuid :: Name -> m (Maybe Text), - names :: Names + names :: Names, + -- The namespace block we are currently parsing under. + -- + -- Mostly, this ought to have no affect on parsing: "applying" a namespace should be a pre-processing pass. All + -- bindings are prefixed with the namespace (easy), and all free variables that match a binding are prefixed (also easy). + -- + -- ... but our "parser" is also doing parse-time name resolution for hash-qualified names, type references, + -- constructors in patterns, and term/type links. + -- + -- So, when parsing a pattern `Bar` like + -- + -- (in `namespace foo`) + -- match whatever with + -- Bar -> ... + -- + -- we need to first prefix `Bar`, giving `foo.Bar`, before looking up in the name in the environment. + -- + -- You might think we could simply parse a term under a pre-namespaced environment, avoiding the need to plumb the + -- namespace through via the parsing environment. That too could work in theory, but would be rather difficult to + -- implement with the current file parsing mechanism that fully parses and resolves all types in the file before + -- moving on to terms. + -- + -- As an example, we don't want this to fail with a `foo.Bar not in scope` error: + -- + -- namespace foo + -- type Bar = ... + -- type Foo = ... foo.Bar ... + -- + -- That is easiest to implement with the current solution – first pre-process the types as above, then run them + -- through the "make type environment" logic (which is fed into the term parser). + maybeNamespace :: Maybe Name } newtype UniqueName = UniqueName (L.Pos -> Int -> Maybe Text) @@ -158,14 +187,10 @@ data Error v = SignatureNeedsAccompanyingBody (L.Token v) | DisallowedAbsoluteName (L.Token Name) | EmptyBlock (L.Token String) - | UnknownAbilityConstructor (L.Token (HQ.HashQualified Name)) (Set ConstructorReference) - | UnknownDataConstructor (L.Token (HQ.HashQualified Name)) (Set ConstructorReference) | UnknownTerm (L.Token (HQ.HashQualified Name)) (Set Referent) | UnknownType (L.Token (HQ.HashQualified Name)) (Set Reference) | UnknownId (L.Token (HQ.HashQualified Name)) (Set Referent) (Set Reference) | ExpectedBlockOpen String (L.Token L.Lexeme) - | -- | Indicates a cases or match/with which doesn't have any patterns - EmptyMatch (L.Token ()) | EmptyWatch Ann | UseInvalidPrefixSuffix (Either (L.Token Name) (L.Token Name)) (Maybe [L.Token Name]) | UseEmpty (L.Token String) -- an empty `use` statement @@ -175,7 +200,7 @@ data Error v MissingTypeModifier (L.Token String) (L.Token v) | -- | A type was found in a position that requires a term TypeNotAllowed (L.Token (HQ.HashQualified Name)) - | ResolutionFailures [Names.ResolutionFailure v Ann] + | ResolutionFailures [Names.ResolutionFailure Ann] | DuplicateTypeNames [(v, [Ann])] | DuplicateTermNames [(v, [Ann])] | -- | PatternArityMismatch expectedArity actualArity location @@ -281,19 +306,15 @@ closeBlock = void <$> matchToken L.Close optionalCloseBlock :: (Ord v) => P v m (L.Token ()) optionalCloseBlock = closeBlock <|> (\() -> L.Token () mempty mempty) <$> P.eof --- | A `Name` is blank when it is unqualified and begins with a `_` (also implying that it is wordy) -isBlank :: Name -> Bool -isBlank n = Name.isUnqualified n && Text.isPrefixOf "_" (INameSegment.toUnescapedText $ Name.lastSegment n) - -- | A HQ Name is blank when its Name is blank and it has no hash. isBlank' :: HQ'.HashQualified Name -> Bool isBlank' = \case - HQ'.NameOnly n -> isBlank n + HQ'.NameOnly n -> Name.isBlank n HQ'.HashQualified _ _ -> False wordyPatternName :: (Var v) => P v m (L.Token v) wordyPatternName = queryToken \case - L.WordyId (HQ'.NameOnly n) -> if isBlank n then Nothing else Just $ Name.toVar n + L.WordyId (HQ'.NameOnly n) -> if Name.isBlank n then Nothing else Just $ Name.toVar n _ -> Nothing -- | Parse a prefix identifier e.g. Foo or (+), discarding any hash @@ -452,6 +473,27 @@ chainr1 p op = go1 chainl1 :: (Ord v) => P v m a -> P v m (a -> a -> a) -> P v m a chainl1 p op = foldl (flip ($)) <$> p <*> P.many (flip <$> op <*> p) +-- chainl1Accum is like chainl1, but it accumulates intermediate results +-- instead of applying them immediately. It's used to implement infix +-- operators that may or may not have precedence rules. +chainl1Accum :: + (P.Stream u, Ord s) => + P.ParsecT s u m a -> + P.ParsecT s u m (a -> a -> a) -> + P.ParsecT s u m (a, [a -> a]) +chainl1Accum p op = do + x <- p + fs <- rest [] + pure (x, fs) + where + rest fs = + ( do + f <- op + y <- p + rest (fs ++ [flip f y]) + ) + <|> return fs + -- | If `p` would succeed, this fails uncommitted. -- Otherwise, `failIfOk` used to produce the output failureIf :: (Ord v) => P v m (P v m b) -> P v m a -> P v m b