From 27d04e40c3a1fbb54dcf82173993053e2e2a5f13 Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Thu, 23 Jul 2026 18:31:30 +0900 Subject: [PATCH 01/33] Implement Solidity-style source syntax --- src/Solcore/Backend/ComptimeCheck.hs | 8 +- src/Solcore/Backend/Specialise.hs | 5 + src/Solcore/Desugarer/ContractDispatch.hs | 17 + src/Solcore/Desugarer/DecisionTreeCompiler.hs | 6 +- src/Solcore/Desugarer/FieldAccess.hs | 10 + src/Solcore/Desugarer/IndirectCall.hs | 4 + src/Solcore/Desugarer/UniqueTypeGen.hs | 2 + src/Solcore/Frontend/ComptimeCheck.hs | 50 +- src/Solcore/Frontend/Lexer/SolcoreLexer.hs | 26 +- src/Solcore/Frontend/Module/Loader.hs | 115 ++- src/Solcore/Frontend/Parser/Decl.hs | 417 ++++---- src/Solcore/Frontend/Parser/Expr.hs | 88 +- src/Solcore/Frontend/Parser/Patterns.hs | 38 +- src/Solcore/Frontend/Parser/SolcoreTypes.hs | 89 +- src/Solcore/Frontend/Parser/Stmt.hs | 88 +- src/Solcore/Frontend/Pretty/ShortName.hs | 4 +- src/Solcore/Frontend/Pretty/SolcorePretty.hs | 454 ++++++--- src/Solcore/Frontend/Pretty/TreePretty.hs | 670 +++++++++---- src/Solcore/Frontend/Syntax/Contract.hs | 4 + src/Solcore/Frontend/Syntax/NameResolution.hs | 129 ++- src/Solcore/Frontend/Syntax/Stmt.hs | 14 +- src/Solcore/Frontend/Syntax/SyntaxTree.hs | 304 +++++- src/Solcore/Frontend/TypeInference/Erase.hs | 2 + .../Frontend/TypeInference/SccAnalysis.hs | 4 + .../Frontend/TypeInference/TcContract.hs | 27 +- src/Solcore/Frontend/TypeInference/TcMonad.hs | 31 +- src/Solcore/Frontend/TypeInference/TcStmt.hs | 72 ++ src/Solcore/Frontend/TypeInference/TcSubst.hs | 23 + src/Solcore/Pipeline/SolcorePipeline.hs | 24 +- test/LocationTests.hs | 135 ++- test/ModuleTypeCheckTests.hs | 135 ++- test/ParserTests.hs | 925 ++++++++++++++++-- test/imports/struct_metadata_lib.solc | 6 + test/imports/struct_metadata_main.solc | 1 + 34 files changed, 3150 insertions(+), 777 deletions(-) create mode 100644 test/imports/struct_metadata_lib.solc create mode 100644 test/imports/struct_metadata_main.solc diff --git a/src/Solcore/Backend/ComptimeCheck.hs b/src/Solcore/Backend/ComptimeCheck.hs index 04883ec7e..f0a8a936a 100644 --- a/src/Solcore/Backend/ComptimeCheck.hs +++ b/src/Solcore/Backend/ComptimeCheck.hs @@ -12,8 +12,8 @@ module Solcore.Backend.ComptimeCheck (checkComptime) where 2. Constraint checking: annotations must be consistent with reality. - A parameter annotated 'comptime' must receive a comptime argument at every call site. - - A 'let x : comptime T = e' binding requires e to be comptime. - - A function annotated '-> comptime T' requires every returned + - A 'let comptime x: T = e' binding requires e to be comptime. + - A function with a 'returns (comptime T)' result requires every returned expression to be comptime. The verifier reports the first violation found as a String error. @@ -48,7 +48,7 @@ checkFunDef :: FunTable -> Set.Set Name -> MastFunDef -> Either String () checkFunDef ft pure_ fd = checkStmts ft pure_ (mastFunRetComptime fd) (mastFunName fd) initEnv (mastFunBody fd) where - -- For '-> comptime' functions, assume ALL params are comptime when checking + -- For functions with a comptime result, assume ALL params are comptime when checking -- the body: this verifies "if all args happen to be comptime, is the result?" -- For other functions, only explicitly-annotated comptime params are trusted. initEnv = @@ -87,7 +87,7 @@ checkStmt ft pure_ retCt fname env stmt = case stmt of checkExp ft pure_ env e let ct' = isComptime ft pure_ env e when_ (retCt && not ct') $ - "function '" ++ show fname ++ "' annotated '-> comptime' returns a runtime expression" + "function '" ++ show fname ++ "' with a comptime result returns a runtime expression" return env MastMatch scrut alts -> do checkExp ft pure_ env scrut diff --git a/src/Solcore/Backend/Specialise.hs b/src/Solcore/Backend/Specialise.hs index 0057ef157..80233e55e 100644 --- a/src/Solcore/Backend/Specialise.hs +++ b/src/Solcore/Backend/Specialise.hs @@ -253,6 +253,11 @@ addInstResolutions :: Instance Id -> SM () addInstResolutions inst = forM_ (instFunctions inst) (addMethodResolution (instDefault inst) (instName inst) (mainTy inst)) specialiseTopDecl :: TopDecl Id -> SM [TopDecl Id] +specialiseTopDecl (TContr (Contract _ _ decls)) + | any isSignatureDecl decls = pure [] + where + isSignatureDecl CSignatureDecl {} = True + isSignatureDecl _ = False specialiseTopDecl (TContr (Contract name args decls)) = withLocalState do addContractResolutions (Contract name args decls) -- Runtime code diff --git a/src/Solcore/Desugarer/ContractDispatch.hs b/src/Solcore/Desugarer/ContractDispatch.hs index 483f95b47..75989aca0 100644 --- a/src/Solcore/Desugarer/ContractDispatch.hs +++ b/src/Solcore/Desugarer/ContractDispatch.hs @@ -38,6 +38,7 @@ contractDispatchTopDecls topdecls = Set.toList extras <> topdecls' where (extras, topdecls') = mapAccumL go Set.empty topdecls go acc (TContr c) + | isInterfaceContract c = (acc, TContr c) | "main" `notElem` functionNames c = (Set.union acc (genNameDecls c), TContr (genMainFn True c)) | otherwise = (acc, TContr (genMainFn False c)) go acc v = (acc, v) @@ -60,8 +61,15 @@ functionNames :: Contract a -> [Name] functionNames = foldr go [] . decls where go (CFunDecl fd) = (sigName (funSignature fd) :) + go (CSignatureDecl _ sig) = (sigName sig :) go _ = id +isInterfaceContract :: Contract a -> Bool +isInterfaceContract = any isSignature . decls + where + isSignature CSignatureDecl {} = True + isSignature _ = False + -- | Returns the (at most one) user-defined fallback function for a contract. findFallback :: Contract a -> Maybe (FunDef a) findFallback c = listToMaybe [fd | CFunDecl fd <- decls c, isFallback fd] @@ -340,6 +348,15 @@ contractAbiEntries = mapMaybe entry . decls (abiOutputs (sigReturn sig)) (stateMutability (sigPayable sig)) | otherwise = Nothing + entry (CSignatureDecl isPublic sig) + | isPublic = + Just $ + AbiFunction + (nameStr (sigName sig)) + (map abiParam (sigParams sig)) + (abiOutputs (sigReturn sig)) + (stateMutability (sigPayable sig)) + | otherwise = Nothing entry _ = Nothing -- | The ABI @stateMutability@ field admits four values: @pure@, @view@, diff --git a/src/Solcore/Desugarer/DecisionTreeCompiler.hs b/src/Solcore/Desugarer/DecisionTreeCompiler.hs index d774248c5..f09ee618b 100644 --- a/src/Solcore/Desugarer/DecisionTreeCompiler.hs +++ b/src/Solcore/Desugarer/DecisionTreeCompiler.hs @@ -93,6 +93,8 @@ instance Compile (Stmt Id) where (:=) <$> compile e1 <*> compile e2 compile (Let c v mt me) = Let c v mt <$> compile me + compile (LetPattern _ pat _ value) = + compile (Match [value] [([pat], [])]) compile (Block body) = Block <$> compile body compile (StmtExp e) = @@ -302,7 +304,9 @@ instance Compile (Exp Id) where instance Compile (Instance Id) where compile (Instance d vs ps n ts t funs) = Instance d vs ps n ts t - <$> pushCtx ("instance " ++ pretty t ++ " : " ++ pretty n) (compile funs) + <$> pushCtx + ("impl " ++ pretty n ++ "<" ++ intercalate ", " (map pretty (t : ts)) ++ ">") + (compile funs) -- compiling a decision tree into a match diff --git a/src/Solcore/Desugarer/FieldAccess.hs b/src/Solcore/Desugarer/FieldAccess.hs index 9cc4738bb..d1dd64f65 100644 --- a/src/Solcore/Desugarer/FieldAccess.hs +++ b/src/Solcore/Desugarer/FieldAccess.hs @@ -139,6 +139,10 @@ transStmt :: ContractEnv -> NmStmt -> (ContractEnv, NmStmt) transStmt cenv (Let c x mty me) = (cenv {ceLocals = Set.insert x cenv.ceLocals}, Let c x mty me') where me' = flip transRhs cenv <$> me +transStmt cenv (LetPattern ct pat mty value) = + ( cenv {ceLocals = Set.union (Set.fromList (patternBindings pat)) cenv.ceLocals}, + LetPattern ct pat mty (transRhs value cenv) + ) transStmt cenv stmt = (cenv, go stmt cenv) where go :: NmStmt -> CEM NmStmt @@ -156,11 +160,17 @@ transStmt cenv stmt = (cenv, go stmt cenv) body' = transBody body forEnv go (Match es eqns) = traces [pretty (r cenv)] r where r = Match <$> mapM transRhs es <*> mapM transEquation eqns go Let {} = error "Impossible" + go LetPattern {} = error "Impossible" go s@Asm {} = pure s go Break = pure Break go Continue = pure Continue go EmptyStmt = pure EmptyStmt +patternBindings :: Pat Name -> [Name] +patternBindings (PVar n) = [n] +patternBindings (PCon _ pats) = concatMap patternBindings pats +patternBindings _ = [] + -- go s = pure s transEquation :: NmEquation -> CEM NmEquation diff --git a/src/Solcore/Desugarer/IndirectCall.hs b/src/Solcore/Desugarer/IndirectCall.hs index cf30872f1..cdee4d10f 100644 --- a/src/Solcore/Desugarer/IndirectCall.hs +++ b/src/Solcore/Desugarer/IndirectCall.hs @@ -85,6 +85,8 @@ instance Desugar (Stmt Name) where (:=) <$> desugar lhs <*> desugar rhs desugar (Let c n mt me) = Let c n mt <$> desugar me + desugar (LetPattern ct pat mt value) = + LetPattern ct pat mt <$> desugar value desugar (Block body) = Block <$> desugar body desugar (StmtExp e) = @@ -174,6 +176,8 @@ instance Collect (ContractDecl Name) where collect (CFieldDecl _) = [] collect (CFunDecl fd) = [sigName (funSignature fd)] + collect (CSignatureDecl _ sig) = + [sigName sig] collect (CMutualDecl ds) = concatMap collect ds collect (CConstrDecl _) = [] collect _ = [] diff --git a/src/Solcore/Desugarer/UniqueTypeGen.hs b/src/Solcore/Desugarer/UniqueTypeGen.hs index 9aab87d0c..181962c62 100644 --- a/src/Solcore/Desugarer/UniqueTypeGen.hs +++ b/src/Solcore/Desugarer/UniqueTypeGen.hs @@ -48,6 +48,8 @@ instance UniqueTypeGen (Contract Name) where instance UniqueTypeGen (ContractDecl Name) where uniqueTyGen (CFunDecl fd) = uniqueTyGen fd + uniqueTyGen (CSignatureDecl _ sig) = + createUniqueType (sigName sig) uniqueTyGen _ = pure () -- creating a new unique type diff --git a/src/Solcore/Frontend/ComptimeCheck.hs b/src/Solcore/Frontend/ComptimeCheck.hs index 9589c3a02..f1b91262e 100644 --- a/src/Solcore/Frontend/ComptimeCheck.hs +++ b/src/Solcore/Frontend/ComptimeCheck.hs @@ -5,7 +5,7 @@ module Solcore.Frontend.ComptimeCheck (checkComptimeEarly) where Classification uses three states: CTComptime — definitely comptime: literal, comptime-bound variable, or - a call to a function annotated '-> comptime' with all + a call to a function with a 'returns (comptime T)' result and all comptime-param arguments classified as CTComptime. CTRuntime — definitely not comptime: a variable bound by a non-comptime function parameter. @@ -15,7 +15,7 @@ module Solcore.Frontend.ComptimeCheck (checkComptimeEarly) where Errors are reported only for CTRuntime violations: - A parameter annotated 'comptime' receives a CTRuntime argument. - - A 'let x : comptime T = e' binding where e classifies as CTRuntime. + - A 'let comptime x: T = e' binding where e classifies as CTRuntime. CTDeferred values are never rejected here; the MAST-level pass handles them. -} @@ -26,6 +26,7 @@ import Solcore.Frontend.Syntax.Name (Name) import Solcore.Frontend.Syntax.Stmt import Solcore.Frontend.Syntax.Ty import Solcore.Frontend.TypeInference.Id (Id (..)) +import Solcore.Primitives.Primitives (invokableName) ----------------------------------------------------------------------- -- Comptime-ness classification @@ -50,6 +51,7 @@ buildSigTable (CompUnit _ topDecls) = Map.fromList $ concatMap fromTopDecl topDe fromTopDecl _ = [] fromContrDecl (CFunDecl fd) = [(sigName (funSignature fd), funSignature fd)] + fromContrDecl (CSignatureDecl _ sig) = [(sigName sig, sig)] fromContrDecl _ = [] ----------------------------------------------------------------------- @@ -73,6 +75,11 @@ checkTopDecl st (TFunDef fd) = checkFunDef st ctx fd where ctx = "function '" ++ show (sigName (funSignature fd)) ++ "'" checkTopDecl st (TContr c) = mapM_ (checkContrDecl st) (decls c) +-- Generated invokable adapters package arguments into a runtime tuple and no +-- longer retain per-element comptime annotations. Their specialised calls are +-- validated by the later MAST pass, as they were before pattern ctness tracking. +checkTopDecl _ (TInstDef inst) + | instName inst == invokableName = Right () checkTopDecl st (TInstDef inst) = mapM_ (checkFunDefInst st inst) (instFunctions inst) checkTopDecl _ _ = Right () @@ -90,7 +97,7 @@ checkFunDef :: SigTable -> String -> FunDef Id -> Either String () checkFunDef st ctx fd = checkBody st (sigRetComptime sig) ctx initEnv (funDefBody fd) where sig = funSignature fd - -- For '-> comptime' functions, treat ALL params as CTComptime when checking + -- For functions with a comptime result, treat ALL params as CTComptime when checking -- the body: this verifies "given comptime args, does the body produce comptime?" -- For other functions, non-comptime params are CTRuntime. initEnv = @@ -136,16 +143,23 @@ checkStmt st retCt ctx env stmt = case stmt of ++ show (idName x) ++ "' is bound to a runtime expression" return $ Map.insert (idName x) (letCtness ct ct') env + LetPattern ct pat _ value -> do + checkExp st env value + let valueCtness = classifyExp st env value + when_ (ct && valueCtness == CTRuntime) $ + "comptime tuple binding is bound to a runtime expression" + return (bindPatternCtness (letCtness ct valueCtness) pat env) (_ := e) -> checkExp st env e >> return env StmtExp e -> checkExp st env e >> return env Return e -> do checkExp st env e when_ (retCt && classifyExp st env e == CTRuntime) $ - ctx ++ ": function annotated '-> comptime' returns a runtime expression" + ctx ++ ": function with a comptime result returns a runtime expression" return env Match es eqs -> do mapM_ (checkExp st env) es - mapM_ (checkEq st retCt ctx env) eqs + let scrutineeCtness = map (classifyExp st env) es + mapM_ (checkEq st retCt ctx env scrutineeCtness) eqs return env If cond t f -> do checkExp st env cond @@ -170,8 +184,26 @@ letCtness :: Bool -> Ctness -> Ctness letCtness True _ = CTComptime letCtness False ct' = ct' -checkEq :: SigTable -> Bool -> String -> CtEnv -> ([Pat Id], Body Id) -> Either String () -checkEq st retCt ctx env (_, body) = checkBody st retCt ctx env body +checkEq :: SigTable -> Bool -> String -> CtEnv -> [Ctness] -> ([Pat Id], Body Id) -> Either String () +checkEq st retCt ctx env scrutineeCtness (pats, body) = + checkBody st retCt ctx patternEnv body + where + patternEnv = + foldl + (\current (pat, ctness) -> bindPatternCtness ctness pat current) + env + (zip pats scrutineeCtness) + +bindPatternCtness :: Ctness -> Pat Id -> CtEnv -> CtEnv +bindPatternCtness ctness (PVar variable) env = + Map.insert (idName variable) ctness env +bindPatternCtness ctness (PCon _ pats) env = + foldl + (\current pat -> bindPatternCtness ctness pat current) + env + pats +bindPatternCtness _ _ env = + env ----------------------------------------------------------------------- -- Expression checking: recurse and enforce comptime-param constraints @@ -244,8 +276,8 @@ combineCt cts | otherwise = CTDeferred -- | Classify a function call result. --- CTComptime iff the function is annotated '-> comptime' and ALL arguments --- are CTComptime. A non-comptime-annotated param in a '-> comptime' function +-- CTComptime iff the function has a comptime result and ALL arguments +-- are CTComptime. A non-comptime-annotated param in such a function -- means "result is comptime when this arg happens to be comptime", so all args -- must be checked, not just the comptime-annotated ones. -- Never CTRuntime for calls — uncertain cases are deferred to MAST. diff --git a/src/Solcore/Frontend/Lexer/SolcoreLexer.hs b/src/Solcore/Frontend/Lexer/SolcoreLexer.hs index 3042cbcb6..9eea81c41 100644 --- a/src/Solcore/Frontend/Lexer/SolcoreLexer.hs +++ b/src/Solcore/Frontend/Lexer/SolcoreLexer.hs @@ -41,15 +41,21 @@ keyword kw = lexeme (try (string kw *> notFollowedBy identChar)) reservedWords :: [String] reservedWords = [ "contract", + "interface", + "library", "import", "export", "hiding", "as", "let", - "data", - "forall", - "class", - "instance", + "comptime", + "enum", + "struct", + "trait", + "impl", + "where", + "returns", + "is", "if", "else", "for", @@ -59,17 +65,27 @@ reservedWords = "leave", "continue", "break", + "while", + "unchecked", "assembly", "match", "function", "fallback", "payable", "public", + "external", + "internal", + "private", + "pure", + "view", "constructor", "return", "lam", "type", - "pragma" + "pragma", + "solcore", + "solidity", + "abicoder" ] identifier :: Parser String diff --git a/src/Solcore/Frontend/Module/Loader.hs b/src/Solcore/Frontend/Module/Loader.hs index 033ad6c68..685cf34c0 100644 --- a/src/Solcore/Frontend/Module/Loader.hs +++ b/src/Solcore/Frontend/Module/Loader.hs @@ -537,8 +537,8 @@ moduleLocalTypeCheckSurface graph modulePath = do } stubTopDeclBody :: TopDecl -> TopDecl -stubTopDeclBody (TContr (Contract n vs contractDecls)) = - TContr (Contract n vs (map stubContractDeclBody contractDecls)) +stubTopDeclBody (TContr (ContractShell kind n vs contractDecls)) = + TContr (ContractShell kind n vs (map stubContractDeclBody contractDecls)) stubTopDeclBody (TFunDef fd) = TFunDef (stubFunDefBody fd) stubTopDeclBody (TInstDef (Instance d vs predCtx n ts t _funs)) = @@ -1396,7 +1396,17 @@ renameSignatureTypeRefs renameMap sig = { sigVars = map (renameTyTypeRefs renameMap) (sigVars sig), sigContext = map (renamePredTypeRefs renameMap) (sigContext sig), sigParams = map (renameParamTypeRefs renameMap) (sigParams sig), - sigReturn = renameTyTypeRefs renameMap <$> sigReturn sig + sigReturnItems = + fmap + (map (renameReturnItemTypeRefs renameMap)) + (sigReturnItems sig) + } + +renameReturnItemTypeRefs :: Map Name Name -> ReturnItem -> ReturnItem +renameReturnItemTypeRefs renameMap returnItem = + returnItem + { returnItemType = + renameTyTypeRefs renameMap (returnItemType returnItem) } renameParamTypeRefs :: Map Name Name -> Param -> Param @@ -1425,6 +1435,12 @@ renameStmtTypeRefs renameMap (StmtModEq e1 e2) = StmtModEq (renameExpTypeRefs renameMap e1) (renameExpTypeRefs renameMap e2) renameStmtTypeRefs renameMap (Let ct n mt me) = Let ct n (renameTyTypeRefs renameMap <$> mt) (renameExpTypeRefs renameMap <$> me) +renameStmtTypeRefs renameMap (LetPattern ct pat mt value) = + LetPattern + ct + pat + (renameTyTypeRefs renameMap <$> mt) + (renameExpTypeRefs renameMap value) renameStmtTypeRefs renameMap (StmtExp e) = StmtExp (renameExpTypeRefs renameMap e) renameStmtTypeRefs renameMap (Return e) = @@ -1441,6 +1457,12 @@ renameStmtTypeRefs renameMap (If e blk1 blk2) = (renameExpTypeRefs renameMap e) (renameBodyTypeRefs renameMap blk1) (renameBodyTypeRefs renameMap blk2) +renameStmtTypeRefs renameMap (While cond body) = + While + (renameExpTypeRefs renameMap cond) + (renameBodyTypeRefs renameMap body) +renameStmtTypeRefs renameMap (Unchecked body) = + Unchecked (renameBodyTypeRefs renameMap body) renameStmtTypeRefs renameMap (For initStmt cond postStmt body) = For (renameStmtTypeRefs renameMap initStmt) @@ -1508,12 +1530,18 @@ renameExpTypeRefs renameMap (ExpPlus e1 e2) = ExpPlus (renameExpTypeRefs renameMap e1) (renameExpTypeRefs renameMap e2) renameExpTypeRefs renameMap (ExpMinus e1 e2) = ExpMinus (renameExpTypeRefs renameMap e1) (renameExpTypeRefs renameMap e2) +renameExpTypeRefs renameMap (ExpPower e1 e2) = + ExpPower (renameExpTypeRefs renameMap e1) (renameExpTypeRefs renameMap e2) renameExpTypeRefs renameMap (ExpTimes e1 e2) = ExpTimes (renameExpTypeRefs renameMap e1) (renameExpTypeRefs renameMap e2) renameExpTypeRefs renameMap (ExpDivide e1 e2) = ExpDivide (renameExpTypeRefs renameMap e1) (renameExpTypeRefs renameMap e2) renameExpTypeRefs renameMap (ExpModulo e1 e2) = ExpModulo (renameExpTypeRefs renameMap e1) (renameExpTypeRefs renameMap e2) +renameExpTypeRefs renameMap (ExpShiftL e1 e2) = + ExpShiftL (renameExpTypeRefs renameMap e1) (renameExpTypeRefs renameMap e2) +renameExpTypeRefs renameMap (ExpShiftR e1 e2) = + ExpShiftR (renameExpTypeRefs renameMap e1) (renameExpTypeRefs renameMap e2) renameExpTypeRefs renameMap (ExpBXor e1 e2) = ExpBXor (renameExpTypeRefs renameMap e1) (renameExpTypeRefs renameMap e2) renameExpTypeRefs renameMap (ExpBAnd e1 e2) = @@ -1566,8 +1594,9 @@ qualifierNameToExp (QualName q n) = ExpVar (Just (qualifierNameToExp q)) (Name n) renameContractTypeRefs :: Map Name Name -> Contract -> Contract -renameContractTypeRefs renameMap (Contract n ts ds) = - Contract +renameContractTypeRefs renameMap (ContractShell kind n ts ds) = + ContractShell + kind n (map (renameTyTypeRefs renameMap) ts) (map (renameContractDeclTypeRefs renameMap) ds) @@ -1580,6 +1609,8 @@ renameContractDeclTypeRefs renameMap (CFieldDecl (Field n ty me)) = (Field n (renameTyTypeRefs renameMap ty) (renameExpTypeRefs renameMap <$> me)) renameContractDeclTypeRefs renameMap (CFunDecl fd) = CFunDecl (renameFunDefTypeRefs renameMap fd) +renameContractDeclTypeRefs renameMap (CSignatureDecl isPublic sig) = + CSignatureDecl isPublic (renameSignatureTypeRefs renameMap sig) renameContractDeclTypeRefs renameMap (CConstrDecl (Constructor ps body payable)) = CConstrDecl ( Constructor @@ -1610,8 +1641,9 @@ renameInstanceTypeRefs renameMap (Instance d vs ctx n pts mt fns) = (map (renameFunDefTypeRefs renameMap) fns) renameDataTyTypeRefs :: Map Name Name -> DataTy -> DataTy -renameDataTyTypeRefs renameMap (DataTy n vs cs) = - DataTy +renameDataTyTypeRefs renameMap (DataTyWithKind kind n vs cs) = + DataTyWithKind + kind (renameTypeName renameMap n) (map (renameTyTypeRefs renameMap) vs) (map (renameConstrTypeRefs renameMap) cs) @@ -1673,12 +1705,12 @@ qualifiedTypeStubDecls qualifier cunit = where dataAliases = [ TDataDef - ( DataTy + ( validationDataTyStub (qualifyName qualifier n) - [] - [Constr (constructorLeafName (constrName c)) [] | c <- cs] + kind + [Constr (constructorLeafName (constrName c)) (constrTy c) | c <- cs] ) - | TDataDef (DataTy n _ cs) <- topDeclsFrom cunit + | TDataDef (DataTyWithKind kind n _ cs) <- topDeclsFrom cunit ] symAliases = [ TSym (stubType (qualifyName qualifier n)) @@ -1731,14 +1763,27 @@ toValidationImportStub (TSym (TySym n _ _)) = Just (TSym (stubType n)) toValidationImportStub d@(TClassDef _) = Just d -toValidationImportStub (TContr (Contract n _ _)) = - Just (TContr (Contract n [] [])) -toValidationImportStub (TDataDef (DataTy n _ cs)) = - Just (TDataDef (DataTy n [] [Constr (constrName c) [] | c <- cs])) +toValidationImportStub (TContr (ContractShell kind n _ _)) = + Just (TContr (ContractShell kind n [] [])) +toValidationImportStub (TDataDef (DataTyWithKind kind n _ cs)) = + Just (TDataDef (validationDataTyStub n kind cs)) toValidationImportStub (TInstDef _) = Nothing toValidationImportStub (TExportDecl _) = Nothing toValidationImportStub (TPragmaDecl _) = Nothing +-- Validation stubs intentionally erase user-written field types, since their +-- dependencies need not be visible in the importing module. Keep the source +-- declaration kind and constructor arity, however, so a struct remains a +-- struct (with all of its field names) throughout loader transformations. +validationDataTyStub :: Name -> DataTyKind -> [Constr] -> DataTy +validationDataTyStub n kind cs = + DataTyWithKind kind n [] (map stubConstr cs) + where + stubConstr (Constr constrName' fieldTypes) = + Constr + constrName' + (replicate (length fieldTypes) (TyCon (Name "word") [])) + typeCheckQualifiedImportDecls :: Set Name -> ModuleGraph -> (Import, Mod.ModuleId) -> Either String [TopDecl] typeCheckQualifiedImportDecls collidingTypeNames graph (imp, modulePath) = case imp of @@ -2014,11 +2059,11 @@ shadowImportedDecls localDecls = ( (termNames, n : typeNames, classNames, instDecls), Just d ) - filterDecl (termNames, typeNames, classNames, instDecls) (TDataDef (DataTy n ts cs)) + filterDecl (termNames, typeNames, classNames, instDecls) d@(TDataDef (DataTy n _ _)) | n `elem` typeNames = ((termNames, typeNames, classNames, instDecls), Nothing) | otherwise = ( (termNames, n : typeNames, classNames, instDecls), - Just (TDataDef (DataTy n ts cs)) + Just d ) filterDecl (termNames, typeNames, classNames, instDecls) d@(TInstDef inst) | instName inst `elem` localClassNames = ((termNames, typeNames, classNames, instDecls), Nothing) @@ -2094,12 +2139,18 @@ renameTopDeclName oldName newName decl TClassDef (Class defaults vars n params var sigs) | n == oldName -> TClassDef (Class defaults vars newName params var sigs) - TContr (Contract n params contractDecls) + TContr (ContractShell kind n params contractDecls) | n == oldName -> - TContr (Contract newName params contractDecls) - TDataDef (DataTy n params constrs) + TContr (ContractShell kind newName params contractDecls) + TDataDef (DataTyWithKind kind n params constrs) | n == oldName -> - TDataDef (DataTy newName params constrs) + TDataDef + ( DataTyWithKind + kind + newName + params + (renameStructConstructor kind newName constrs) + ) _ -> decl @@ -2128,19 +2179,37 @@ selectTopDeclForExportRef itemRef d@(TContr (Contract n _ _)) Just (renameTopDeclName (exportedItemSourceName itemRef) (exportedItemName itemRef) d) | otherwise = Nothing -selectTopDeclForExportRef itemRef (TDataDef (DataTy n ts cs)) +selectTopDeclForExportRef itemRef (TDataDef (DataTyWithKind kind n ts cs)) | exportedItemSourceName itemRef /= n = Nothing | otherwise = case exportedItemConstructors itemRef of Just visibleConstructors -> - Just (TDataDef (DataTy (exportedItemName itemRef) ts (filterVisibleConstructors visibleConstructors cs))) + Just + ( TDataDef + ( DataTyWithKind + kind + (exportedItemName itemRef) + ts + ( renameStructConstructor + kind + (exportedItemName itemRef) + (filterVisibleConstructors visibleConstructors cs) + ) + ) + ) Nothing -> Nothing selectTopDeclForExportRef _ (TInstDef _) = Nothing selectTopDeclForExportRef _ (TExportDecl _) = Nothing selectTopDeclForExportRef _ (TPragmaDecl _) = Nothing +renameStructConstructor :: DataTyKind -> Name -> [Constr] -> [Constr] +renameStructConstructor (StructKind _) newName [Constr _ fieldTypes] = + [Constr newName fieldTypes] +renameStructConstructor _ _ constrs = + constrs + filterVisibleConstructors :: [Name] -> [Constr] -> [Constr] filterVisibleConstructors visibleConstructors = filter (\constr -> constructorLeafName (constrName constr) `elem` visibleConstructors) diff --git a/src/Solcore/Frontend/Parser/Decl.hs b/src/Solcore/Frontend/Parser/Decl.hs index 13c8a9168..6f2f0f96b 100644 --- a/src/Solcore/Frontend/Parser/Decl.hs +++ b/src/Solcore/Frontend/Parser/Decl.hs @@ -11,12 +11,12 @@ import Data.List.NonEmpty qualified as NE import Solcore.Frontend.Lexer.SolcoreLexer import Solcore.Frontend.Parser.Expr (exprP) import Solcore.Frontend.Parser.SolcoreTypes - ( atomTypeP, - paramP, + ( paramP, qualifiedName, - sigPrefixP, simpleNameP, + typeParamsP, typeP, + whereClauseP, ) import Solcore.Frontend.Parser.Stmt (bodyP) import Solcore.Frontend.Syntax.Name @@ -34,54 +34,39 @@ compUnitP = do expP :: Parser Exp expP = exprP bodyP -withSigPrefix :: ([Ty] -> [Pred] -> Parser a) -> Parser a -withSigPrefix k = do - (vars, ctx) <- option ([], []) (try sigPrefixP) - k vars ctx - importP :: Parser Import importP = do keyword "import" choice - [ do - path <- externalPathP - choice - [ do - _ <- symbol "." - entries <- braces (itemEntryP `sepBy` comma) - hids <- option [] hidingP <* semicolon - return (ImportOnly path (SelectItems entries hids)), - do - keyword "as" - n <- simpleNameP - _ <- semicolon - return (ImportAlias path n), - ImportModule path <$ semicolon - ], + [ try $ do + _ <- symbol "*" + keyword "as" + aliasName <- simpleNameP + keyword "from" + path <- importPathP + _ <- semicolon + pure (ImportAlias path aliasName), + try $ do + entries <- braces (itemEntryP `sepBy` comma) + keyword "from" + path <- importPathP + hiddenNames <- option [] hidingP + _ <- semicolon + pure (ImportOnly path (SelectItems entries hiddenNames)), do - path <- modulePathP - choice - [ do - _ <- symbol "." - entries <- braces (itemEntryP `sepBy` comma) - hids <- option [] hidingP - _ <- semicolon - return (ImportOnly path (SelectItems entries hids)), - do - keyword "as" - n <- simpleNameP - _ <- semicolon - return (ImportAlias path n), - ImportModule path <$ semicolon - ] + path <- importPathP + ImportModule path <$ semicolon ] where hidingP = keyword "hiding" *> braces (simpleNameP `sepBy` comma) +importPathP :: Parser ModulePath +importPathP = try externalPathP <|> modulePathP + modulePathP :: Parser ModulePath modulePathP = do h <- identifier - ts <- many (try (char '.' *> notFollowedBy (char '{') *> identifier)) + ts <- many (try (char '.' *> identifier)) return (classifyModulePath (foldl QualName (Name h) ts)) externalPathP :: Parser ModulePath @@ -89,7 +74,7 @@ externalPathP = do lib <- symbol "@" *> identifier <* char '.' sc h <- identifier - ts <- many (try (char '.' *> notFollowedBy (char '{') *> identifier)) + ts <- many (try (char '.' *> identifier)) return (ExternalPath (Name lib) (foldl QualName (Name h) ts)) classifyModulePath :: Name -> ModulePath @@ -181,21 +166,34 @@ constrSelectorP = pragmaP :: Parser Pragma pragmaP = do keyword "pragma" - ty <- pragmaTypeP - st <- pragmaStatusForP ty - _ <- semicolon - return (Pragma ty st) + choice + [ do + keyword "solcore" + ty <- pragmaTypeP + st <- pragmaStatusForP ty + _ <- semicolon + pure (Pragma ty st), + externalPragmaP "solidity" SolidityPragma, + externalPragmaP "abicoder" AbiCoderPragma + ] + +externalPragmaP :: String -> (String -> PragmaType) -> Parser Pragma +externalPragmaP namespace pragmaConstructor = do + keyword namespace + pragmaValue <- unwords . words <$> manyTill anySingle (char ';') + sc + pure (Pragma (pragmaConstructor pragmaValue) Enabled) pragmaTypeP :: Parser PragmaType pragmaTypeP = NoCoverageCondition - <$ keyword "no-coverage-condition" + <$ keyword "noCoverageCondition" <|> NoPattersonCondition - <$ keyword "no-patterson-condition" + <$ keyword "noPattersonCondition" <|> NoBoundVariableCondition - <$ keyword "no-bounded-variable-condition" + <$ keyword "noBoundVariableCondition" <|> NoGenericInstanceFor - <$ keyword "no-generic-instance-for" + <$ keyword "noGenericInstanceFor" -- | Parse the pragma status. For 'NoGenericInstanceFor' a non-empty list of -- type names is mandatory; for all other pragma types the list is optional and @@ -208,15 +206,36 @@ pragmaStatusForP _ = option DisableAll $ do names <- simpleNameP `sepBy1` comma return (DisableFor (NE.fromList names)) -dataP :: Parser DataTy -dataP = do - keyword "data" +enumP :: Parser DataTy +enumP = do + keyword "enum" n <- simpleNameP - params <- option [] (parens (typeP `sepBy1` comma)) - cs <- option [] (equalsP *> (constrP `sepBy1` symbol "|")) - _ <- semicolon + params <- typeParamsP + cs <- braces (constrP `sepEndBy` comma) return (DataTy n params cs) +structP :: Parser DataTy +structP = do + keyword "struct" + n <- simpleNameP + params <- typeParamsP + fields <- braces (many structFieldP) + pure + ( StructTy + n + params + (map fst fields) + (map snd fields) + ) + +structFieldP :: Parser (Name, Ty) +structFieldP = do + fieldName' <- simpleNameP + _ <- colon + fieldType <- typeP + _ <- semicolon + pure (fieldName', fieldType) + constrP :: Parser Constr constrP = do n <- simpleNameP @@ -227,153 +246,185 @@ tySymP :: Parser TySym tySymP = do keyword "type" n <- simpleNameP - params <- option [] (parens (typeP `sepBy1` comma)) - _ <- equalsP + params <- typeParamsP + keyword "is" t <- typeP _ <- semicolon return (TySym n params t) --- Instance methods live outside a contract, so they may not carry the --- contract-only modifiers ('public' / 'payable'). +functionModifierP :: Parser FunctionModifier +functionModifierP = + choice + [ VisibilityModifier VisibilityPublic <$ keyword "public", + VisibilityModifier VisibilityExternal <$ keyword "external", + VisibilityModifier VisibilityInternal <$ keyword "internal", + VisibilityModifier VisibilityPrivate <$ keyword "private", + MutabilityModifier MutabilityPure <$ keyword "pure", + MutabilityModifier MutabilityView <$ keyword "view", + MutabilityModifier MutabilityPayable <$ keyword "payable" + ] + +parseFunctionModifiers :: Bool -> Parser (Bool, [FunctionModifier]) +parseFunctionModifiers allowContractModifiers = do + modifiers <- many functionModifierP + let visibility = [v | VisibilityModifier v <- modifiers] + mutability = [m | MutabilityModifier m <- modifiers] + isPublic = + any + (`elem` [VisibilityPublic, VisibilityExternal]) + visibility + isPayable = MutabilityPayable `elem` mutability + when (length visibility > 1) $ + fail "a function may declare at most one visibility modifier" + when (length mutability > 1) $ + fail "a function may declare at most one mutability modifier" + when (not allowContractModifiers && (not (null visibility) || isPayable)) $ + fail "visibility and `payable` modifiers are only allowed on contract functions" + pure (isPublic, modifiers) + funDefP :: Parser FunDef -funDefP = try $ withSigPrefix (funDefAfterPrefix False) - --- | Parse a function definition after its optional signature prefix. --- 'allowContractModifiers' controls whether the leading `public` and `payable` --- modifiers are accepted: both are only meaningful inside a `contract { … }` --- body, so callers outside a contract (top-level functions, instance methods) --- pass 'False' and an explicit modifier is rejected with a clear error. -funDefAfterPrefix :: Bool -> [Ty] -> [Pred] -> Parser FunDef -funDefAfterPrefix allowContractModifiers vars ctx = do - isPub <- publicModifierP allowContractModifiers - sig <- signatureP allowContractModifiers vars ctx +funDefP = funDefWithModifiers False + +funDefWithModifiers :: Bool -> Parser FunDef +funDefWithModifiers allowContractModifiers = do + (isPublic, sig) <- signatureP allowContractModifiers body <- braces bodyP - return (FunDef isPub sig (implicitReturn body)) - --- | Parse an optional `public` visibility modifier. When 'allowPublic' is --- 'False' (anywhere outside a contract body), an explicit `public` is rejected --- with a clear error rather than being silently accepted. -publicModifierP :: Bool -> Parser Bool -publicModifierP allowPublic = do - isPub <- option False (True <$ try (keyword "public")) - when (isPub && not allowPublic) $ - fail "'public' is only allowed on functions declared inside a contract" - return isPub - -implicitReturn :: Body -> Body -implicitReturn [StmtExp e] = [Return e] -implicitReturn stmts = stmts - --- | Parse an optional @payable@ modifier. @payable@ is only meaningful on a --- function, the constructor, or the fallback *inside a contract*; callers in --- any other context pass @allowPayable = False@ so we reject it with a clear --- error instead of silently accepting it. -payableP :: Bool -> Parser Bool -payableP allowPayable = - option False $ do - keyword "payable" - if allowPayable - then pure True - else fail "`payable` is only allowed on a function, constructor, or fallback inside a contract" - -signatureP :: Bool -> [Ty] -> [Pred] -> Parser Signature -signatureP allowPayable vars ctx = do - payable <- payableP allowPayable + pure (FunDef isPublic sig body) + +signatureP :: Bool -> Parser (Bool, Signature) +signatureP allowContractModifiers = do keyword "function" n <- simpleNameP + vars <- typeParamsP ps <- parens (paramP `sepBy` comma) - (rc, ret) <- option (False, Nothing) $ do - _ <- symbol "->" - ct <- option False (True <$ keyword "comptime") - t <- typeP - return (ct, Just t) - return (Signature vars ctx n ps rc ret payable) - -fallbackDefAfterPrefix :: [Ty] -> [Pred] -> Parser FunDef -fallbackDefAfterPrefix vars ctx = do - sig <- fallbackSignatureP vars ctx - body <- braces bodyP - return (FunDef False sig (implicitReturn body)) - -fallbackSignatureP :: [Ty] -> [Pred] -> Parser Signature -fallbackSignatureP vars ctx = do - payable <- payableP True + (isPublic, modifiers) <- parseFunctionModifiers allowContractModifiers + returnItems <- optional returnsClauseP + ctx <- whereClauseP + pure + ( isPublic, + SignatureWithSyntax vars ctx n ps returnItems modifiers + ) + +returnsClauseP :: Parser [ReturnItem] +returnsClauseP = do + keyword "returns" + parens (returnItemP `sepBy` comma) + +returnItemP :: Parser ReturnItem +returnItemP = do + isComptime <- option False (True <$ keyword "comptime") + returnName <- optional (try (simpleNameP <* colon)) + ReturnItem isComptime returnName <$> typeP + +fallbackDefP :: Parser FunDef +fallbackDefP = do keyword "fallback" ps <- parens (paramP `sepBy` comma) - case ps of - [] -> pure () - _ -> fail "fallback function must not declare input parameters" - ret <- optional (symbol "->" *> typeP) - case ret of - Nothing -> pure () - Just (TyCon (Name "()") []) -> pure () - Just _ -> fail "fallback function must return unit (`()`)" - return (Signature vars ctx (Name "fallback") ps False ret payable) - --- | One function signature inside a class body. --- Commits to requiring ';' once the signature is parsed, so a missing --- semicolon produces "expecting ';' after function signature" rather than --- the confusing "unexpected 'f', expecting '}'". -classSigP :: Parser Signature -classSigP = do - sig <- try (withSigPrefix (signatureP False)) + when (not (null ps)) $ + fail "fallback function must not declare input parameters" + modifiers <- many functionModifierP + let visibility = [v | VisibilityModifier v <- modifiers] + mutability = [m | MutabilityModifier m <- modifiers] + when (visibility /= [VisibilityExternal]) $ + fail "fallback must declare exactly one `external` visibility modifier" + when (length mutability > 1 || any (`elem` [MutabilityPure, MutabilityView]) mutability) $ + fail "fallback only supports the `payable` mutability modifier" + body <- braces bodyP + let sig = + SignatureWithSyntax + [] + [] + (Name "fallback") + [] + Nothing + modifiers + pure (FunDef False sig body) + +traitSignatureP :: Parser Signature +traitSignatureP = do + (isPublic, sig) <- signatureP False + when isPublic $ + fail "trait methods cannot have contract visibility" _ <- semicolon "';' after function signature" - return sig - -classAfterPrefix :: [Ty] -> [Pred] -> Parser Class -classAfterPrefix vars ctx = do - keyword "class" - mty <- atomTypeP - _ <- colon - cname <- qualifiedName - params <- option [] (parens (typeP `sepBy1` comma)) - sigs <- braces (many classSigP) - return (Class vars ctx cname params mty sigs) - -instanceAfterPrefix :: [Ty] -> [Pred] -> Parser Instance -instanceAfterPrefix vars ctx = do + pure sig + +traitP :: Parser Class +traitP = do + keyword "trait" + traitName <- qualifiedName + vars <- typeParamsP + (primaryVar, params) <- case vars of + [] -> fail "a trait must declare at least one type parameter" + primaryTy : extraParams -> pure (primaryTy, extraParams) + ctx <- whereClauseP + sigs <- braces (many traitSignatureP) + pure (Class vars ctx traitName params primaryVar sigs) + +implP :: Parser Instance +implP = do isDefault <- option False (True <$ keyword "default") - keyword "instance" - mty <- atomTypeP - _ <- colon - iname <- qualifiedName - params <- option [] (parens (typeP `sepBy1` comma)) + keyword "impl" + vars <- typeParamsP + implName <- qualifiedName + args <- between (symbol "<") (symbol ">") (typeP `sepBy1` comma) + (primaryTy, params) <- case args of + [] -> fail "an impl must supply at least one trait type argument" + mainArg : extraArgs -> pure (mainArg, extraArgs) + ctx <- whereClauseP funs <- braces (many funDefP) - return (Instance isDefault vars ctx iname params mty funs) + pure (Instance isDefault vars ctx implName params primaryTy funs) contractP :: Parser Contract contractP = do keyword "contract" n <- simpleNameP - params <- option [] (parens (typeP `sepBy1` comma)) + params <- typeParamsP ds <- braces (many contractDeclP) - return (Contract n params ds) + return (ContractShell ContractKind n params ds) + +interfaceP :: Parser Contract +interfaceP = do + keyword "interface" + n <- simpleNameP + params <- typeParamsP + ds <- braces (many interfaceDeclP) + return (ContractShell InterfaceKind n params ds) + +libraryP :: Parser Contract +libraryP = do + keyword "library" + n <- simpleNameP + params <- typeParamsP + ds <- braces (many libraryDeclP) + return (ContractShell LibraryKind n params ds) contractDeclP :: Parser ContractDecl contractDeclP = CDataDecl - <$> dataP + <$> (try structP <|> enumP) <|> CConstrDecl <$> try constructorDeclP - <|> rejectPublicOnImplicitlyPublicP - <|> withSigPrefix - ( \vars ctx -> - CFunDecl - <$> (try (funDefAfterPrefix True vars ctx) <|> fallbackDefAfterPrefix vars ctx) - ) + <|> CFunDecl + <$> try fallbackDefP + <|> CFunDecl + <$> try (funDefWithModifiers True) <|> CFieldDecl <$> fieldDeclP --- | `fallback` and `constructor` are implicitly public; reject an explicit --- `public` modifier on them with a clear error rather than a confusing --- parser failure. -rejectPublicOnImplicitlyPublicP :: Parser a -rejectPublicOnImplicitlyPublicP = do - kw <- try $ do - _ <- keyword "public" - _ <- optional (keyword "payable") - ("fallback" <$ keyword "fallback") <|> ("constructor" <$ keyword "constructor") - fail (kw ++ " is implicitly public; remove the 'public' keyword") +interfaceDeclP :: Parser ContractDecl +interfaceDeclP = do + (isPublic, sig) <- signatureP True + _ <- semicolon "';' after interface function signature" + pure (CSignatureDecl isPublic sig) + +libraryDeclP :: Parser ContractDecl +libraryDeclP = + CDataDecl + <$> (try structP <|> enumP) + <|> CFunDecl + <$> try (funDefWithModifiers True) + <|> CFieldDecl + <$> fieldDeclP fieldDeclP :: Parser Field fieldDeclP = do @@ -386,29 +437,27 @@ fieldDeclP = do constructorDeclP :: Parser Constructor constructorDeclP = do - payable <- option False (True <$ keyword "payable") keyword "constructor" ps <- parens (paramP `sepBy` comma) + modifiers <- many functionModifierP + when (any (/= MutabilityModifier MutabilityPayable) modifiers || length modifiers > 1) $ + fail "constructor only supports the `payable` modifier" body <- braces bodyP - return (Constructor ps body payable) + return (Constructor ps body (MutabilityModifier MutabilityPayable `elem` modifiers)) topDeclP :: Parser TopDecl topDeclP = choice [ TPragmaDecl <$> pragmaP, TExportDecl <$> exportP, - TDataDef <$> dataP, + TDataDef <$> structP, + TDataDef <$> enumP, TSym <$> tySymP, - TContr <$> contractP, + TContr <$> (contractP <|> interfaceP <|> libraryP), contractOnlyDeclP, - withSigPrefix - ( \vars ctx -> - choice - [ TFunDef <$> funDefAfterPrefix False vars ctx, - TClassDef <$> classAfterPrefix vars ctx, - TInstDef <$> instanceAfterPrefix vars ctx - ] - ) + TFunDef <$> try funDefP, + TClassDef <$> try traitP, + TInstDef <$> implP ] -- | @constructor@ and @fallback@ declarations are only meaningful inside a diff --git a/src/Solcore/Frontend/Parser/Expr.hs b/src/Solcore/Frontend/Parser/Expr.hs index b81a69563..eb0fc1b53 100644 --- a/src/Solcore/Frontend/Parser/Expr.hs +++ b/src/Solcore/Frontend/Parser/Expr.hs @@ -7,8 +7,7 @@ import Common.LightYear import Control.Monad.Combinators.Expr import Solcore.Diagnostics (SourceSpan) import Solcore.Frontend.Lexer.SolcoreLexer -import Solcore.Frontend.Parser.Patterns (patListP) -import Solcore.Frontend.Parser.SolcoreTypes (atomTypeP, locatedFromSpans, locatedP, paramP, simpleNameP, typeP) +import Solcore.Frontend.Parser.SolcoreTypes (locatedFromSpans, locatedP, paramP, simpleNameP, typeP) import Solcore.Frontend.Syntax.Location (sourceSpanOf) import Solcore.Frontend.Syntax.Name import Solcore.Frontend.Syntax.SyntaxTree @@ -16,46 +15,43 @@ import Solcore.Frontend.Syntax.SyntaxTree type BodyP = Parser [Stmt] exprP :: BodyP -> Parser Exp -exprP bp = tyAnnP bp +exprP = ternaryP -tyAnnP :: BodyP -> Parser Exp -tyAnnP bp = do - e <- ternaryP bp - option e $ do - t <- colon *> typeP - pure (locatedExpFrom [sourceSpanOf e, sourceSpanOf t] (TyExp e t)) +castP :: BodyP -> Parser Exp +castP bp = do + e <- unaryP bp + targets <- many (keyword "as" *> typeP) + pure (foldl cast e targets) + where + cast value target = + locatedExpFrom [sourceSpanOf value, sourceSpanOf target] (TyExp value target) + +unaryP :: BodyP -> Parser Exp +unaryP bp = do + operators <- many logicalNotP + operand <- postfixP bp + pure (foldr ($) operand operators) + where + logicalNotP = + unaryExp ExpLNot + <$ try (lexeme (char '!' <* notFollowedBy (char '='))) ternaryP :: BodyP -> Parser Exp -ternaryP bp = - try (ifThenElseP bp) <|> do - e1 <- binaryP bp - option e1 $ do - _ <- symbol "?" - e2 <- ternaryP bp - _ <- symbol ":" - e3 <- ternaryP bp - return (locatedExpFrom (map sourceSpanOf [e1, e2, e3]) (ExpCond e1 e2 e3)) - -ifThenElseP :: BodyP -> Parser Exp -ifThenElseP bp = locatedP locatedExp $ do - keyword "if" - e1 <- ternaryP bp - keyword "then" - e2 <- ternaryP bp - keyword "else" - e3 <- ternaryP bp - return (ExpCond e1 e2 e3) +ternaryP bp = do + e1 <- binaryP bp + option e1 $ do + _ <- symbol "?" + e2 <- ternaryP bp + _ <- symbol ":" + e3 <- ternaryP bp + return (locatedExpFrom (map sourceSpanOf [e1, e2, e3]) (ExpCond e1 e2 e3)) binaryP :: BodyP -> Parser Exp -binaryP bp = makeExprParser (postfixP bp) opTable +binaryP bp = makeExprParser (castP bp) opTable opTable :: [[Operator Parser Exp]] opTable = - [ [ Prefix - ( unaryExp ExpLNot - <$ try (lexeme (char '!' <* notFollowedBy (char '='))) - ) - ], + [ [InfixR (binaryExp ExpPower <$ try (symbol "**"))], [ InfixL (binaryExp ExpTimes <$ try (symbol "*")), InfixL (binaryExp ExpDivide <$ try (symbol "/")), InfixL @@ -72,6 +68,9 @@ opTable = <$ try (lexeme (char '-' <* notFollowedBy (char '='))) ) ], + [ InfixL (binaryExp ExpShiftL <$ try (symbol "<<")), + InfixL (binaryExp ExpShiftR <$ try (symbol ">>")) + ], [ InfixL ( binaryExp ExpBAnd <$ try (lexeme (char '&' <* notFollowedBy (char '&') <* notFollowedBy (char '='))) @@ -85,13 +84,7 @@ opTable = [ InfixL ( binaryExp ExpBOr <$ try - ( lexeme (char '|' <* notFollowedBy (char '|') <* notFollowedBy (char '=')) - -- `|` also separates match arms (`| pat => ...`). Since `=>` - -- never follows a bitwise-or operand, treat `|` as a case - -- separator (not an operator) whenever `pat =>` comes next, - -- leaving it for the match-equation parser to consume. - <* notFollowedBy (try (patListP *> symbol "=>")) - ) + (lexeme (char '|' <* notFollowedBy (char '|') <* notFollowedBy (char '='))) ) ], [ InfixN (binaryExp ExpLE <$ try (symbol "<=")), @@ -137,7 +130,7 @@ idxOp bp = do return (\e -> locatedExpFrom [sourceSpanOf e, sourceSpanOf idx] (ExpIndexed e idx)) atomP :: BodyP -> Parser Exp -atomP bp = litP <|> try (lamP bp) <|> proxyP <|> try (dotNameP bp) <|> parenP bp <|> nameP bp +atomP bp = litP <|> try (lamP bp) <|> try (dotNameP bp) <|> parenP bp <|> nameP bp litP :: Parser Exp litP = @@ -152,13 +145,16 @@ lamP :: BodyP -> Parser Exp lamP bp = locatedP locatedExp $ do keyword "lam" ps <- parens (paramP `sepBy` comma) - retTy <- optional (symbol "->" *> typeP) + retTy <- optional $ do + keyword "returns" + ts <- parens (typeP `sepBy` comma) + pure $ case ts of + [] -> TyCon "()" [] + [t] -> t + _ -> foldr1 pairTy ts body <- braces bp return (Lam ps body retTy) -proxyP :: Parser Exp -proxyP = locatedP locatedExp (ExpAt <$> (symbol "@" *> atomTypeP)) - dotNameP :: BodyP -> Parser Exp dotNameP bp = locatedP locatedExp $ do _ <- char '.' diff --git a/src/Solcore/Frontend/Parser/Patterns.hs b/src/Solcore/Frontend/Parser/Patterns.hs index c7f28f909..2200a091f 100644 --- a/src/Solcore/Frontend/Parser/Patterns.hs +++ b/src/Solcore/Frontend/Parser/Patterns.hs @@ -1,12 +1,15 @@ module Solcore.Frontend.Parser.Patterns ( patP, patListP, + bindingTuplePatP, ) where import Common.LightYear +import Control.Monad (when) +import Data.Set qualified as Set import Solcore.Frontend.Lexer.SolcoreLexer -import {-# SOURCE #-} Solcore.Frontend.Parser.Expr (exprP) +import Solcore.Frontend.Parser.Expr (exprP) import Solcore.Frontend.Parser.SolcoreTypes (locatedP, qualifiedName, simpleNameP) import Solcore.Frontend.Syntax.Name import Solcore.Frontend.Syntax.SyntaxTree @@ -17,6 +20,39 @@ patP = locatedP locatedPat (wildcardP <|> litP <|> dotPatP <|> parenPatP <|> try patListP :: Parser [Pat] patListP = patP `sepBy1` comma +-- | A local destructuring binding is deliberately narrower than a match +-- pattern: every leaf is a fresh name (or @_@), and the outer shape must be a +-- tuple. In particular, constructors and literal patterns are not accepted. +bindingTuplePatP :: Parser Pat +bindingTuplePatP = do + pat <- locatedP locatedPat bindingTupleRawP + let names = bindingNames pat + when (length names /= Set.size (Set.fromList names)) $ + fail "duplicate names in destructuring binding" + pure pat + +bindingNames :: Pat -> [Name] +bindingNames PWildcard = [] +bindingNames (Pat n []) = [n] +bindingNames (Pat _ ps) = concatMap bindingNames ps +bindingNames _ = [] + +bindingPatP :: Parser Pat +bindingPatP = + locatedP locatedPat (wildcardP <|> bindingTupleRawP <|> bindingNameP) + +bindingTupleRawP :: Parser Pat +bindingTupleRawP = parens $ do + ps <- bindingPatP `sepBy1` comma + when (length ps < 2) $ + fail "a destructuring binding requires at least two tuple elements" + pure (Pat (Name "pair") ps) + +bindingNameP :: Parser Pat +bindingNameP = do + n <- simpleNameP + pure (Pat n []) + wildcardP :: Parser Pat wildcardP = PWildcard <$ lexeme (string "_" <* notFollowedBy (alphaNumChar <|> char '_')) diff --git a/src/Solcore/Frontend/Parser/SolcoreTypes.hs b/src/Solcore/Frontend/Parser/SolcoreTypes.hs index c6a49963a..42c291271 100644 --- a/src/Solcore/Frontend/Parser/SolcoreTypes.hs +++ b/src/Solcore/Frontend/Parser/SolcoreTypes.hs @@ -5,7 +5,8 @@ module Solcore.Frontend.Parser.SolcoreTypes predP, predListP, paramP, - sigPrefixP, + typeParamsP, + whereClauseP, simpleNameP, locatedP, locatedFromSpans, @@ -13,7 +14,6 @@ module Solcore.Frontend.Parser.SolcoreTypes where import Common.LightYear -import Control.Monad.Combinators.Expr import Data.Foldable (foldlM) import Solcore.Diagnostics (SourceSpan (..)) import Solcore.Frontend.Lexer.SolcoreLexer @@ -43,19 +43,38 @@ locatedIdentifierP = do pure (sourceSpanBetween startOffset startPos endOffset endPos, identifierText) typeP :: Parser Ty -typeP = locatedP locatedTy (makeExprParser atomTypeP [[InfixR (mkArrowTy <$ symbol "->")]]) - where - mkArrowTy t1 t2 = - locatedFromSpans locatedTy [sourceSpanOf t1, sourceSpanOf t2] (TyCon "->" [t1, t2]) +typeP = locatedP locatedTy postfixTypeP atomTypeP :: Parser Ty -atomTypeP = locatedP locatedTy (proxyTypeP <|> parenTypeP <|> namedTypeP) - -proxyTypeP :: Parser Ty -proxyTypeP = TyCon "Proxy" . (: []) <$> (symbol "@" *> atomTypeP) +atomTypeP = locatedP locatedTy (mappingTypeP <|> parenTypeP <|> namedTypeP) + +postfixTypeP :: Parser Ty +postfixTypeP = do + base <- functionTypeP <|> atomTypeP + suffixes <- many typeSuffixP + pure (foldl (flip ($)) base suffixes) + +typeSuffixP :: Parser (Ty -> Ty) +typeSuffixP = + choice + [ do + size <- brackets (optional arraySizeP) + pure $ \elementTy -> + case size of + Nothing -> TyCon "array" [elementTy] + Just sizeTy -> TyCon "array" [sizeTy, elementTy], + TyCon "memory" . (: []) <$ keyword "memory", + TyCon "storage" . (: []) <$ keyword "storage", + TyCon "calldata" . (: []) <$ keyword "calldata" + ] + +arraySizeP :: Parser Ty +arraySizeP = + (do n <- integer; pure (TyCon (Name (show n)) [])) + <|> typeP namedTypeP :: Parser Ty -namedTypeP = TyCon <$> qualifiedName <*> option [] (parens (typeP `sepBy1` comma)) +namedTypeP = TyCon <$> qualifiedName <*> option [] (angles (typeP `sepBy1` comma)) parenTypeP :: Parser Ty parenTypeP = parens (mkParenTy <$> (typeP `sepBy` comma)) @@ -64,12 +83,39 @@ parenTypeP = parens (mkParenTy <$> (typeP `sepBy` comma)) mkParenTy [t] = t mkParenTy ts = foldr1 pairTy ts +mappingTypeP :: Parser Ty +mappingTypeP = do + keyword "mapping" + (keyTy, valueTy) <- parens $ do + keyTy <- typeP + _ <- symbol "=>" + valueTy <- typeP + pure (keyTy, valueTy) + pure (TyCon "mapping" [keyTy, valueTy]) + +functionTypeP :: Parser Ty +functionTypeP = do + keyword "function" + args <- parens (typeP `sepBy` comma) + visibility <- + optional + ( FunctionTypeInternal <$ keyword "internal" + <|> FunctionTypeExternal <$ keyword "external" + ) + results <- optional returnsTypeP + pure (FunctionTy args visibility results) + +returnsTypeP :: Parser [Ty] +returnsTypeP = do + keyword "returns" + parens (typeP `sepBy` comma) + predP :: Parser Pred predP = do - subjectTy <- atomTypeP + subjectTy <- typeP _ <- colon cls <- qualifiedName - params <- option [] (parens (typeP `sepBy1` comma)) + params <- option [] (angles (typeP `sepBy1` comma)) return (InCls cls subjectTy params) predListP :: Parser [Pred] @@ -84,16 +130,19 @@ paramP = do Just t -> Typed ct n t Nothing -> Untyped ct n -sigPrefixP :: Parser ([Ty], [Pred]) -sigPrefixP = do - keyword "forall" - vars <- some (tyVar <* optional comma) - _ <- symbol "." - ctx <- option [] $ try (predListP <* symbol "=>") - return (vars, ctx) +typeParamsP :: Parser [Ty] +typeParamsP = + option [] (angles (tyVar `sepBy1` comma)) where tyVar = locatedP locatedTy (flip TyCon [] <$> simpleNameP) +whereClauseP :: Parser [Pred] +whereClauseP = + option [] (keyword "where" *> predListP) + +angles :: Parser a -> Parser a +angles = between (symbol "<") (symbol ">") + locatedP :: (SourceSpan -> a -> a) -> Parser a -> Parser a locatedP locate parser = do startPos <- getSourcePos diff --git a/src/Solcore/Frontend/Parser/Stmt.hs b/src/Solcore/Frontend/Parser/Stmt.hs index eb39b2da3..e0ec41a71 100644 --- a/src/Solcore/Frontend/Parser/Stmt.hs +++ b/src/Solcore/Frontend/Parser/Stmt.hs @@ -5,11 +5,11 @@ module Solcore.Frontend.Parser.Stmt where import Common.LightYear -import Control.Monad (void) +import Control.Monad (void, when) import Language.Yul.Parser (yulBlock) import Solcore.Frontend.Lexer.SolcoreLexer import Solcore.Frontend.Parser.Expr (exprP) -import Solcore.Frontend.Parser.Patterns (patListP) +import Solcore.Frontend.Parser.Patterns (bindingTuplePatP, patP) import Solcore.Frontend.Parser.SolcoreTypes (locatedP, simpleNameP, typeP) import Solcore.Frontend.Syntax.SyntaxTree @@ -25,10 +25,13 @@ stmtP = <|> returnP <|> try ifP <|> forP + <|> whileP <|> breakP <|> continueP <|> matchP <|> asmP + <|> uncheckedP + <|> try revertP <|> blockP <|> try exprOrAssignP @@ -41,18 +44,29 @@ continueP = locatedP locatedStmt (Continue <$ (keyword "continue" *> semicolon)) letP :: Parser Stmt letP = locatedP locatedStmt $ do keyword "let" - n <- simpleNameP - (ct, mt) <- option (False, Nothing) $ do - _ <- colon - ct <- option False (True <$ keyword "comptime") - t <- typeP - return (ct, Just t) - me <- optional (equalsP *> expP) + ct <- option False (True <$ keyword "comptime") + stmt <- try (tupleLetRemainder ct) <|> simpleLetRemainder ct _ <- semicolon - return (Let ct n mt me) + pure stmt + where + simpleLetRemainder ct = do + n <- simpleNameP + mt <- optional (colon *> typeP) + me <- optional (equalsP *> expP) + pure (Let ct n mt me) + + tupleLetRemainder ct = do + pat <- bindingTuplePatP + mt <- optional (colon *> typeP) + value <- equalsP *> expP + pure (LetPattern ct pat mt value) returnP :: Parser Stmt -returnP = locatedP locatedStmt (Return <$> (keyword "return" *> expP <* semicolon)) +returnP = locatedP locatedStmt $ do + keyword "return" + value <- option (ExpName Nothing "()" []) expP + _ <- semicolon + pure (Return value) ifP :: Parser Stmt ifP = locatedP locatedStmt $ do @@ -75,16 +89,32 @@ forP = locatedP locatedStmt $ do body <- braces bodyP return (For initS cond postS body) +whileP :: Parser Stmt +whileP = locatedP locatedStmt $ do + keyword "while" + cond <- parens expP + body <- braces bodyP + pure (While cond body) + matchP :: Parser Stmt matchP = locatedP locatedStmt $ do keyword "match" - scrutinees <- expP `sepBy1` comma - eqns <- braces (many equationP) + scrutinees <- parens (expP `sepBy1` comma) + eqns <- braces (many (equationP (length scrutinees))) return (Match scrutinees eqns) asmP :: Parser Stmt asmP = locatedP locatedStmt (Asm <$> (keyword "assembly" *> yulBlock)) -- yulBlock includes the surrounding braces +uncheckedP :: Parser Stmt +uncheckedP = + locatedP locatedStmt (Unchecked <$> (keyword "unchecked" *> braces bodyP)) + +revertP :: Parser Stmt +revertP = + locatedP locatedStmt + (StmtExp (ExpName Nothing "revert" []) <$ (keyword "revert" *> semicolon)) + blockP :: Parser Stmt blockP = locatedP locatedStmt (Block <$> braces bodyP) @@ -99,7 +129,7 @@ exprOrAssignP = locatedP locatedStmt $ do do rhs <- symbol "&=" *> expP; _ <- semicolon; return (StmtBAndEq lhs rhs), do rhs <- symbol "|=" *> expP; _ <- semicolon; return (StmtBOrEq lhs rhs), do rhs <- symbol "%=" *> expP; _ <- semicolon; return (StmtModEq lhs rhs), - StmtExp lhs <$ optional semicolon + StmtExp lhs <$ semicolon ] forInitP :: Parser Stmt @@ -121,12 +151,9 @@ forPostP = locatedP locatedStmt $ do forLetP :: Parser Stmt forLetP = locatedP locatedStmt $ do keyword "let" + ct <- option False (True <$ keyword "comptime") n <- simpleNameP - (ct, mt) <- option (False, Nothing) $ do - _ <- colon - ct <- option False (True <$ keyword "comptime") - t <- typeP - return (ct, Just t) + mt <- optional (colon *> typeP) me <- optional (equalsP *> expP) return (Let ct n mt me) @@ -144,8 +171,27 @@ forAssignP = locatedP locatedStmt $ do return (StmtExp lhs) ] -equationP :: Parser Equation -equationP = (,) <$> (symbol "|" *> patListP) <*> (symbol "=>" *> bodyP) +equationP :: Int -> Parser Equation +equationP arity = + caseEquationP arity <|> defaultEquationP arity + +caseEquationP :: Int -> Parser Equation +caseEquationP arity = do + keyword "case" + pats <- + if arity == 1 + then (: []) <$> patP + else parens (patP `sepBy1` comma) + when (length pats /= arity) $ + fail "case pattern count must match the number of match scrutinees" + body <- braces bodyP + pure (pats, body) + +defaultEquationP :: Int -> Parser Equation +defaultEquationP arity = do + keyword "default" + body <- braces bodyP + pure (replicate arity PWildcard, body) equalsP :: Parser () equalsP = void $ try (lexeme (char '=' <* notFollowedBy (char '='))) diff --git a/src/Solcore/Frontend/Pretty/ShortName.hs b/src/Solcore/Frontend/Pretty/ShortName.hs index 46c24d6aa..d83ec8338 100644 --- a/src/Solcore/Frontend/Pretty/ShortName.hs +++ b/src/Solcore/Frontend/Pretty/ShortName.hs @@ -29,7 +29,9 @@ instance (HasShortName a) => HasShortName (FunDef a) where instance (HasShortName a) => HasShortName (Instance a) where shortName (Instance _d _vs _ctx n ts t _funs) = do - unwords ["instance", pretty (InCls n t ts)] + unwords ["impl", pretty n ++ "<" ++ commaSepTypes (t : ts) ++ ">"] + where + commaSepTypes = foldr1 (\left right -> left ++ ", " ++ right) . map pretty instance HasShortName Pred where shortName p = unwords ["constraint", pretty p] diff --git a/src/Solcore/Frontend/Pretty/SolcorePretty.hs b/src/Solcore/Frontend/Pretty/SolcorePretty.hs index a0abfccea..c331d33c2 100644 --- a/src/Solcore/Frontend/Pretty/SolcorePretty.hs +++ b/src/Solcore/Frontend/Pretty/SolcorePretty.hs @@ -37,15 +37,19 @@ instance (Pretty a) => Pretty (CompUnit a) where instance Pretty Import where ppr (ImportModule path) = - text "import" <+> ppr path <+> semi + text "import" <+> (ppr path <> semi) ppr (ImportAlias path asName) = - hsep [text "import", ppr path, text "as", ppr asName, semi] - ppr (ImportOnly path items) = + hsep [text "import", text "*", text "as", ppr asName, text "from", ppr path] <> semi + ppr (ImportOnly path (SelectItems items hidden)) = hsep - [ text "import", - ppr path <> text ".", - pprItemSelector items <> semi - ] + ( [ text "import", + pprItemSelector items, + text "from", + ppr path + ] + ++ pprHiding hidden + ) + <> semi instance Pretty ModulePath where ppr (RelativePath path) = ppr path @@ -105,14 +109,14 @@ instance Pretty ExportSelectorEntry where ppr (SelectExportConstructors typeName ctorSelector) = ppr typeName <> parens (ppr ctorSelector) -pprItemSelector :: ItemSelector -> Doc -pprItemSelector (SelectItems items hidden) = - base <> pprHiding hidden - where - base = lbrace <> commaSep (map ppr items) <> rbrace - pprHiding [] = empty - pprHiding names = - space <> (text "hiding" <+> (lbrace <> commaSep (map ppr names) <> rbrace)) +pprItemSelector :: [ItemSelectorEntry] -> Doc +pprItemSelector items = + lbrace <> commaSep (map ppr items) <> rbrace + +pprHiding :: [Name] -> [Doc] +pprHiding [] = [] +pprHiding names = + [text "hiding", lbrace <> commaSep (map ppr names) <> rbrace] instance Pretty ItemSelectorEntry where ppr SelectAllItems = text "*" @@ -125,15 +129,23 @@ exportSelectorIsOnlyWildcard (SelectExportItems [SelectExportAllItems]) = True exportSelectorIsOnlyWildcard _ = False instance Pretty Pragma where + ppr (Pragma (SolidityPragma version) Enabled) = + hsep [text "pragma", text "solidity", text version] <> semi + ppr (Pragma (AbiCoderPragma version) Enabled) = + hsep [text "pragma", text "abicoder", text version] <> semi ppr (Pragma _ Enabled) = empty ppr (Pragma ty st) = - hsep [text "pragma", ppr ty, ppr st, semi] + hsep [text "pragma", text "solcore", ppr ty, ppr st] <> semi instance Pretty PragmaType where - ppr NoBoundVariableCondition = text "no-bounded-variable-condition" - ppr NoCoverageCondition = text "no-coverage-condition" - ppr NoPattersonCondition = text "no-patterson-condition" - ppr NoGenericInstanceFor = text "no-generic-instance-for" + ppr NoBoundVariableCondition = text "noBoundVariableCondition" + ppr NoCoverageCondition = text "noCoverageCondition" + ppr NoPattersonCondition = text "noPattersonCondition" + ppr NoGenericInstanceFor = text "noGenericInstanceFor" + ppr (SolidityPragma version) = + hsep [text "solidity", text version] + ppr (AbiCoderPragma version) = + hsep [text "abicoder", text version] instance Pretty PragmaStatus where ppr (DisableFor ns) = @@ -143,8 +155,7 @@ instance Pretty PragmaStatus where instance (Pretty a) => Pretty (Contract a) where ppr (Contract n ts ds) = text "contract" - <+> ppr n - <+> pprTyParams (map TyVar ts) + <+> (ppr n <> pprTyParams (map TyVar ts)) <+> lbrace $$ nest 3 (vcat (map ppr ds)) $$ rbrace @@ -156,6 +167,8 @@ instance (Pretty a) => Pretty (ContractDecl a) where ppr fd ppr (CFunDecl fd) = ppr fd + ppr (CSignatureDecl isExternal sig) = + pprContractSignature isExternal sig <> semi ppr (CMutualDecl ds) = vcat (map ppr ds) ppr (CConstrDecl c) = @@ -163,52 +176,43 @@ instance (Pretty a) => Pretty (ContractDecl a) where instance (Pretty a) => Pretty (Constructor a) where ppr (Constructor ps bd payable) = - (if payable then text "payable" <+> text "constructor" else text "constructor") - <+> pprParams ps + (text "constructor" <> pprParams ps) + <+> pprPayable payable <+> lbrace $$ nest 3 (vcat (map ppr bd)) $$ rbrace instance Pretty DataTy where ppr (DataTy n ps cs) = - text "data" - <+> ppr n - <+> pprTyParams (map TyVar ps) - <+> rs - <+> text ";" - where - rs = - if null cs - then empty - else - equals <+> hsep (punctuate bar (map ppr cs)) - bar = text " |" + text "enum" + <+> (ppr n <> pprTyParams (map TyVar ps)) + <+> lbrace + $$ nest 3 (vcat (punctuate comma (map ppr cs))) + $$ rbrace instance Pretty TySym where ppr (TySym n vs t) = - text "type" - <+> ppr n - <+> pprTyParams (map TyVar vs) - <+> text "=" - <+> ppr t + ( text "type" + <+> (ppr n <> pprTyParams (map TyVar vs)) + <+> text "is" + <+> ppr t + ) + <> semi instance Pretty Constr where - ppr (Constr n []) = ppr n <> text " " + ppr (Constr n []) = ppr (constructorLeafName n) ppr (Constr n ts) = - ppr n <> parens (pprConstrArgs ts) + ppr (constructorLeafName n) <> parens (pprConstrArgs ts) pprConstrArgs :: [Ty] -> Doc pprConstrArgs [] = empty pprConstrArgs ts = commaSep $ map ppr ts instance (Pretty a) => Pretty (Class a) where - ppr (Class bvs ps n vs v sigs) = - pprSigPrefix bvs ps - <+> text "class " - <+> ppr v - <+> colon - <+> ppr n - <+> pprTyParams (TyVar <$> vs) + ppr (Class _ ps n vs v sigs) = + text "trait" + <+> (ppr n <> pprTyParams (TyVar <$> (v : vs))) + <+> pprWhere ps <+> lbrace $$ nest 3 (pprSignatures sigs) $$ rbrace @@ -218,31 +222,15 @@ pprSignatures = vcat . map ((<> semi) . ppr) instance (Pretty a) => Pretty (Signature a) where - ppr (Signature vs ctx n ps rc ty pay) = - pprSigPrefix vs ctx - <+> (if pay then text "payable" else empty) - <+> text "function" - <+> ppr n - <+> pprParams ps - <+> pprRetTy rc ty - -pprSigPrefix :: [Tyvar] -> [Pred] -> Doc -pprSigPrefix [] [] = empty -pprSigPrefix [] ps = pprContext ps -pprSigPrefix vs [] = - text "forall" <+> hsep (map ppr vs) <+> text "." -pprSigPrefix vs ps = - text "forall" <+> hsep (map ppr vs) <+> text "." $$ pprContext ps + ppr = pprSignature False instance (Pretty a) => Pretty (Instance a) where ppr (Instance d vs ctx n tys ty funs) = - pprSigPrefix vs ctx - <+> pprDefault d - <> text "instance" - <+> ppr ty - <+> colon - <+> ppr n - <+> pprTyParams tys + pprDefault d + <> text "impl" + <> pprTyParams (map TyVar vs) + <+> (ppr n <> pprTyParams (ty : tys)) + <+> pprWhere ctx <+> lbrace $$ nest 3 (pprFunBlock funs) $$ rbrace @@ -255,6 +243,11 @@ pprContext [] = empty pprContext ps = (commaSep $ map ppr ps) <+> text "=>" +pprWhere :: [Pred] -> Doc +pprWhere [] = empty +pprWhere ps = + text "where" <+> commaSep (map ppr ps) + instance Pretty [Pred] where ppr = parens . commaSepList @@ -264,21 +257,64 @@ pprFunBlock = instance (Pretty a) => Pretty (Field a) where ppr (Field n ty e) = - ppr n <+> colon <+> (ppr ty) <+> pprInitOpt e + ((ppr n <> colon) <+> ppr ty) <> pprInitOpt e instance (Pretty a) => Pretty (Body a) where ppr = vcat . map ppr instance (Pretty a) => Pretty (FunDef a) where ppr (FunDef isPub sig bd) = - ((if isPub then text "public " else empty) <> ppr sig) + pprSignature isPub sig <+> lbrace $$ nest 3 (vcat (map ppr bd)) $$ rbrace +pprSignature :: (Pretty a) => Bool -> Signature a -> Doc +pprSignature isPub (Signature vs ctx n ps rc ty pay) + | n == Name "fallback" = + (text "fallback" <> pprParams ps) + <+> text "external" + <+> pprPayable pay + | otherwise = + text "function" + <+> (ppr n <> pprTyParams (map TyVar vs) <> pprParams ps) + <+> pprFunctionModifiers isPub pay + <+> pprRetTy rc ty + <+> pprWhere ctx + +pprContractSignature :: (Pretty a) => Bool -> Signature a -> Doc +pprContractSignature isExternal (Signature vs ctx n ps rc ty pay) = + text "function" + <+> (ppr n <> pprTyParams (map TyVar vs) <> pprParams ps) + <+> hsep + ( [text "external" | isExternal] + ++ [text "payable" | pay] + ) + <+> pprRetTy rc ty + <+> pprWhere ctx + +pprFunctionModifiers :: Bool -> Bool -> Doc +pprFunctionModifiers isPub payable = + hsep + ( [text "public" | isPub] + ++ [text "payable" | payable] + ) + +pprPayable :: Bool -> Doc +pprPayable True = text "payable" +pprPayable False = empty + pprRetTy :: Bool -> Maybe Ty -> Doc pprRetTy _ Nothing = empty -pprRetTy rc (Just t) = text "->" <+> pprConst rc <> ppr t +pprRetTy True (Just t) = + text "returns" <+> parens (text "comptime" <+> ppr t) +pprRetTy False (Just t) = + text "returns" <+> parens (pprReturnItems t) + +pprReturnItems :: Ty -> Doc +pprReturnItems t@(TyCon n _) + | isTuple n = commaSep (map ppr (tupleElements t)) +pprReturnItems t = ppr t pprParams :: (Pretty a) => [Param a] -> Doc pprParams = parens . commaSep . map ppr @@ -287,30 +323,43 @@ pprConst :: Bool -> Doc pprConst True = text "comptime " pprConst False = empty +pprComptime :: Bool -> Doc +pprComptime True = text "comptime" +pprComptime False = empty + instance (Pretty a) => Pretty (Param a) where ppr (Typed c n ty) = - pprConst c <> (ppr n <+> colon <+> ppr ty) + pprConst c <> ((ppr n <> colon) <+> ppr ty) ppr (Untyped c n) = pprConst c <> ppr n instance (Pretty a) => Pretty (Stmt a) where ppr (n := e) = - ppr n <+> equals <+> ppr e <+> semi + ppr n <+> equals <+> (ppr e <> semi) ppr (Let c n ty m) = - text "let" <+> ppr n <+> pprOptTy c ty <+> pprInitOpt m + ( text "let" + <+> pprComptime c + <+> (ppr n <> pprOptTy ty) + ) + <> pprInitOpt m + ppr (LetPattern ct pat ty value) = + (text "let" <+> pprComptime ct <+> (ppr pat <> pprOptTy ty)) + <> pprInitOpt (Just value) ppr (Block body) = lbrace $$ nest 3 (ppr body) $$ rbrace - ppr (StmtExp e) = - ppr e <> semi - ppr (Return e) = - text "return" <+> ppr e <> semi + ppr (StmtExp e) + | isBareRevert e = text "revert" <> semi + | otherwise = ppr e <> semi + ppr (Return e) + | isUnitExp e = text "return" <> semi + | otherwise = text "return" <+> (ppr e <> semi) ppr (Match e eqns) = text "match" <+> (parens $ commaSep $ map ppr e) <+> lbrace - $$ vcat (map ppr eqns) + $$ nest 3 (vcat (map ppr eqns)) $$ rbrace ppr (Asm yblk) = text "assembly" @@ -339,7 +388,16 @@ instance (Pretty a) => Pretty (Stmt a) where pprForClause :: (Pretty a) => Stmt a -> Doc pprForClause (n := e) = ppr n <+> equals <+> ppr e -pprForClause (Let ct n ty m) = text "let" <+> ppr n <+> pprOptTy ct ty <+> pprForInitOpt m +pprForClause (Let ct n ty m) = + text "let" + <+> pprComptime ct + <+> (ppr n <> pprOptTy ty) + <+> pprForInitOpt m +pprForClause (LetPattern ct pat ty value) = + text "let" + <+> pprComptime ct + <+> (ppr pat <> pprOptTy ty) + <+> pprForInitOpt (Just value) pprForClause (StmtExp e) = ppr e pprForClause (Block stmts) = hsep (punctuate comma (map pprForClause stmts)) pprForClause EmptyStmt = empty @@ -350,26 +408,35 @@ pprForInitOpt Nothing = empty pprForInitOpt (Just e) = equals <+> ppr e instance (Pretty a) => Pretty (Equation a) where - ppr (p, ss) = - text "|" - <+> commaSep (map ppr p) - <+> text "=>" - $$ nest 3 (vcat (map ppr ss)) + ppr (ps, ss) + | not (null ps) && all isWildcardPat ps = + text "default" + <+> lbrace + $$ nest 3 (vcat (map ppr ss)) + $$ rbrace + | otherwise = + text "case" + <+> pprCasePatterns ps + <+> lbrace + $$ nest 3 (vcat (map ppr ss)) + $$ rbrace instance (Pretty a) => Pretty (Equations a) where ppr = vcat . map ppr -pprOptTy :: Bool -> Maybe Ty -> Doc -pprOptTy _ Nothing = empty -pprOptTy c (Just t) +pprCasePatterns :: (Pretty a) => [Pat a] -> Doc +pprCasePatterns [pat] = ppr pat +pprCasePatterns pats = parens (commaSep (map ppr pats)) + +isWildcardPat :: Pat a -> Bool +isWildcardPat PWildcard = True +isWildcardPat _ = False + +pprOptTy :: Maybe Ty -> Doc +pprOptTy Nothing = empty +pprOptTy (Just t) | isVar t = empty - | otherwise = case splitTy t of - ([], t') -> text ":" <+> pprConst c <> ppr t' - (ts', t') -> - text ":" - <+> parens (commaSep (map ppr ts')) - <+> text "->" - <+> ppr t' + | otherwise = colon <+> ppr t isVar :: Ty -> Bool isVar (TyVar _) = True @@ -377,37 +444,103 @@ isVar _ = False pprInitOpt :: (Pretty a) => Maybe (Exp a) -> Doc pprInitOpt Nothing = semi -pprInitOpt (Just e) = equals <+> ppr e <+> semi +pprInitOpt (Just e) = + space <> (equals <+> ppr e) <> semi + +parensWhen :: Bool -> Doc -> Doc +parensWhen True d = parens d +parensWhen _ d = d instance (Pretty a) => Pretty (Exp a) where - ppr (Var v) = ppr v - ppr (Con n es) - | isTuple n = parens $ commaSep (map ppr es) - | otherwise = - ppr n - <> if null es - then empty - else (parens (nest 1 $ commaSep $ map ppr es)) - ppr (Lit l) = ppr l - ppr (Call e n es) = - pprE e <> ppr n <> (parens (nest 1 $ commaSep $ map ppr es)) - ppr (Lam args bd _) = - text "lam" - <+> pprParams args + ppr = pprTypedExpPrec lowestTypedExpPrec + +lowestTypedExpPrec, ternaryTypedExpPrec, castTypedExpPrec :: Int +postfixTypedExpPrec, atomTypedExpPrec :: Int +lowestTypedExpPrec = 0 +ternaryTypedExpPrec = 10 +castTypedExpPrec = 110 +postfixTypedExpPrec = 130 +atomTypedExpPrec = 140 + +pprTypedExpPrec :: (Pretty a) => Int -> Exp a -> Doc +pprTypedExpPrec context expression = + parensWhen + (typedExpPrecedence expression < context) + (pprTypedExpNode expression) + +pprTypedExpNode :: (Pretty a) => Exp a -> Doc +pprTypedExpNode (Var v) = ppr v +pprTypedExpNode expression@(Con n [_, _]) + | isTuple n = + parens + ( commaSep + (map (pprTypedExpPrec lowestTypedExpPrec) (typedTupleExpElements expression)) + ) +pprTypedExpNode (Con n []) + | isUnitConstructorName n = text "()" + | otherwise = ppr n +pprTypedExpNode (Con n es) = + ppr n + <> parens + (nest 1 $ commaSep $ map (pprTypedExpPrec lowestTypedExpPrec) es) +pprTypedExpNode (Lit l) = ppr l +pprTypedExpNode (Call Nothing n es) = + ppr n + <> parens + (nest 1 $ commaSep $ map (pprTypedExpPrec lowestTypedExpPrec) es) +pprTypedExpNode (Call (Just receiver) n es) = + pprTypedExpPrec postfixTypedExpPrec receiver + <> char '.' + <> ppr n + <> parens + (nest 1 $ commaSep $ map (pprTypedExpPrec lowestTypedExpPrec) es) +pprTypedExpNode (Lam args bd lambdaRetTy) = + (text "lam" <> pprParams args) + <+> pprRetTy False lambdaRetTy <+> lbrace $$ nest 3 (vcat (map ppr bd)) $$ rbrace - ppr (TyExp e ty) = - ppr e <+> text ":" <+> ppr ty - ppr (FieldAccess me n) = maybe (text "this") ppr me <> char '.' <> ppr n - ppr (Cond e1 e2 e3) = hsep [text "if", ppr e1, text "then", ppr e2, text "else", ppr e3] - ppr (Indexed e1 e2) = ppr e1 <> brackets (ppr e2) +pprTypedExpNode (TyExp e ty) = + pprTypedExpPrec castTypedExpPrec e <+> text "as" <+> ppr ty +pprTypedExpNode (FieldAccess Nothing n) = + text "this" <> char '.' <> ppr n +pprTypedExpNode (FieldAccess (Just receiver) n) = + pprTypedExpPrec postfixTypedExpPrec receiver <> char '.' <> ppr n +pprTypedExpNode (Cond condition thenExpression elseExpression) = + hsep + [ pprTypedExpPrec (ternaryTypedExpPrec + 1) condition, + text "?", + pprTypedExpPrec ternaryTypedExpPrec thenExpression, + colon, + pprTypedExpPrec ternaryTypedExpPrec elseExpression + ] +pprTypedExpNode (Indexed collection index) = + pprTypedExpPrec postfixTypedExpPrec collection + <> brackets (pprTypedExpPrec lowestTypedExpPrec index) -- ppr e = text $ "Pretty.ppr not implemented for\n" ++ show(pShow e) -pprE :: (Pretty a) => Maybe (Exp a) -> Doc -pprE Nothing = "" -pprE (Just e) = ppr e <> text "." +typedExpPrecedence :: Exp a -> Int +typedExpPrecedence (Cond _ _ _) = ternaryTypedExpPrec +typedExpPrecedence (TyExp _ _) = castTypedExpPrec +typedExpPrecedence (Call (Just _) _ _) = postfixTypedExpPrec +typedExpPrecedence (FieldAccess _ _) = postfixTypedExpPrec +typedExpPrecedence (Indexed _ _) = postfixTypedExpPrec +typedExpPrecedence _ = atomTypedExpPrec + +typedTupleExpElements :: (Pretty a) => Exp a -> [Exp a] +typedTupleExpElements (Con n [left, right]) + | isTuple n = left : typedTupleExpElements right +typedTupleExpElements expression = [expression] + +isUnitConstructorName :: (Pretty a) => a -> Bool +isUnitConstructorName constructorName = + rendered == "()" + || rendered == "unit" + || "()<" `isPrefixOf` rendered + || "unit<" `isPrefixOf` rendered + where + rendered = pretty constructorName instance (Pretty a) => Pretty (Pat a) where ppr (PVar n) = @@ -425,7 +558,17 @@ instance (Pretty a) => Pretty (Pat a) where instance Pretty Literal where ppr (IntLit l) = integer (toInteger l) - ppr (StrLit l) = quotes (text l) + ppr (StrLit l) = pprStringLiteral l + +pprStringLiteral :: String -> Doc +pprStringLiteral = doubleQuotes . text . concatMap escapeStringChar + +escapeStringChar :: Char -> String +escapeStringChar '\\' = "\\\\" +escapeStringChar '"' = "\\\"" +escapeStringChar '\n' = "\\n" +escapeStringChar '\t' = "\\t" +escapeStringChar c = [c] instance Pretty Tyvar where ppr (TVar n) = ppr n @@ -433,7 +576,7 @@ instance Pretty Tyvar where instance Pretty Pred where ppr (InCls n t ts) = - ppr t <+> colon <+> ppr n <+> pprTyParams ts + (ppr t <> colon) <+> (ppr n <> pprTyParams ts) ppr (t1 :~: t2) = ppr t1 <+> text "~" <+> ppr t2 @@ -456,25 +599,70 @@ instance Pretty MetaTv where instance Pretty Ty where ppr (TyVar v) = ppr v ppr (Meta v) = ppr v - ppr (t1@(_ :-> _) :-> t2) = - parens (ppr t1) <+> text "->" <+> ppr t2 - ppr (t1 :-> t2) = - ppr t1 <+> (text "->") <+> ppr t2 - ppr (TyCon n ts) - | isTuple n = parens $ commaSep (map ppr ts) + ppr t@(_ :-> _) = + let (args, ret) = splitTy t + in (text "function" <> parens (commaSep (map ppr args))) + <+> text "internal" + <+> pprRetTy False (Just ret) + ppr (TyCon n [keyTy, valueTy]) + | n == Name "mapping" = + text "mapping" + <> parens (ppr keyTy <+> text "=>" <+> ppr valueTy) + ppr (TyCon n [elementTy]) + | n == Name "array" = + ppr elementTy <> brackets empty + ppr (TyCon n [sizeTy, elementTy]) + | n == Name "array" = + ppr elementTy <> brackets (ppr sizeTy) + ppr (TyCon n [t]) + | isDataLocation n = ppr t <+> ppr n + ppr t@(TyCon n _) + | isTuple n = parens $ commaSep (map ppr (tupleElements t)) | isUnit n = text "()" - | otherwise = ppr n <> (pprTyParams ts) + ppr (TyCon n ts) = + ppr n <> pprTyParams ts isUnit :: Name -> Bool -isUnit n = pretty n == "unit" +isUnit n = + n == Name "unit" || n == Name "()" isTuple :: (Pretty a) => a -> Bool isTuple s = pretty s == "pair" +isDataLocation :: Name -> Bool +isDataLocation n = + n `elem` [Name "memory", Name "storage", Name "calldata"] + +tupleElements :: Ty -> [Ty] +tupleElements (TyCon n [left, right]) + | isTuple n = left : tupleElements right +tupleElements t = [t] + pprTyParams :: [Ty] -> Doc pprTyParams [] = empty pprTyParams ts = - parens (commaSep (map ppr ts)) + angles (commaSep (map ppr ts)) + +constructorLeafName :: Name -> Name +constructorLeafName (QualName _ leaf) = Name leaf +constructorLeafName n = n + +isUnitExp :: (Pretty a) => Exp a -> Bool +isUnitExp (Con n []) = + rendered == "()" + || rendered == "unit" + || "()<" `isPrefixOf` rendered + || "unit<" `isPrefixOf` rendered + where + rendered = pretty n +isUnitExp _ = False + +isBareRevert :: (Pretty a) => Exp a -> Bool +isBareRevert (Call Nothing n []) = + rendered == "revert" || "revert<" `isPrefixOf` rendered + where + rendered = pretty n +isBareRevert _ = False instance Pretty Subst where ppr = braces . commaSep . map go . Map.toList . unSubst diff --git a/src/Solcore/Frontend/Pretty/TreePretty.hs b/src/Solcore/Frontend/Pretty/TreePretty.hs index 44bfeb339..99b006a97 100644 --- a/src/Solcore/Frontend/Pretty/TreePretty.hs +++ b/src/Solcore/Frontend/Pretty/TreePretty.hs @@ -1,6 +1,6 @@ {-# OPTIONS_GHC -Wno-orphans #-} -module Solcore.Frontend.Pretty.TreePretty where +module Solcore.Frontend.Pretty.TreePretty (pretty, isTuple) where import Common.Pretty import Data.List.NonEmpty qualified as N @@ -16,15 +16,19 @@ instance Pretty CompUnit where instance Pretty Import where ppr (ImportModule path) = - text "import" <+> ppr path <+> semi + text "import" <+> (ppr path <> semi) ppr (ImportAlias path asName) = - hsep [text "import", ppr path, text "as", ppr asName, semi] - ppr (ImportOnly path items) = + hsep [text "import", text "*", text "as", ppr asName, text "from", ppr path] <> semi + ppr (ImportOnly path (SelectItems items hidden)) = hsep - [ text "import", - ppr path <> text ".", - pprItemSelector items <> semi - ] + ( [ text "import", + pprItemSelector items, + text "from", + ppr path + ] + ++ pprHiding hidden + ) + <> semi instance Pretty ModulePath where ppr (RelativePath path) = ppr path @@ -82,14 +86,14 @@ instance Pretty ExportSelectorEntry where ppr (SelectExportConstructors typeName ctorSelector) = ppr typeName <> parens (ppr ctorSelector) -pprItemSelector :: ItemSelector -> Doc -pprItemSelector (SelectItems items hidden) = - base <> pprHiding hidden - where - base = lbrace <> commaSep (map ppr items) <> rbrace - pprHiding [] = empty - pprHiding names = - space <> (text "hiding" <+> (lbrace <> commaSep (map ppr names) <> rbrace)) +pprItemSelector :: [ItemSelectorEntry] -> Doc +pprItemSelector items = + lbrace <> commaSep (map ppr items) <> rbrace + +pprHiding :: [Name] -> [Doc] +pprHiding [] = [] +pprHiding names = + [text "hiding", lbrace <> commaSep (map ppr names) <> rbrace] instance Pretty ItemSelectorEntry where ppr SelectAllItems = text "*" @@ -102,15 +106,23 @@ exportSelectorIsOnlyWildcard (SelectExportItems [SelectExportAllItems]) = True exportSelectorIsOnlyWildcard _ = False instance Pretty Pragma where + ppr (Pragma (SolidityPragma version) Enabled) = + hsep [text "pragma", text "solidity", text version] <> semi + ppr (Pragma (AbiCoderPragma version) Enabled) = + hsep [text "pragma", text "abicoder", text version] <> semi ppr (Pragma _ Enabled) = empty ppr (Pragma ty st) = - hsep [text "pragma", ppr ty, ppr st, semi] + hsep [text "pragma", text "solcore", ppr ty, ppr st] <> semi instance Pretty PragmaType where - ppr NoBoundVariableCondition = text "no-bounded-variable-condition" - ppr NoCoverageCondition = text "no-coverage-condition" - ppr NoPattersonCondition = text "no-patterson-condition" - ppr NoGenericInstanceFor = text "no-generic-instance-for" + ppr NoBoundVariableCondition = text "noBoundVariableCondition" + ppr NoCoverageCondition = text "noCoverageCondition" + ppr NoPattersonCondition = text "noPattersonCondition" + ppr NoGenericInstanceFor = text "noGenericInstanceFor" + ppr (SolidityPragma version) = + hsep [text "solidity", text version] + ppr (AbiCoderPragma version) = + hsep [text "abicoder", text version] instance Pretty PragmaStatus where ppr (DisableFor ns) = @@ -118,14 +130,18 @@ instance Pretty PragmaStatus where ppr _ = empty instance Pretty Contract where - ppr (Contract n ts ds) = - text "contract" - <+> ppr n - <+> pprTyParams ts + ppr (ContractShell kind n ts ds) = + pprContractKind kind + <+> (ppr n <> pprTyParams ts) <+> lbrace $$ nest 3 (vcat (map ppr ds)) $$ rbrace +pprContractKind :: ContractKind -> Doc +pprContractKind ContractKind = text "contract" +pprContractKind InterfaceKind = text "interface" +pprContractKind LibraryKind = text "library" + instance Pretty ContractDecl where ppr (CDataDecl dt) = ppr dt @@ -133,57 +149,60 @@ instance Pretty ContractDecl where ppr fd ppr (CFunDecl fd) = ppr fd + ppr (CSignatureDecl isPublic sig) = + pprInterfaceSignature isPublic sig <> semi ppr (CConstrDecl c) = ppr c instance Pretty Constructor where ppr (Constructor ps bd payable) = - (if payable then text "payable" <+> text "constructor" else text "constructor") - <+> pprParams ps + (text "constructor" <> pprParams ps) + <+> pprPayable payable <+> lbrace $$ nest 3 (vcat (map ppr bd)) $$ rbrace instance Pretty DataTy where + ppr (StructTy n ps fieldNames fieldTypes) = + text "struct" + <+> (ppr n <> pprTyParams ps) + <+> lbrace + $$ nest 3 (vcat (zipWith pprStructField fieldNames fieldTypes)) + $$ rbrace ppr (DataTy n ps cs) = - text "data" - <+> ppr n - <+> pprTyParams ps - <+> rs - <+> text ";" - where - rs = - if null cs - then empty - else - equals <+> hsep (punctuate bar (map ppr cs)) - bar = text " |" + text "enum" + <+> (ppr n <> pprTyParams ps) + <+> lbrace + $$ nest 3 (vcat (punctuate comma (map ppr cs))) + $$ rbrace + +pprStructField :: Name -> Ty -> Doc +pprStructField fieldName' fieldType = + ((ppr fieldName' <> colon) <+> ppr fieldType) <> semi instance Pretty TySym where ppr (TySym n vs t) = - text "type" - <+> ppr n - <+> pprTyParams vs - <+> text "=" - <+> ppr t + ( text "type" + <+> (ppr n <> pprTyParams vs) + <+> text "is" + <+> ppr t + ) + <> semi instance Pretty Constr where - ppr (Constr n []) = ppr n <> text " " + ppr (Constr n []) = ppr (constructorLeafName n) ppr (Constr n ts) = - ppr n <> parens (pprConstrArgs ts) + ppr (constructorLeafName n) <> parens (pprConstrArgs ts) pprConstrArgs :: [Ty] -> Doc pprConstrArgs [] = empty pprConstrArgs ts = commaSep $ map ppr ts instance Pretty Class where - ppr (Class bvs ps n vs v sigs) = - pprSigPrefix bvs ps - <+> text "class " - <+> ppr v - <+> colon - <+> ppr n - <+> pprTyParams vs + ppr (Class _ ps n vs v sigs) = + text "trait" + <+> (ppr n <> pprTyParams (v : vs)) + <+> pprWhere ps <+> lbrace $$ nest 3 (pprSignatures sigs) $$ rbrace @@ -193,31 +212,13 @@ pprSignatures = vcat . map ((<> semi) . ppr) instance Pretty Signature where - ppr (Signature vs ctx n ps rc ty pay) = - pprSigPrefix vs ctx - <+> (if pay then text "payable" else empty) - <+> text "function" - <+> ppr n - <+> pprParams ps - <+> pprRetTy rc ty - -pprSigPrefix :: [Ty] -> [Pred] -> Doc -pprSigPrefix [] [] = empty -pprSigPrefix [] ps = pprContext ps -pprSigPrefix vs [] = - text "forall" <+> hsep (map ppr vs) <+> text "." -pprSigPrefix vs ps = - text "forall" <+> hsep (map ppr vs) <+> text "." $$ pprContext ps + ppr = pprSignature False instance Pretty Instance where ppr (Instance d vs ctx n tys ty funs) = - pprSigPrefix vs ctx - <+> pprDefault d - <+> text "instance" - <+> ppr ty - <+> colon - <+> ppr n - <+> pprTyParams tys + (pprDefault d <> text "impl" <> pprTyParams vs) + <+> (ppr n <> pprTyParams (ty : tys)) + <+> pprWhere ctx <+> lbrace $$ nest 3 (pprFunBlock funs) $$ rbrace @@ -225,10 +226,10 @@ instance Pretty Instance where pprDefault :: Bool -> Doc pprDefault b = if b then text "default " else empty -pprContext :: [Pred] -> Doc -pprContext [] = empty -pprContext ps = - (commaSep $ map ppr ps) <+> text "=>" +pprWhere :: [Pred] -> Doc +pprWhere [] = empty +pprWhere ps = + text "where" <+> commaSep (map ppr ps) instance Pretty [Pred] where ppr = parens . commaSepList @@ -239,21 +240,94 @@ pprFunBlock = instance Pretty Field where ppr (Field n ty e) = - ppr n <+> colon <+> (ppr ty) <+> pprInitOpt e + ((ppr n <> colon) <+> ppr ty) <> pprInitOpt e instance Pretty Body where ppr = vcat . map ppr instance Pretty FunDef where ppr (FunDef isPub sig bd) = - ((if isPub then text "public " else empty) <> ppr sig) + pprSignature isPub sig <+> lbrace $$ nest 3 (vcat (map ppr bd)) $$ rbrace +pprSignature :: Bool -> Signature -> Doc +pprSignature isPub (SignatureWithSyntax vs ctx n ps returnItems modifiers) + | n == Name "fallback" = + (text "fallback" <> pprParams ps) + <+> pprFunctionModifiers + (ensureVisibility VisibilityExternal modifiers) + | otherwise = + text "function" + <+> (ppr n <> pprTyParams vs <> pprParams ps) + <+> pprFunctionModifiers + ( if isPub + then ensureVisibility VisibilityPublic modifiers + else modifiers + ) + <+> pprSignatureReturns returnItems + <+> pprWhere ctx + +pprInterfaceSignature :: Bool -> Signature -> Doc +pprInterfaceSignature isExternal (SignatureWithSyntax vs ctx n ps returnItems modifiers) = + text "function" + <+> (ppr n <> pprTyParams vs <> pprParams ps) + <+> pprFunctionModifiers + ( if isExternal + then ensureVisibility VisibilityExternal modifiers + else modifiers + ) + <+> pprSignatureReturns returnItems + <+> pprWhere ctx + +pprFunctionModifiers :: [FunctionModifier] -> Doc +pprFunctionModifiers = hsep . map pprFunctionModifier + +pprFunctionModifier :: FunctionModifier -> Doc +pprFunctionModifier (VisibilityModifier VisibilityPublic) = text "public" +pprFunctionModifier (VisibilityModifier VisibilityExternal) = text "external" +pprFunctionModifier (VisibilityModifier VisibilityInternal) = text "internal" +pprFunctionModifier (VisibilityModifier VisibilityPrivate) = text "private" +pprFunctionModifier (MutabilityModifier MutabilityPure) = text "pure" +pprFunctionModifier (MutabilityModifier MutabilityView) = text "view" +pprFunctionModifier (MutabilityModifier MutabilityPayable) = text "payable" + +ensureVisibility :: FunctionVisibility -> [FunctionModifier] -> [FunctionModifier] +ensureVisibility fallbackVisibility modifiers + | any isVisibilityModifier modifiers = modifiers + | otherwise = VisibilityModifier fallbackVisibility : modifiers + where + isVisibilityModifier (VisibilityModifier _) = True + isVisibilityModifier _ = False + +pprSignatureReturns :: Maybe [ReturnItem] -> Doc +pprSignatureReturns Nothing = empty +pprSignatureReturns (Just items) = + text "returns" <+> parens (commaSep (map pprReturnItem items)) + +pprReturnItem :: ReturnItem -> Doc +pprReturnItem (ReturnItem isComptime returnName returnTy) = + pprConst isComptime + <> case returnName of + Nothing -> ppr returnTy + Just n -> (ppr n <> colon) <+> ppr returnTy + +pprPayable :: Bool -> Doc +pprPayable True = text "payable" +pprPayable False = empty + pprRetTy :: Bool -> Maybe Ty -> Doc pprRetTy _ Nothing = empty -pprRetTy rc (Just t) = text "->" <+> (pprConst rc <> ppr t) +pprRetTy True (Just t) = + text "returns" <+> parens (text "comptime" <+> ppr t) +pprRetTy False (Just t) = + text "returns" <+> parens (pprReturnItems t) + +pprReturnItems :: Ty -> Doc +pprReturnItems t@(TyCon n _) + | isTuple n = commaSep (map ppr (tupleElements t)) +pprReturnItems t = ppr t pprParams :: [Param] -> Doc pprParams = parens . commaSep . map ppr @@ -262,42 +336,55 @@ pprConst :: Bool -> Doc pprConst True = text "comptime " pprConst False = empty +pprComptime :: Bool -> Doc +pprComptime True = text "comptime" +pprComptime False = empty + instance Pretty Param where ppr (Typed c n ty) = - pprConst c <> (ppr n <+> colon <+> ppr ty) + pprConst c <> ((ppr n <> colon) <+> ppr ty) ppr (Untyped c n) = pprConst c <> ppr n instance Pretty Stmt where ppr (Assign n e) = - ppr n <+> equals <+> ppr e <+> semi + ppr n <+> equals <+> (ppr e <> semi) ppr (StmtPlusEq e1 e2) = - hsep [ppr e1, text "+=", ppr e2] + hsep [ppr e1, text "+=", ppr e2] <> semi ppr (StmtMinusEq e1 e2) = - hsep [ppr e1, text "-=", ppr e2] + hsep [ppr e1, text "-=", ppr e2] <> semi ppr (StmtBXorEq e1 e2) = - hsep [ppr e1, text "^=", ppr e2] + hsep [ppr e1, text "^=", ppr e2] <> semi ppr (StmtBAndEq e1 e2) = - hsep [ppr e1, text "&=", ppr e2] + hsep [ppr e1, text "&=", ppr e2] <> semi ppr (StmtBOrEq e1 e2) = - hsep [ppr e1, text "|=", ppr e2] + hsep [ppr e1, text "|=", ppr e2] <> semi ppr (StmtModEq e1 e2) = - hsep [ppr e1, text "%=", ppr e2] + hsep [ppr e1, text "%=", ppr e2] <> semi ppr (Let c n ty m) = - text "let" <+> ppr n <+> pprOptTy c ty <+> pprInitOpt m + ( text "let" + <+> pprComptime c + <+> (ppr n <> pprOptTy ty) + ) + <> pprInitOpt m + ppr (LetPattern ct pat ty value) = + (text "let" <+> pprComptime ct <+> (ppr pat <> pprOptTy ty)) + <> pprInitOpt (Just value) ppr (Block body) = lbrace $$ nest 3 (ppr body) $$ rbrace - ppr (StmtExp e) = - ppr e <> semi - ppr (Return e) = - text "return" <+> ppr e <+> semi + ppr (StmtExp e) + | isBareRevert e = text "revert" <> semi + | otherwise = ppr e <> semi + ppr (Return e) + | isUnitExp e = text "return" <> semi + | otherwise = text "return" <+> (ppr e <> semi) ppr (Match e eqns) = text "match" <+> (parens $ commaSep $ map ppr e) <+> lbrace - $$ vcat (map ppr eqns) + $$ nest 3 (vcat (map ppr eqns)) $$ rbrace ppr (Asm yblk) = text "assembly" @@ -314,6 +401,17 @@ instance Pretty Stmt where <+> lbrace $$ nest 3 (ppr blk2) $$ rbrace + ppr (While cond body) = + text "while" + <+> parens (ppr cond) + <+> lbrace + $$ nest 3 (ppr body) + $$ rbrace + ppr (Unchecked body) = + text "unchecked" + <+> lbrace + $$ nest 3 (ppr body) + $$ rbrace ppr (For initStmt cond postStmt body) = text "for" <+> parens (hsep [pprForClause initStmt <> semi, ppr cond <> semi, pprForClause postStmt]) @@ -332,8 +430,18 @@ pprForClause (StmtBXorEq e1 e2) = hsep [ppr e1, text "^=", ppr e2] pprForClause (StmtBAndEq e1 e2) = hsep [ppr e1, text "&=", ppr e2] pprForClause (StmtBOrEq e1 e2) = hsep [ppr e1, text "|=", ppr e2] pprForClause (StmtModEq e1 e2) = hsep [ppr e1, text "%=", ppr e2] -pprForClause (Let ct n ty m) = text "let" <+> ppr n <+> pprOptTy ct ty <+> pprForInitOpt m +pprForClause (Let ct n ty m) = + text "let" + <+> pprComptime ct + <+> (ppr n <> pprOptTy ty) + <+> pprForInitOpt m +pprForClause (LetPattern ct pat ty value) = + text "let" + <+> pprComptime ct + <+> (ppr pat <> pprOptTy ty) + <+> pprForInitOpt (Just value) pprForClause (StmtExp e) = ppr e +pprForClause (Block stmts) = commaSep (map pprForClause stmts) pprForClause EmptyStmt = empty pprForClause s = ppr s @@ -342,110 +450,220 @@ pprForInitOpt Nothing = empty pprForInitOpt (Just e) = equals <+> ppr e instance Pretty Equation where - ppr (p, ss) = - text "|" - <+> commaSep (map ppr p) - <+> text "=>" - $$ nest 3 (vcat (map ppr ss)) + ppr (ps, ss) + | not (null ps) && all isWildcardPat ps = + text "default" + <+> lbrace + $$ nest 3 (vcat (map ppr ss)) + $$ rbrace + | otherwise = + text "case" + <+> pprCasePatterns ps + <+> lbrace + $$ nest 3 (vcat (map ppr ss)) + $$ rbrace instance Pretty Equations where ppr = vcat . map ppr -pprOptTy :: Bool -> Maybe Ty -> Doc -pprOptTy _ Nothing = empty -pprOptTy c (Just t) = - case splitTy t of - ([], t') -> text ":" <+> (pprConst c <> ppr t') - (ts', t') -> - text ":" - <+> parens (commaSep (map ppr ts')) - <+> text "->" - <+> ppr t' +pprCasePatterns :: [Pat] -> Doc +pprCasePatterns [pat] = ppr pat +pprCasePatterns pats = parens (commaSep (map ppr pats)) + +isWildcardPat :: Pat -> Bool +isWildcardPat PWildcard = True +isWildcardPat _ = False + +pprOptTy :: Maybe Ty -> Doc +pprOptTy Nothing = empty +pprOptTy (Just t) = colon <+> ppr t pprInitOpt :: Maybe Exp -> Doc pprInitOpt Nothing = semi -pprInitOpt (Just e) = equals <+> ppr e <+> semi +pprInitOpt (Just e) = + space <> (equals <+> ppr e) <> semi parensWhen :: Bool -> Doc -> Doc parensWhen True d = parens d parensWhen _ d = d instance Pretty Exp where - ppr (Lit l) = ppr l - ppr (ExpName me n es) = - maybe empty (\e -> ppr e <> char '.') me - <> ppr n - <> parensWhen - (not $ null es) - (commaSep (map ppr es)) - ppr (ExpVar me v) = - maybe empty (\e -> ppr e <> char '.') me - <> ppr v - ppr (ExpDotName n es) = - char '.' - <> ppr n - <> parensWhen - (not $ null es) - (commaSep (map ppr es)) - ppr (Lam args bd _) = - text "lam" - <+> pprParams args + ppr = pprExpPrec lowestExpPrec + +lowestExpPrec, ternaryExpPrec, logicalOrExpPrec, logicalAndExpPrec :: Int +equalityExpPrec, relationalExpPrec, bitOrExpPrec, bitXorExpPrec :: Int +bitAndExpPrec, shiftExpPrec, additiveExpPrec, multiplicativeExpPrec :: Int +powerExpPrec, castExpPrec :: Int +unaryExpPrec, postfixExpPrec, atomExpPrec :: Int +lowestExpPrec = 0 +ternaryExpPrec = 10 +logicalOrExpPrec = 20 +logicalAndExpPrec = 30 +equalityExpPrec = 40 +relationalExpPrec = 50 +bitOrExpPrec = 60 +bitXorExpPrec = 70 +bitAndExpPrec = 80 +shiftExpPrec = 85 +additiveExpPrec = 90 +multiplicativeExpPrec = 100 +powerExpPrec = 105 +castExpPrec = 110 +unaryExpPrec = 120 +postfixExpPrec = 130 +atomExpPrec = 140 + +pprExpPrec :: Int -> Exp -> Doc +pprExpPrec context expression = + parensWhen + (expPrecedence expression < context) + (pprExpNode expression) + +pprExpNode :: Exp -> Doc +pprExpNode (Lit l) = ppr l +pprExpNode expression@(ExpName Nothing n [_, _]) + | isTuple n = + parens (commaSep (map (pprExpPrec lowestExpPrec) (tupleExpElements expression))) +pprExpNode (ExpName Nothing n []) + | isUnit n = text "()" + | otherwise = ppr n <> parens empty +pprExpNode (ExpName Nothing n es) = + ppr n <> parens (commaSep (map (pprExpPrec lowestExpPrec) es)) +pprExpNode (ExpName (Just receiver) n es) = + pprExpPrec postfixExpPrec receiver + <> char '.' + <> ppr n + <> parens (commaSep (map (pprExpPrec lowestExpPrec) es)) +pprExpNode (ExpVar Nothing v) = ppr v +pprExpNode (ExpVar (Just receiver) v) = + pprExpPrec postfixExpPrec receiver <> char '.' <> ppr v +pprExpNode (ExpDotName n []) = + char '.' <> ppr n +pprExpNode (ExpDotName n es) = + char '.' + <> ppr n + <> parens (commaSep (map (pprExpPrec lowestExpPrec) es)) +pprExpNode (Lam args bd lambdaRetTy) = + (text "lam" <> pprParams args) + <+> pprRetTy False lambdaRetTy <+> lbrace $$ nest 3 (vcat (map ppr bd)) $$ rbrace - ppr (TyExp e ty) = - ppr e <+> text ":" <+> ppr ty - ppr (ExpIndexed e1 e2) = - ppr e1 <> brackets (ppr e2) - ppr (ExpPlus e1 e2) = - hsep [ppr e1, text "+", ppr e2] - ppr (ExpMinus e1 e2) = - hsep [ppr e1, text "-", ppr e2] - ppr (ExpTimes e1 e2) = - hsep [ppr e1, text "*", ppr e2] - ppr (ExpDivide e1 e2) = - hsep [ppr e1, text "/", ppr e2] - ppr (ExpModulo e1 e2) = - hsep [ppr e1, text "%", ppr e2] - ppr (ExpBXor e1 e2) = - hsep [ppr e1, text "^", ppr e2] - ppr (ExpBAnd e1 e2) = - hsep [ppr e1, text "&", ppr e2] - ppr (ExpBOr e1 e2) = - hsep [ppr e1, text "|", ppr e2] - ppr (ExpLT e1 e2) = - hsep [ppr e1, text "<", ppr e2] - ppr (ExpGT e1 e2) = - hsep [ppr e1, text ">", ppr e2] - ppr (ExpLE e1 e2) = - hsep [ppr e1, text "<=", ppr e2] - ppr (ExpGE e1 e2) = - hsep [ppr e1, text ">=", ppr e2] - ppr (ExpEE e1 e2) = - hsep [ppr e1, text "==", ppr e2] - ppr (ExpNE e1 e2) = - hsep [ppr e1, text "!=", ppr e2] - ppr (ExpLAnd e1 e2) = - hsep [ppr e1, text "&&", ppr e2] - ppr (ExpLOr e1 e2) = - hsep [ppr e1, text "||", ppr e2] - ppr (ExpLNot e1) = - hsep [text "!", ppr e1] - ppr (ExpCond e1 e2 e3) = - hsep - [ text "if", - ppr e1, - text "then", - ppr e2, - text "else", - ppr e3 - ] - ppr (ExpAt t) = - text "@" <> ppr t - -pprE :: Maybe Exp -> Doc -pprE Nothing = "" -pprE (Just e) = ppr e <> text "." +pprExpNode (TyExp e ty) = + pprExpPrec castExpPrec e <+> text "as" <+> ppr ty +pprExpNode (ExpIndexed collection index) = + pprExpPrec postfixExpPrec collection + <> brackets (pprExpPrec lowestExpPrec index) +pprExpNode (ExpPlus left right) = + pprLeftAssocBinary additiveExpPrec "+" left right +pprExpNode (ExpMinus left right) = + pprLeftAssocBinary additiveExpPrec "-" left right +pprExpNode (ExpPower left right) = + pprRightAssocBinary powerExpPrec "**" left right +pprExpNode (ExpTimes left right) = + pprLeftAssocBinary multiplicativeExpPrec "*" left right +pprExpNode (ExpDivide left right) = + pprLeftAssocBinary multiplicativeExpPrec "/" left right +pprExpNode (ExpModulo left right) = + pprLeftAssocBinary multiplicativeExpPrec "%" left right +pprExpNode (ExpShiftL left right) = + pprLeftAssocBinary shiftExpPrec "<<" left right +pprExpNode (ExpShiftR left right) = + pprLeftAssocBinary shiftExpPrec ">>" left right +pprExpNode (ExpBXor left right) = + pprLeftAssocBinary bitXorExpPrec "^" left right +pprExpNode (ExpBAnd left right) = + pprLeftAssocBinary bitAndExpPrec "&" left right +pprExpNode (ExpBOr left right) = + pprLeftAssocBinary bitOrExpPrec "|" left right +pprExpNode (ExpLT left right) = + pprNonAssocBinary relationalExpPrec "<" left right +pprExpNode (ExpGT left right) = + pprNonAssocBinary relationalExpPrec ">" left right +pprExpNode (ExpLE left right) = + pprNonAssocBinary relationalExpPrec "<=" left right +pprExpNode (ExpGE left right) = + pprNonAssocBinary relationalExpPrec ">=" left right +pprExpNode (ExpEE left right) = + pprNonAssocBinary equalityExpPrec "==" left right +pprExpNode (ExpNE left right) = + pprNonAssocBinary equalityExpPrec "!=" left right +pprExpNode (ExpLAnd left right) = + pprLeftAssocBinary logicalAndExpPrec "&&" left right +pprExpNode (ExpLOr left right) = + pprLeftAssocBinary logicalOrExpPrec "||" left right +pprExpNode (ExpLNot operand) = + char '!' <> pprExpPrec unaryExpPrec operand +pprExpNode (ExpCond condition thenExpression elseExpression) = + hsep + [ pprExpPrec (ternaryExpPrec + 1) condition, + text "?", + pprExpPrec ternaryExpPrec thenExpression, + colon, + pprExpPrec ternaryExpPrec elseExpression + ] +pprExpNode (ExpAt t) = + text "Proxy" + <+> text "as" + <+> ppr (TyCon (Name "Proxy") [t]) + +pprLeftAssocBinary :: Int -> String -> Exp -> Exp -> Doc +pprLeftAssocBinary precedence operator left right = + hsep + [ pprExpPrec precedence left, + text operator, + pprExpPrec (precedence + 1) right + ] + +pprRightAssocBinary :: Int -> String -> Exp -> Exp -> Doc +pprRightAssocBinary precedence operator left right = + hsep + [ pprExpPrec (precedence + 1) left, + text operator, + pprExpPrec precedence right + ] + +pprNonAssocBinary :: Int -> String -> Exp -> Exp -> Doc +pprNonAssocBinary precedence operator left right = + hsep + [ pprExpPrec (precedence + 1) left, + text operator, + pprExpPrec (precedence + 1) right + ] + +expPrecedence :: Exp -> Int +expPrecedence (ExpCond _ _ _) = ternaryExpPrec +expPrecedence (ExpLOr _ _) = logicalOrExpPrec +expPrecedence (ExpLAnd _ _) = logicalAndExpPrec +expPrecedence (ExpEE _ _) = equalityExpPrec +expPrecedence (ExpNE _ _) = equalityExpPrec +expPrecedence (ExpLT _ _) = relationalExpPrec +expPrecedence (ExpGT _ _) = relationalExpPrec +expPrecedence (ExpLE _ _) = relationalExpPrec +expPrecedence (ExpGE _ _) = relationalExpPrec +expPrecedence (ExpBOr _ _) = bitOrExpPrec +expPrecedence (ExpBXor _ _) = bitXorExpPrec +expPrecedence (ExpBAnd _ _) = bitAndExpPrec +expPrecedence (ExpShiftL _ _) = shiftExpPrec +expPrecedence (ExpShiftR _ _) = shiftExpPrec +expPrecedence (ExpPlus _ _) = additiveExpPrec +expPrecedence (ExpMinus _ _) = additiveExpPrec +expPrecedence (ExpTimes _ _) = multiplicativeExpPrec +expPrecedence (ExpDivide _ _) = multiplicativeExpPrec +expPrecedence (ExpModulo _ _) = multiplicativeExpPrec +expPrecedence (ExpPower _ _) = powerExpPrec +expPrecedence (TyExp _ _) = castExpPrec +expPrecedence (ExpAt _) = castExpPrec +expPrecedence (ExpLNot _) = unaryExpPrec +expPrecedence (ExpName (Just _) _ _) = postfixExpPrec +expPrecedence (ExpVar (Just _) _) = postfixExpPrec +expPrecedence (ExpIndexed _ _) = postfixExpPrec +expPrecedence _ = atomExpPrec + +tupleExpElements :: Exp -> [Exp] +tupleExpElements (ExpName Nothing n [left, right]) + | isTuple n = left : tupleExpElements right +tupleExpElements expression = [expression] instance Pretty Pat where ppr (Pat n []) = ppr n @@ -465,32 +683,92 @@ instance Pretty Pat where instance Pretty Literal where ppr (IntLit l) = integer (toInteger l) - ppr (StrLit l) = quotes (text l) + ppr (StrLit l) = pprStringLiteral l + +pprStringLiteral :: String -> Doc +pprStringLiteral = doubleQuotes . text . concatMap escapeStringChar + +escapeStringChar :: Char -> String +escapeStringChar '\\' = "\\\\" +escapeStringChar '"' = "\\\"" +escapeStringChar '\n' = "\\n" +escapeStringChar '\t' = "\\t" +escapeStringChar c = [c] instance Pretty Pred where ppr (InCls n t ts) = - ppr t <+> colon <+> ppr n <+> pprTyParams ts + (ppr t <> colon) <+> (ppr n <> pprTyParams ts) instance Pretty Ty where - ppr (t1@(_ :-> _) :-> t2) = - parens (ppr t1) <+> text "->" <+> ppr t2 - ppr (t1 :-> t2) = - ppr t1 <+> (text "->") <+> ppr t2 - ppr (TyCon n ts) - | isTuple n = parens $ commaSep (map ppr ts) + ppr (FunctionTy args visibility returns) = + (text "function" <> parens (commaSep (map ppr args))) + <+> pprFunctionTypeVisibility visibility + <+> pprFunctionTypeReturns returns + ppr t@(_ :-> _) = + let (args, ret) = splitTy t + in (text "function" <> parens (commaSep (map ppr args))) + <+> text "internal" + <+> pprRetTy False (Just ret) + ppr (TyCon n [keyTy, valueTy]) + | n == Name "mapping" = + text "mapping" + <> parens (ppr keyTy <+> text "=>" <+> ppr valueTy) + ppr (TyCon n [elementTy]) + | n == Name "array" = + ppr elementTy <> brackets empty + ppr (TyCon n [sizeTy, elementTy]) + | n == Name "array" = + ppr elementTy <> brackets (ppr sizeTy) + ppr (TyCon n [t]) + | isDataLocation n = ppr t <+> ppr n + ppr t@(TyCon n _) + | isTuple n = parens $ commaSep (map ppr (tupleElements t)) | isUnit n = text "()" - | otherwise = ppr n <> (pprTyParams ts) + ppr (TyCon n ts) = + ppr n <> pprTyParams ts + +pprFunctionTypeVisibility :: Maybe FunctionTypeVisibility -> Doc +pprFunctionTypeVisibility Nothing = empty +pprFunctionTypeVisibility (Just FunctionTypeInternal) = text "internal" +pprFunctionTypeVisibility (Just FunctionTypeExternal) = text "external" + +pprFunctionTypeReturns :: Maybe [Ty] -> Doc +pprFunctionTypeReturns Nothing = empty +pprFunctionTypeReturns (Just returns) = + text "returns" <+> parens (commaSep (map ppr returns)) isUnit :: Name -> Bool -isUnit n = pretty n == "unit" +isUnit n = + n == Name "unit" || n == Name "()" isTuple :: (Pretty a) => a -> Bool isTuple s = pretty s == "pair" +isDataLocation :: Name -> Bool +isDataLocation n = + n `elem` [Name "memory", Name "storage", Name "calldata"] + +tupleElements :: Ty -> [Ty] +tupleElements (TyCon n [left, right]) + | isTuple n = left : tupleElements right +tupleElements t = [t] + pprTyParams :: [Ty] -> Doc pprTyParams [] = empty pprTyParams ts = - parens (commaSep (map ppr ts)) + angles (commaSep (map ppr ts)) + +constructorLeafName :: Name -> Name +constructorLeafName (QualName _ leaf) = Name leaf +constructorLeafName n = n + +isUnitExp :: Exp -> Bool +isUnitExp (ExpName Nothing n []) = isUnit n +isUnitExp _ = False + +isBareRevert :: Exp -> Bool +isBareRevert (ExpName Nothing n []) = n == Name "revert" +isBareRevert _ = False splitTy :: Ty -> ([Ty], Ty) splitTy (a :-> b) = diff --git a/src/Solcore/Frontend/Syntax/Contract.hs b/src/Solcore/Frontend/Syntax/Contract.hs index 2264fc8f4..6b21ef873 100644 --- a/src/Solcore/Frontend/Syntax/Contract.hs +++ b/src/Solcore/Frontend/Syntax/Contract.hs @@ -35,6 +35,8 @@ data PragmaType | NoPattersonCondition | NoBoundVariableCondition | NoGenericInstanceFor + | SolidityPragma String + | AbiCoderPragma String deriving (Eq, Ord, Show, Data, Typeable) data PragmaStatus @@ -233,6 +235,7 @@ data ContractDecl a = CDataDecl DataTy | CFieldDecl (Field a) | CFunDecl (FunDef a) + | CSignatureDecl Bool (Signature a) | CMutualDecl [ContractDecl a] -- used only after SCC analysis | CConstrDecl (Constructor a) deriving (Eq, Ord, Show, Data, Typeable) @@ -355,5 +358,6 @@ instance (HasSourceSpan a) => HasSourceSpan (ContractDecl a) where sourceSpanOf (CDataDecl dataTy) = sourceSpanOf dataTy sourceSpanOf (CFieldDecl field) = sourceSpanOf field sourceSpanOf (CFunDecl funDef) = sourceSpanOf funDef + sourceSpanOf (CSignatureDecl _ sig) = sourceSpanOf sig sourceSpanOf (CMutualDecl decls') = sourceSpanOf decls' sourceSpanOf (CConstrDecl constructor) = sourceSpanOf constructor diff --git a/src/Solcore/Frontend/Syntax/NameResolution.hs b/src/Solcore/Frontend/Syntax/NameResolution.hs index fd9ac43fc..9787afbd1 100644 --- a/src/Solcore/Frontend/Syntax/NameResolution.hs +++ b/src/Solcore/Frontend/Syntax/NameResolution.hs @@ -5,6 +5,7 @@ import Control.Applicative import Control.Monad import Control.Monad.Except import Control.Monad.State +import Data.Char (isDigit) import Data.Generics (Data, everything, extQ, mkQ) import Data.List ((\\)) import Data.Map (Map) @@ -23,8 +24,10 @@ import Solcore.Frontend.Syntax.Ty -- name resolution nameResolution :: S.CompUnit -> IO (Either CompilerError (CompUnit Name)) -nameResolution (S.CompUnit imps ds) = - fmap fst <$> nameResolutionTopDeclSegments imps [ds] +nameResolution unit@(S.CompUnit imps ds) = + case validateDuplicateNamespacesInCompUnit unit of + Left err -> pure (Left err) + Right () -> fmap fst <$> nameResolutionTopDeclSegments imps [ds] nameResolutionTopDeclSegments :: [S.Import] -> @@ -104,9 +107,11 @@ validateDuplicateNamespaces ds = do validateContractDuplicates :: S.Contract -> Either CompilerError () validateContractDuplicates (S.Contract cname _ decls) = do let typeNames = [n | S.CDataDecl (S.DataTy n _ _) <- decls] + fieldNames = [n | S.CFieldDecl (S.Field n _ _) <- decls] termNames = contractTermNames decls context = "contract " ++ pretty cname ensureNoDuplicateNamesIn context "type namespace" typeNames + ensureNoDuplicateNamesIn context "field namespace" fieldNames ensureNoDuplicateNamesIn context "term namespace" termNames topLevelTypeNames :: [S.TopDecl] -> [Name] @@ -130,6 +135,7 @@ contractTermNames :: [S.ContractDecl] -> [Name] contractTermNames = concatMap collect where collect (S.CFunDecl (S.FunDef _ sig _)) = [S.sigName sig] + collect (S.CSignatureDecl _ sig) = [S.sigName sig] collect (S.CDataDecl (S.DataTy tyCon _ cons)) = map (qualifiedConstructorName tyCon . S.constrName) cons collect _ = [] @@ -232,6 +238,8 @@ addContractDecl (S.CFieldDecl (S.Field n _ _)) = addField n addContractDecl (S.CFunDecl (S.FunDef _ sig _)) = addFunctionName (S.sigName sig) +addContractDecl (S.CSignatureDecl _ sig) = + addFunctionName (S.sigName sig) addContractDecl _ = pure () instance Resolve S.ContractDecl where @@ -243,6 +251,9 @@ instance Resolve S.ContractDecl where CFieldDecl <$> resolve fd `wrapError` d resolve d@(S.CFunDecl f) = CFunDecl <$> resolve f `wrapError` d + resolve d@(S.CSignatureDecl isPublic sig) = do + sig' <- resolve sig `wrapError` d + pure (CSignatureDecl (isPublic || S.sigIsPublic sig) sig') resolve d@(S.CConstrDecl cd) = CConstrDecl <$> resolve cd `wrapError` d @@ -289,15 +300,24 @@ instance Resolve S.Class where instance Resolve S.Signature where type Result S.Signature = Signature Name - resolve s@(S.Signature vs ctx n ps rc mt pay) = + resolve s@(S.SignatureWithSyntax vs ctx n ps _ _) = withLocalCtx $ do let ns = map tyconName vs mapM_ addTyVar ns ctx' <- resolve ctx `wrapError` s ps' <- resolve ps `wrapError` s - mt' <- resolve mt `wrapError` s + mt' <- resolve (S.sigReturn s) `wrapError` s let vs' = map TVar ns - pure (Signature vs' ctx' n ps' rc mt' pay) + pure + ( Signature + vs' + ctx' + n + ps' + (S.sigRetComptime s) + mt' + (S.sigPayable s) + ) instance Resolve S.Instance where type Result S.Instance = Instance Name @@ -340,6 +360,10 @@ instance Resolve S.PragmaType where pure NoBoundVariableCondition resolve S.NoGenericInstanceFor = pure NoGenericInstanceFor + resolve (S.SolidityPragma version) = + pure (SolidityPragma version) + resolve (S.AbiCoderPragma version) = + pure (AbiCoderPragma version) instance Resolve S.PragmaStatus where type Result S.PragmaStatus = PragmaStatus @@ -351,20 +375,29 @@ instance Resolve S.PragmaStatus where instance Resolve S.FunDef where type Result S.FunDef = FunDef Name - resolve f@(S.FunDef isPub (S.Signature vs ctx n ps rc mt pay) bds) = + resolve f@(S.FunDef legacyIsPub sourceSig@(S.SignatureWithSyntax vs ctx n ps _ _) bds) = do let ns = map tyconName vs withLocalCtx $ do mapM_ addTyVar ns ctx' <- resolve ctx `wrapError` f ps' <- resolve ps `wrapError` f - mt' <- resolve mt `wrapError` f + mt' <- resolve (S.sigReturn sourceSig) `wrapError` f let args = map paramName ps' mapM_ addParameter args bds' <- resolve bds `wrapError` f let vs' = map TVar ns - sig = Signature vs' ctx' n ps' rc mt' pay - pure (FunDef isPub sig bds') + sig = + Signature + vs' + ctx' + n + ps' + (S.sigRetComptime sourceSig) + mt' + (S.sigPayable sourceSig) + isPublic = legacyIsPub || S.sigIsPublic sourceSig + pure (FunDef isPublic sig bds') instance Resolve S.Stmt where type Result S.Stmt = Stmt Name @@ -392,6 +425,12 @@ instance Resolve S.Stmt where me' <- resolve me `wrapError` s addLocalVar n pure (Let c n mt' me') + resolve s@(S.LetPattern ct pat mt value) = + locatedLike s locatedStmt <$> do + mt' <- resolve mt `wrapError` s + value' <- resolve value `wrapError` s + pat' <- resolveBindingPat pat `wrapError` s + pure (LetPattern ct pat' mt' value') resolve s@(S.Block blk) = locatedLike s locatedStmt <$> withLocalCtx (Block <$> resolve blk) resolve s@(S.StmtExp e) = @@ -404,6 +443,10 @@ instance Resolve S.Stmt where pure (locatedLike s locatedStmt (Asm blk)) resolve s@(S.If e blk1 blk2) = locatedLike s locatedStmt <$> (If <$> resolve e <*> resolve blk1 <*> resolve blk2) + resolve s@(S.While cond body) = + locatedLike s locatedStmt <$> (For EmptyStmt <$> resolve cond <*> pure EmptyStmt <*> resolve body) + resolve s@(S.Unchecked body) = + locatedLike s locatedStmt <$> withLocalCtx (Block <$> resolve body) resolve s@(S.For initStmt cond postStmt body) = locatedLike s locatedStmt <$> (For <$> resolve initStmt <*> resolve cond <*> resolve postStmt <*> resolve body) resolve s@S.Break = pure (locatedLike s locatedStmt Break) @@ -479,6 +522,22 @@ mkTuplePat ps = foldr1 pairPat ps pairPat :: Pat Name -> Pat Name -> Pat Name pairPat p1 p2 = PCon (Name "pair") [p1, p2] +-- A destructuring let binds names regardless of whether a constructor with the +-- same spelling is in scope. Its parser only admits tuple/name/wildcard +-- shapes, so resolve it separately from refutable match patterns. +resolveBindingPat :: S.Pat -> ResolveM (Pat Name) +resolveBindingPat p@(S.Pat n []) = do + addLocalVar n + pure (locatedLike p locatedPat (PVar n)) +resolveBindingPat p@(S.Pat n ps) + | n == Name "pair" = do + ps' <- mapM resolveBindingPat ps + pure (locatedLike p locatedPat (mkTuplePat ps')) +resolveBindingPat p@S.PWildcard = + pure (locatedLike p locatedPat PWildcard) +resolveBindingPat p = + invalidPatternSyntax p + constructorLeafName :: Name -> Name constructorLeafName q@(QualName _ n) = copyNameSourceSpan q (Name n) constructorLeafName n = n @@ -803,6 +862,11 @@ resolveExp c@(S.ExpMinus e1 e2) = e2' <- resolve e2 `wrapError` c let fun = QualName (Name "Sub") "sub" pure $ Call Nothing fun [e1', e2'] +resolveExp c@(S.ExpPower e1 e2) = + do + e1' <- resolve e1 `wrapError` c + e2' <- resolve e2 `wrapError` c + pure $ Call Nothing (Name "exp") [e1', e2'] resolveExp c@(S.ExpTimes e1 e2) = do e1' <- resolve e1 `wrapError` c @@ -821,6 +885,16 @@ resolveExp c@(S.ExpModulo e1 e2) = e2' <- resolve e2 `wrapError` c let fun = QualName (Name "Mod") "mod" pure $ Call Nothing fun [e1', e2'] +resolveExp c@(S.ExpShiftL e1 e2) = + do + value <- resolve e1 `wrapError` c + amount <- resolve e2 `wrapError` c + pure $ Call Nothing (Name "shl") [amount, value] +resolveExp c@(S.ExpShiftR e1 e2) = + do + value <- resolve e1 `wrapError` c + amount <- resolve e2 `wrapError` c + pure $ Call Nothing (Name "shr") [amount, value] resolveExp c@(S.ExpBXor e1 e2) = do e1' <- resolve e1 `wrapError` c @@ -946,14 +1020,49 @@ tyconName (S.TyCon n _) = n instance Resolve S.Ty where type Result S.Ty = Ty + resolve functionTy@(S.FunctionTy args _visibility returns) = + locatedLike functionTy locatedTy <$> do + args' <- resolve args `wrapError` functionTy + returns' <- resolveFunctionReturns returns `wrapError` functionTy + pure (funtype args' returns') resolve tc@(S.TyCon n ts) = locatedLike tc locatedTy <$> do ndt <- lookupType n case ndt of - Just TTyCon -> TyCon n <$> resolve ts `wrapError` tc + Just TTyCon -> + TyCon n <$> resolveTypeArguments n ts `wrapError` tc Just TTyVar -> pure (TyVar (TVar n)) _ -> undefinedTypeConstructor tc +resolveFunctionReturns :: Maybe [S.Ty] -> ResolveM Ty +resolveFunctionReturns Nothing = pure (TyCon (Name "()") []) +resolveFunctionReturns (Just returns) = do + returns' <- resolve returns + pure $ case returns' of + [] -> TyCon (Name "()") [] + [returnTy] -> returnTy + _ -> foldr1 (\left right -> TyCon (Name "pair") [left, right]) returns' + +resolveTypeArguments :: Name -> [S.Ty] -> ResolveM [Ty] +resolveTypeArguments n [size, element] + | n == Name "array", + Just resolvedSize <- numericArraySize size = + (resolvedSize :) . (: []) <$> resolve element +resolveTypeArguments _ ts = + resolve ts + +-- A fixed-array length is a type-level natural in the existing internal +-- representation, not a user-defined type constructor. Keep it scoped to the +-- first argument of the built-in two-argument array shape so a bare numeric +-- "type" remains invalid everywhere else. +numericArraySize :: S.Ty -> Maybe Ty +numericArraySize size@(S.TyCon n@(Name digits) []) + | not (null digits), + all isDigit digits = + Just (locatedLike size locatedTy (TyCon n [])) +numericArraySize _ = + Nothing + -- definition of an environment data DeclType diff --git a/src/Solcore/Frontend/Syntax/Stmt.hs b/src/Solcore/Frontend/Syntax/Stmt.hs index 141781b63..80321b236 100644 --- a/src/Solcore/Frontend/Syntax/Stmt.hs +++ b/src/Solcore/Frontend/Syntax/Stmt.hs @@ -18,6 +18,7 @@ type Equations a = [Equation a] data Stmt a = AssignWithLocation NodeLocation (Exp a) (Exp a) -- assignment | LetWithLocation NodeLocation Bool a (Maybe Ty) (Maybe (Exp a)) -- local variable; Bool is True when 'comptime' modifier is present + | LetPatternWithLocation NodeLocation Bool (Pat a) (Maybe Ty) (Exp a) -- irrefutable tuple binding; Bool marks 'comptime' | BlockWithLocation NodeLocation (Body a) -- lexical block | StmtExpWithLocation NodeLocation (Exp a) -- expression level statements | ReturnWithLocation NodeLocation (Exp a) -- return statements @@ -42,6 +43,11 @@ pattern Let ct n ty value <- LetWithLocation _ ct n ty value where Let ct n ty value = LetWithLocation unlocatedNode ct n ty value +pattern LetPattern :: Bool -> Pat a -> Maybe Ty -> Exp a -> Stmt a +pattern LetPattern ct pat ty value <- LetPatternWithLocation _ ct pat ty value + where + LetPattern ct pat ty value = LetPatternWithLocation unlocatedNode ct pat ty value + pattern Block :: Body a -> Stmt a pattern Block body <- BlockWithLocation _ body where @@ -92,13 +98,15 @@ pattern EmptyStmt <- EmptyStmtWithLocation _ where EmptyStmt = EmptyStmtWithLocation unlocatedNode -{-# COMPLETE (:=), Let, Block, StmtExp, Return, Match, Asm, If, For, Break, Continue, EmptyStmt #-} +{-# COMPLETE (:=), Let, LetPattern, Block, StmtExp, Return, Match, Asm, If, For, Break, Continue, EmptyStmt #-} type Body a = [Stmt a] locatedStmt :: SourceSpan -> Stmt a -> Stmt a locatedStmt sourceSpan (lhs := rhs) = AssignWithLocation (locatedNode sourceSpan) lhs rhs locatedStmt sourceSpan (Let ct n ty value) = LetWithLocation (locatedNode sourceSpan) ct n ty value +locatedStmt sourceSpan (LetPattern ct pat ty value) = + LetPatternWithLocation (locatedNode sourceSpan) ct pat ty value locatedStmt sourceSpan (Block body) = BlockWithLocation (locatedNode sourceSpan) body locatedStmt sourceSpan (StmtExp exp) = StmtExpWithLocation (locatedNode sourceSpan) exp locatedStmt sourceSpan (Return exp) = ReturnWithLocation (locatedNode sourceSpan) exp @@ -115,6 +123,8 @@ instance (HasSourceSpan a) => HasSourceSpan (Stmt a) where firstSourceSpan [sourceSpanOf location, sourceSpanOf lhs, sourceSpanOf rhs] sourceSpanOf (LetWithLocation location _ n ty value) = firstSourceSpan [sourceSpanOf location, sourceSpanOf n, sourceSpanOf ty, sourceSpanOf value] + sourceSpanOf (LetPatternWithLocation location _ pat ty value) = + firstSourceSpan [sourceSpanOf location, sourceSpanOf pat, sourceSpanOf ty, sourceSpanOf value] sourceSpanOf (BlockWithLocation location body) = firstSourceSpan [sourceSpanOf location, sourceSpanOf body] sourceSpanOf (StmtExpWithLocation location exp) = @@ -164,7 +174,7 @@ data Exp a | LitWithLocation NodeLocation Literal -- literal | CallWithLocation NodeLocation (Maybe (Exp a)) a [Exp a] -- function call | LamWithLocation NodeLocation [Param a] (Body a) (Maybe Ty) -- lambda-abstraction - | TyExpWithLocation NodeLocation (Exp a) Ty -- type annotated expression + | TyExpWithLocation NodeLocation (Exp a) Ty -- explicit type conversion expression | CondWithLocation NodeLocation (Exp a) (Exp a) (Exp a) -- conditional expression | IndexedWithLocation NodeLocation (Exp a) (Exp a) -- e1[e2] deriving (Eq, Ord, Show, Data, Typeable) diff --git a/src/Solcore/Frontend/Syntax/SyntaxTree.hs b/src/Solcore/Frontend/Syntax/SyntaxTree.hs index 41dca013d..6a4df53d1 100644 --- a/src/Solcore/Frontend/Syntax/SyntaxTree.hs +++ b/src/Solcore/Frontend/Syntax/SyntaxTree.hs @@ -1,10 +1,11 @@ {-# LANGUAGE PatternSynonyms #-} +{-# LANGUAGE ViewPatterns #-} module Solcore.Frontend.Syntax.SyntaxTree where import Data.Generics (Data, Typeable) import Data.List (union) -import Data.List.NonEmpty +import Data.List.NonEmpty (NonEmpty, toList) import Language.Yul import Solcore.Diagnostics (SourceSpan) import Solcore.Frontend.Syntax.Location @@ -38,6 +39,8 @@ data PragmaType | NoPattersonCondition | NoBoundVariableCondition | NoGenericInstanceFor + | SolidityPragma String + | AbiCoderPragma String deriving (Eq, Ord, Show, Data, Typeable) data PragmaStatus @@ -106,24 +109,67 @@ data ItemSelectorEntry -- definition of the contract structure +data ContractKind + = ContractKind + | InterfaceKind + | LibraryKind + deriving (Eq, Ord, Show, Data, Typeable) + data Contract - = Contract - { name :: Name, + = ContractWithKind + { contractKind :: ContractKind, + name :: Name, tyParams :: [Ty], decls :: [ContractDecl] } deriving (Eq, Ord, Show, Data, Typeable) +-- Keep the historical three-argument source-AST constructor available to +-- callers. It builds an ordinary contract, while matching all Solidity-style +-- declaration shells so existing compiler traversals keep working. +pattern Contract :: Name -> [Ty] -> [ContractDecl] -> Contract +pattern Contract n ts ds <- ContractWithKind _ n ts ds + where + Contract n ts ds = ContractWithKind ContractKind n ts ds + +pattern ContractShell :: ContractKind -> Name -> [Ty] -> [ContractDecl] -> Contract +pattern ContractShell k n ts ds = ContractWithKind k n ts ds + +{-# COMPLETE Contract #-} +{-# COMPLETE ContractShell #-} + -- definition of a algebraic data type data DataTy - = DataTy - { dataName :: Name, + = DataTyWithKind + { dataTyKind :: DataTyKind, + dataName :: Name, dataParams :: [Ty], dataConstrs :: [Constr] } deriving (Eq, Ord, Show, Data, Typeable) +data DataTyKind + = EnumKind + | StructKind [Name] + deriving (Eq, Ord, Show, Data, Typeable) + +pattern DataTy :: Name -> [Ty] -> [Constr] -> DataTy +pattern DataTy n ts cs <- DataTyWithKind _ n ts cs + where + DataTy n ts cs = DataTyWithKind EnumKind n ts cs + +-- Struct fields remain named in the source AST. Name resolution deliberately +-- lowers this shape to a one-constructor algebraic data type. +pattern StructTy :: Name -> [Ty] -> [Name] -> [Ty] -> DataTy +pattern StructTy n ts fieldNames fieldTypes <- + DataTyWithKind (StructKind fieldNames) n ts [Constr _ fieldTypes] + where + StructTy n ts fieldNames fieldTypes = + DataTyWithKind (StructKind fieldNames) n ts [Constr n fieldTypes] + +{-# COMPLETE DataTy #-} + data Constr = Constr { constrName :: Name, @@ -147,6 +193,65 @@ pattern TyCon n ts <- TyConWithLocation _ n ts pattern (:->) :: Ty -> Ty -> Ty pattern (:->) t1 t2 = TyCon (Name "->") [t1, t2] +data FunctionTypeVisibility + = FunctionTypeInternal + | FunctionTypeExternal + deriving (Eq, Ord, Show, Data, Typeable) + +-- Function types keep their source-only visibility and list structure until +-- name resolution. The private TyCon encoding lets the historical Ty shape +-- remain compatible with existing traversals while still distinguishing a +-- zero-argument function from its result type. +pattern FunctionTy :: [Ty] -> Maybe FunctionTypeVisibility -> Maybe [Ty] -> Ty +pattern FunctionTy args visibility returns <- + (functionTyView -> Just (args, visibility, returns)) + where + FunctionTy args visibility returns = + TyCon + (Name "$function") + [ TyCon (Name "$arguments") args, + functionVisibilityMarker visibility, + functionReturnsMarker returns + ] + +functionTyView :: Ty -> Maybe ([Ty], Maybe FunctionTypeVisibility, Maybe [Ty]) +functionTyView + ( TyCon + (Name "$function") + [ TyCon (Name "$arguments") args, + visibilityMarker, + returnsMarker + ] + ) = do + visibility <- functionVisibilityView visibilityMarker + returns <- functionReturnsView returnsMarker + pure (args, visibility, returns) +functionTyView _ = Nothing + +functionVisibilityMarker :: Maybe FunctionTypeVisibility -> Ty +functionVisibilityMarker Nothing = TyCon (Name "$default-visibility") [] +functionVisibilityMarker (Just FunctionTypeInternal) = + TyCon (Name "$internal") [] +functionVisibilityMarker (Just FunctionTypeExternal) = + TyCon (Name "$external") [] + +functionVisibilityView :: Ty -> Maybe (Maybe FunctionTypeVisibility) +functionVisibilityView (TyCon (Name "$default-visibility") []) = Just Nothing +functionVisibilityView (TyCon (Name "$internal") []) = + Just (Just FunctionTypeInternal) +functionVisibilityView (TyCon (Name "$external") []) = + Just (Just FunctionTypeExternal) +functionVisibilityView _ = Nothing + +functionReturnsMarker :: Maybe [Ty] -> Ty +functionReturnsMarker Nothing = TyCon (Name "$no-returns") [] +functionReturnsMarker (Just returns) = TyCon (Name "$returns") returns + +functionReturnsView :: Ty -> Maybe (Maybe [Ty]) +functionReturnsView (TyCon (Name "$no-returns") []) = Just Nothing +functionReturnsView (TyCon (Name "$returns") returns) = Just (Just returns) +functionReturnsView _ = Nothing + tyName :: Ty -> Name tyName (TyCon n _) = n @@ -207,18 +312,120 @@ data Class } deriving (Eq, Ord, Show, Data, Typeable) +data FunctionVisibility + = VisibilityPublic + | VisibilityExternal + | VisibilityInternal + | VisibilityPrivate + deriving (Eq, Ord, Show, Data, Typeable) + +data FunctionMutability + = MutabilityPure + | MutabilityView + | MutabilityPayable + deriving (Eq, Ord, Show, Data, Typeable) + +data FunctionModifier + = VisibilityModifier FunctionVisibility + | MutabilityModifier FunctionMutability + deriving (Eq, Ord, Show, Data, Typeable) + +data ReturnItem + = ReturnItem + { returnItemComptime :: Bool, + returnItemName :: Maybe Name, + returnItemType :: Ty + } + deriving (Eq, Ord, Show, Data, Typeable) + data Signature - = Signature + = SignatureWithSyntax { sigVars :: [Ty], sigContext :: [Pred], sigName :: Name, sigParams :: [Param], - sigRetComptime :: Bool, - sigReturn :: Maybe Ty, - sigPayable :: Bool + sigReturnItems :: Maybe [ReturnItem], + sigModifiers :: [FunctionModifier] } deriving (Eq, Ord, Show, Data, Typeable) +-- Keep the historical seven-argument constructor available to source-AST +-- clients. New parser code uses SignatureWithSyntax so named return items and +-- exact Solidity modifier spellings survive parse/pretty-print round trips. +pattern Signature :: [Ty] -> [Pred] -> Name -> [Param] -> Bool -> Maybe Ty -> Bool -> Signature +pattern Signature vars context funName params returnComptime returnTy payable <- + SignatureWithSyntax + vars + context + funName + params + (legacyReturnView -> (returnComptime, returnTy)) + (legacyPayableView -> payable) + where + Signature vars context funName params returnComptime returnTy payable = + SignatureWithSyntax + vars + context + funName + params + (legacyReturnItems returnComptime returnTy) + [MutabilityModifier MutabilityPayable | payable] + +{-# COMPLETE Signature #-} + +sigRetComptime :: Signature -> Bool +sigRetComptime = fst . legacyReturnView . sigReturnItems + +sigReturn :: Signature -> Maybe Ty +sigReturn = snd . legacyReturnView . sigReturnItems + +sigPayable :: Signature -> Bool +sigPayable = legacyPayableView . sigModifiers + +sigIsPublic :: Signature -> Bool +sigIsPublic = + any + ( \modifier -> case modifier of + VisibilityModifier VisibilityPublic -> True + VisibilityModifier VisibilityExternal -> True + _ -> False + ) + . sigModifiers + +legacyReturnItems :: Bool -> Maybe Ty -> Maybe [ReturnItem] +legacyReturnItems _ Nothing = Nothing +legacyReturnItems returnComptime (Just returnTy) = + Just + [ ReturnItem returnComptime Nothing itemTy + | itemTy <- legacyTupleElements returnTy + ] + +legacyReturnView :: Maybe [ReturnItem] -> (Bool, Maybe Ty) +legacyReturnView Nothing = (False, Nothing) +legacyReturnView (Just items) = + ( any returnItemComptime items, + Just (returnItemsType items) + ) + +returnItemsType :: [ReturnItem] -> Ty +returnItemsType [] = TyCon (Name "()") [] +returnItemsType [item] = returnItemType item +returnItemsType items = foldr1 pairTy (map returnItemType items) + +legacyTupleElements :: Ty -> [Ty] +legacyTupleElements t@(TyCon n _) + | n == Name "pair" = tupleElementsForLegacy t +legacyTupleElements t = [t] + +tupleElementsForLegacy :: Ty -> [Ty] +tupleElementsForLegacy (TyCon (Name "pair") [left, right]) = + left : tupleElementsForLegacy right +tupleElementsForLegacy t = [t] + +legacyPayableView :: [FunctionModifier] -> Bool +legacyPayableView = + elem (MutabilityModifier MutabilityPayable) + data Instance = Instance { instDefault :: Bool, @@ -255,6 +462,7 @@ data ContractDecl = CDataDecl DataTy | CFieldDecl Field | CFunDecl FunDef + | CSignatureDecl Bool Signature | CConstrDecl Constructor deriving (Eq, Ord, Show, Data, Typeable) @@ -336,8 +544,12 @@ instance HasSourceSpan Contract where firstSourceSpan [sourceSpanOf n, sourceSpanOf tyParams', sourceSpanOf contractDecls] instance HasSourceSpan DataTy where - sourceSpanOf (DataTy n tyParams' constrs) = - firstSourceSpan [sourceSpanOf n, sourceSpanOf tyParams', sourceSpanOf constrs] + sourceSpanOf (DataTyWithKind kind n tyParams' constrs) = + firstSourceSpan [sourceSpanOf n, sourceSpanOf tyParams', sourceSpanOf kind, sourceSpanOf constrs] + +instance HasSourceSpan DataTyKind where + sourceSpanOf EnumKind = Nothing + sourceSpanOf (StructKind fieldNames) = sourceSpanOf fieldNames instance HasSourceSpan Constr where sourceSpanOf (Constr n tys) = @@ -356,8 +568,12 @@ instance HasSourceSpan Class where firstSourceSpan [sourceSpanOf boundVars, sourceSpanOf context, sourceSpanOf clsName, sourceSpanOf params, sourceSpanOf main, sourceSpanOf signatures'] instance HasSourceSpan Signature where - sourceSpanOf (Signature vars context sig params _ retTy _) = - firstSourceSpan [sourceSpanOf vars, sourceSpanOf context, sourceSpanOf sig, sourceSpanOf params, sourceSpanOf retTy] + sourceSpanOf (SignatureWithSyntax vars context sig params returnItems _) = + firstSourceSpan [sourceSpanOf vars, sourceSpanOf context, sourceSpanOf sig, sourceSpanOf params, sourceSpanOf returnItems] + +instance HasSourceSpan ReturnItem where + sourceSpanOf (ReturnItem _ returnName returnTy) = + firstSourceSpan [sourceSpanOf returnName, sourceSpanOf returnTy] instance HasSourceSpan Instance where sourceSpanOf (Instance _ vars context clsName params main funs) = @@ -375,6 +591,7 @@ instance HasSourceSpan ContractDecl where sourceSpanOf (CDataDecl dataTy) = sourceSpanOf dataTy sourceSpanOf (CFieldDecl field) = sourceSpanOf field sourceSpanOf (CFunDecl funDef) = sourceSpanOf funDef + sourceSpanOf (CSignatureDecl _ sig) = sourceSpanOf sig sourceSpanOf (CConstrDecl constructor) = sourceSpanOf constructor -- definition of statements @@ -392,12 +609,15 @@ data Stmt | StmtBOrEqWithLocation NodeLocation Exp Exp -- e1 |= e2 | StmtModEqWithLocation NodeLocation Exp Exp -- e1 %= e2 | LetWithLocation NodeLocation Bool Name (Maybe Ty) (Maybe Exp) -- local variable; Bool is True when 'comptime' modifier is present + | LetPatternWithLocation NodeLocation Bool Pat (Maybe Ty) Exp -- irrefutable tuple binding; Bool marks 'comptime' | BlockWithLocation NodeLocation Body -- lexical block | StmtExpWithLocation NodeLocation Exp -- expression level statements | ReturnWithLocation NodeLocation Exp -- return statements | MatchWithLocation NodeLocation [Exp] Equations -- pattern matching | AsmWithLocation NodeLocation YulBlock -- Yul block | IfWithLocation NodeLocation Exp Body Body -- If statement + | WhileWithLocation NodeLocation Exp Body -- source-level while; lowered during name resolution + | UncheckedWithLocation NodeLocation Body -- source-level unchecked block; lowered during name resolution | ForWithLocation NodeLocation Stmt Exp Stmt Body -- for(init; cond; post) { body } | BreakWithLocation NodeLocation -- break out of the innermost enclosing for loop | ContinueWithLocation NodeLocation -- continue to the next iteration of the innermost enclosing for loop @@ -444,6 +664,11 @@ pattern Let ct n ty value <- LetWithLocation _ ct n ty value where Let ct n ty value = LetWithLocation unlocatedNode ct n ty value +pattern LetPattern :: Bool -> Pat -> Maybe Ty -> Exp -> Stmt +pattern LetPattern ct pat ty value <- LetPatternWithLocation _ ct pat ty value + where + LetPattern ct pat ty value = LetPatternWithLocation unlocatedNode ct pat ty value + pattern Block :: Body -> Stmt pattern Block body <- BlockWithLocation _ body where @@ -474,6 +699,16 @@ pattern If cond thenBody elseBody <- IfWithLocation _ cond thenBody elseBody where If cond thenBody elseBody = IfWithLocation unlocatedNode cond thenBody elseBody +pattern While :: Exp -> Body -> Stmt +pattern While cond body <- WhileWithLocation _ cond body + where + While cond body = WhileWithLocation unlocatedNode cond body + +pattern Unchecked :: Body -> Stmt +pattern Unchecked body <- UncheckedWithLocation _ body + where + Unchecked body = UncheckedWithLocation unlocatedNode body + pattern For :: Stmt -> Exp -> Stmt -> Body -> Stmt pattern For initStmt cond postStmt body <- ForWithLocation _ initStmt cond postStmt body where @@ -494,7 +729,7 @@ pattern EmptyStmt <- EmptyStmtWithLocation _ where EmptyStmt = EmptyStmtWithLocation unlocatedNode -{-# COMPLETE Assign, StmtPlusEq, StmtMinusEq, StmtBXorEq, StmtBAndEq, StmtBOrEq, StmtModEq, Let, Block, StmtExp, Return, Match, Asm, If, For, Break, Continue, EmptyStmt #-} +{-# COMPLETE Assign, StmtPlusEq, StmtMinusEq, StmtBXorEq, StmtBAndEq, StmtBOrEq, StmtModEq, Let, LetPattern, Block, StmtExp, Return, Match, Asm, If, While, Unchecked, For, Break, Continue, EmptyStmt #-} type Body = [Stmt] @@ -523,12 +758,16 @@ locatedStmt sourceSpan (StmtModEq lhs rhs) = StmtModEqWithLocation location lhs locatedStmt sourceSpan (Let ct n ty value) = LetWithLocation location ct n ty value where location = locatedNode sourceSpan +locatedStmt sourceSpan (LetPattern ct pat ty value) = + LetPatternWithLocation (locatedNode sourceSpan) ct pat ty value locatedStmt sourceSpan (Block body) = BlockWithLocation (locatedNode sourceSpan) body locatedStmt sourceSpan (StmtExp exp) = StmtExpWithLocation (locatedNode sourceSpan) exp locatedStmt sourceSpan (Return exp) = ReturnWithLocation (locatedNode sourceSpan) exp locatedStmt sourceSpan (Match exps equations) = MatchWithLocation (locatedNode sourceSpan) exps equations locatedStmt sourceSpan (Asm block) = AsmWithLocation (locatedNode sourceSpan) block locatedStmt sourceSpan (If cond thenBody elseBody) = IfWithLocation (locatedNode sourceSpan) cond thenBody elseBody +locatedStmt sourceSpan (While cond body) = WhileWithLocation (locatedNode sourceSpan) cond body +locatedStmt sourceSpan (Unchecked body) = UncheckedWithLocation (locatedNode sourceSpan) body locatedStmt sourceSpan (For initStmt cond postStmt body) = ForWithLocation (locatedNode sourceSpan) initStmt cond postStmt body locatedStmt sourceSpan Break = BreakWithLocation (locatedNode sourceSpan) locatedStmt sourceSpan Continue = ContinueWithLocation (locatedNode sourceSpan) @@ -551,6 +790,8 @@ instance HasSourceSpan Stmt where firstSourceSpan [sourceSpanOf location, sourceSpanOf lhs, sourceSpanOf rhs] sourceSpanOf (LetWithLocation location _ n ty value) = firstSourceSpan [sourceSpanOf location, sourceSpanOf n, sourceSpanOf ty, sourceSpanOf value] + sourceSpanOf (LetPatternWithLocation location _ pat ty value) = + firstSourceSpan [sourceSpanOf location, sourceSpanOf pat, sourceSpanOf ty, sourceSpanOf value] sourceSpanOf (BlockWithLocation location body) = firstSourceSpan [sourceSpanOf location, sourceSpanOf body] sourceSpanOf (StmtExpWithLocation location exp) = @@ -563,6 +804,10 @@ instance HasSourceSpan Stmt where sourceSpanOf location sourceSpanOf (IfWithLocation location cond thenBody elseBody) = firstSourceSpan [sourceSpanOf location, sourceSpanOf cond, sourceSpanOf thenBody, sourceSpanOf elseBody] + sourceSpanOf (WhileWithLocation location cond body) = + firstSourceSpan [sourceSpanOf location, sourceSpanOf cond, sourceSpanOf body] + sourceSpanOf (UncheckedWithLocation location body) = + firstSourceSpan [sourceSpanOf location, sourceSpanOf body] sourceSpanOf (ForWithLocation location initStmt cond postStmt body) = firstSourceSpan [sourceSpanOf location, sourceSpanOf initStmt, sourceSpanOf cond, sourceSpanOf postStmt, sourceSpanOf body] sourceSpanOf (BreakWithLocation location) = @@ -591,13 +836,16 @@ data Exp | ExpVarWithLocation NodeLocation (Maybe Exp) Name -- variables or field access | ExpDotNameWithLocation NodeLocation Name [Exp] -- contextual constructor shorthand, e.g. .Some(1), .None | LamWithLocation NodeLocation [Param] Body (Maybe Ty) -- lambda-abstraction - | TyExpWithLocation NodeLocation Exp Ty -- type annotation expression + | TyExpWithLocation NodeLocation Exp Ty -- explicit type conversion expression | ExpIndexedWithLocation NodeLocation Exp Exp -- e1[e2] | ExpPlusWithLocation NodeLocation Exp Exp -- e1 + e2 | ExpMinusWithLocation NodeLocation Exp Exp -- e1 - e2 + | ExpPowerWithLocation NodeLocation Exp Exp -- e1 ** e2 | ExpTimesWithLocation NodeLocation Exp Exp -- e1 * e2 | ExpDivideWithLocation NodeLocation Exp Exp -- e1 / e2 | ExpModuloWithLocation NodeLocation Exp Exp -- e1 % e2 + | ExpShiftLWithLocation NodeLocation Exp Exp -- e1 << e2 + | ExpShiftRWithLocation NodeLocation Exp Exp -- e1 >> e2 | ExpBXorWithLocation NodeLocation Exp Exp -- e1 ^ e2 | ExpBAndWithLocation NodeLocation Exp Exp -- e1 & e2 | ExpBOrWithLocation NodeLocation Exp Exp -- e1 | e2 @@ -659,6 +907,11 @@ pattern ExpMinus lhs rhs <- ExpMinusWithLocation _ lhs rhs where ExpMinus lhs rhs = ExpMinusWithLocation unlocatedNode lhs rhs +pattern ExpPower :: Exp -> Exp -> Exp +pattern ExpPower lhs rhs <- ExpPowerWithLocation _ lhs rhs + where + ExpPower lhs rhs = ExpPowerWithLocation unlocatedNode lhs rhs + pattern ExpTimes :: Exp -> Exp -> Exp pattern ExpTimes lhs rhs <- ExpTimesWithLocation _ lhs rhs where @@ -674,6 +927,16 @@ pattern ExpModulo lhs rhs <- ExpModuloWithLocation _ lhs rhs where ExpModulo lhs rhs = ExpModuloWithLocation unlocatedNode lhs rhs +pattern ExpShiftL :: Exp -> Exp -> Exp +pattern ExpShiftL lhs rhs <- ExpShiftLWithLocation _ lhs rhs + where + ExpShiftL lhs rhs = ExpShiftLWithLocation unlocatedNode lhs rhs + +pattern ExpShiftR :: Exp -> Exp -> Exp +pattern ExpShiftR lhs rhs <- ExpShiftRWithLocation _ lhs rhs + where + ExpShiftR lhs rhs = ExpShiftRWithLocation unlocatedNode lhs rhs + pattern ExpBXor :: Exp -> Exp -> Exp pattern ExpBXor lhs rhs <- ExpBXorWithLocation _ lhs rhs where @@ -744,7 +1007,7 @@ pattern ExpAt ty <- ExpAtWithLocation _ ty where ExpAt ty = ExpAtWithLocation unlocatedNode ty -{-# COMPLETE Lit, ExpName, ExpVar, ExpDotName, Lam, TyExp, ExpIndexed, ExpPlus, ExpMinus, ExpTimes, ExpDivide, ExpModulo, ExpBXor, ExpBAnd, ExpBOr, ExpLT, ExpGT, ExpLE, ExpGE, ExpEE, ExpNE, ExpLAnd, ExpLOr, ExpLNot, ExpCond, ExpAt #-} +{-# COMPLETE Lit, ExpName, ExpVar, ExpDotName, Lam, TyExp, ExpIndexed, ExpPlus, ExpMinus, ExpPower, ExpTimes, ExpDivide, ExpModulo, ExpShiftL, ExpShiftR, ExpBXor, ExpBAnd, ExpBOr, ExpLT, ExpGT, ExpLE, ExpGE, ExpEE, ExpNE, ExpLAnd, ExpLOr, ExpLNot, ExpCond, ExpAt #-} locatedExp :: SourceSpan -> Exp -> Exp locatedExp sourceSpan (Lit lit) = LitWithLocation location lit @@ -758,9 +1021,12 @@ locatedExp sourceSpan (TyExp exp ty) = TyExpWithLocation (locatedNode sourceSpan locatedExp sourceSpan (ExpIndexed lhs rhs) = ExpIndexedWithLocation (locatedNode sourceSpan) lhs rhs locatedExp sourceSpan (ExpPlus lhs rhs) = ExpPlusWithLocation (locatedNode sourceSpan) lhs rhs locatedExp sourceSpan (ExpMinus lhs rhs) = ExpMinusWithLocation (locatedNode sourceSpan) lhs rhs +locatedExp sourceSpan (ExpPower lhs rhs) = ExpPowerWithLocation (locatedNode sourceSpan) lhs rhs locatedExp sourceSpan (ExpTimes lhs rhs) = ExpTimesWithLocation (locatedNode sourceSpan) lhs rhs locatedExp sourceSpan (ExpDivide lhs rhs) = ExpDivideWithLocation (locatedNode sourceSpan) lhs rhs locatedExp sourceSpan (ExpModulo lhs rhs) = ExpModuloWithLocation (locatedNode sourceSpan) lhs rhs +locatedExp sourceSpan (ExpShiftL lhs rhs) = ExpShiftLWithLocation (locatedNode sourceSpan) lhs rhs +locatedExp sourceSpan (ExpShiftR lhs rhs) = ExpShiftRWithLocation (locatedNode sourceSpan) lhs rhs locatedExp sourceSpan (ExpBXor lhs rhs) = ExpBXorWithLocation (locatedNode sourceSpan) lhs rhs locatedExp sourceSpan (ExpBAnd lhs rhs) = ExpBAndWithLocation (locatedNode sourceSpan) lhs rhs locatedExp sourceSpan (ExpBOr lhs rhs) = ExpBOrWithLocation (locatedNode sourceSpan) lhs rhs @@ -794,12 +1060,18 @@ instance HasSourceSpan Exp where firstSourceSpan [sourceSpanOf location, sourceSpanOf lhs, sourceSpanOf rhs] sourceSpanOf (ExpMinusWithLocation location lhs rhs) = firstSourceSpan [sourceSpanOf location, sourceSpanOf lhs, sourceSpanOf rhs] + sourceSpanOf (ExpPowerWithLocation location lhs rhs) = + firstSourceSpan [sourceSpanOf location, sourceSpanOf lhs, sourceSpanOf rhs] sourceSpanOf (ExpTimesWithLocation location lhs rhs) = firstSourceSpan [sourceSpanOf location, sourceSpanOf lhs, sourceSpanOf rhs] sourceSpanOf (ExpDivideWithLocation location lhs rhs) = firstSourceSpan [sourceSpanOf location, sourceSpanOf lhs, sourceSpanOf rhs] sourceSpanOf (ExpModuloWithLocation location lhs rhs) = firstSourceSpan [sourceSpanOf location, sourceSpanOf lhs, sourceSpanOf rhs] + sourceSpanOf (ExpShiftLWithLocation location lhs rhs) = + firstSourceSpan [sourceSpanOf location, sourceSpanOf lhs, sourceSpanOf rhs] + sourceSpanOf (ExpShiftRWithLocation location lhs rhs) = + firstSourceSpan [sourceSpanOf location, sourceSpanOf lhs, sourceSpanOf rhs] sourceSpanOf (ExpBXorWithLocation location lhs rhs) = firstSourceSpan [sourceSpanOf location, sourceSpanOf lhs, sourceSpanOf rhs] sourceSpanOf (ExpBAndWithLocation location lhs rhs) = diff --git a/src/Solcore/Frontend/TypeInference/Erase.hs b/src/Solcore/Frontend/TypeInference/Erase.hs index b4583cf12..f1d9f2a24 100644 --- a/src/Solcore/Frontend/TypeInference/Erase.hs +++ b/src/Solcore/Frontend/TypeInference/Erase.hs @@ -47,6 +47,8 @@ instance Erase (Stmt Id) where (erase e1) := (erase e2) erase (Let c n mt me) = Let c (idName n) mt (erase me) + erase (LetPattern ct pat mt value) = + LetPattern ct (erase pat) mt (erase value) erase (Block body) = Block (erase body) erase (StmtExp e) = diff --git a/src/Solcore/Frontend/TypeInference/SccAnalysis.hs b/src/Solcore/Frontend/TypeInference/SccAnalysis.hs index ef68d6edf..e7eb023e2 100644 --- a/src/Solcore/Frontend/TypeInference/SccAnalysis.hs +++ b/src/Solcore/Frontend/TypeInference/SccAnalysis.hs @@ -145,6 +145,7 @@ instance Decl (ContractDecl Name) where decl (CDataDecl dt) = decl dt decl (CFieldDecl fd) = decl fd decl (CFunDecl fd) = decl fd + decl (CSignatureDecl _ sig) = decl sig decl (CMutualDecl ds) = concatMap decl ds decl (CConstrDecl _) = [] @@ -194,6 +195,8 @@ instance Names (Stmt Name) where names [e1, e2] names (Let _ _ mt me) = names mt `union` names me + names (LetPattern _ _ mt value) = + names mt `union` names value names (Block body) = names body names (StmtExp e) = @@ -265,6 +268,7 @@ instance Names (ContractDecl Name) where names (CDataDecl dt) = names dt names (CFieldDecl fd) = names fd names (CFunDecl fd) = names fd + names (CSignatureDecl _ sig) = names sig names (CMutualDecl cs) = names cs names (CConstrDecl cd) = names cd diff --git a/src/Solcore/Frontend/TypeInference/TcContract.hs b/src/Solcore/Frontend/TypeInference/TcContract.hs index 6cee63f33..488dd4d94 100644 --- a/src/Solcore/Frontend/TypeInference/TcContract.hs +++ b/src/Solcore/Frontend/TypeInference/TcContract.hs @@ -295,13 +295,19 @@ initializeEnv (Contract _ _ cdecls) = do [fd | CFunDecl fd@(FunDef _ sig _) <- cdecls, hasAnn sig] ++ [fd | CMutualDecl ds <- cdecls, CFunDecl fd@(FunDef _ sig _) <- ds, hasAnn sig] nmschs <- extractSignatures fds - mapM_ (uncurry extEnv) nmschs + signatureSchemes <- + forM [sig | CSignatureDecl _ sig <- cdecls] $ \sig -> do + scheme <- annotatedScheme [] [] sig + pure (sigName sig, scheme) + mapM_ (uncurry extEnv) (nmschs ++ signatureSchemes) checkDecl :: ContractDecl Name -> TcM () checkDecl (CDataDecl dt) = checkDataType dt checkDecl (CFunDecl (FunDef _ sig _)) = extSignature sig +checkDecl (CSignatureDecl _ sig) = + extSignature sig checkDecl (CFieldDecl fd) = tcField fd >> return () checkDecl (CMutualDecl ds) = @@ -319,6 +325,8 @@ tcDecl (CFunDecl d) = case d' of [] -> tcmError "Impossible! Empty function binding!" (x : _) -> pure (CFunDecl x) +tcDecl (CSignatureDecl isPublic sig) = + CSignatureDecl isPublic <$> tcContractSignature sig tcDecl (CMutualDecl ds) = do let f (CFunDecl fd) = fd @@ -330,6 +338,23 @@ tcDecl (CMutualDecl ds) = tcDecl (CConstrDecl cd) = CConstrDecl <$> tcConstructor cd tcDecl (CDataDecl d) = CDataDecl <$> tcDataDecl d +-- Interface declarations contribute a callable, fully checked signature but +-- deliberately have no body to infer. +tcContractSignature :: Signature Name -> TcM (Signature Id) +tcContractSignature sig@(Signature vars predicates n params retComptime returnTy payable) = do + unless (isFullyAnnotated sig) (topLevelFunctionAnnotationError sig) + checkAllTypeVarsBound sig (bv sig) vars + checkConstraints predicates `wrapError` sig + params' <- mapM tcSignatureParam params + returnTy' <- traverse kindCheck returnTy `wrapError` sig + pure (Signature vars predicates n params' retComptime returnTy' payable) + where + tcSignatureParam p@(Typed comptime paramName' ty) = do + ty' <- kindCheck ty `wrapError` p + pure (Typed comptime (Id paramName' ty') ty') + tcSignatureParam (Untyped _ _) = + tcmError "Interface function parameters must have type annotations" + -- kind check data declarations tcDataDecl :: DataTy -> TcM DataTy diff --git a/src/Solcore/Frontend/TypeInference/TcMonad.hs b/src/Solcore/Frontend/TypeInference/TcMonad.hs index 81be22e0b..435a9e216 100644 --- a/src/Solcore/Frontend/TypeInference/TcMonad.hs +++ b/src/Solcore/Frontend/TypeInference/TcMonad.hs @@ -3,6 +3,7 @@ module Solcore.Frontend.TypeInference.TcMonad where import Control.Monad import Control.Monad.Except import Control.Monad.State +import Data.Char (isDigit) import Data.Generics (Data, everything, extQ, mkQ) import Data.List import Data.List.NonEmpty qualified as N @@ -224,7 +225,14 @@ kindCheck (t1 :-> t2) = kindCheck t@(TyCon n ts) = do ti <- askTypeInfo n `wrapError` t - unless (n == Name "pair" || arity ti == length ts) $ + let suppliedArity = + case ts of + [size, _] + | n == Name "array", + isNumericArraySize size -> + 1 + _ -> length ts + unless (n == Name "pair" || arity ti == suppliedArity) $ tcmError $ unlines [ "Invalid number of type arguments!", @@ -236,13 +244,26 @@ kindCheck t@(TyCon n ts) = "but, type " ++ pretty t ++ " has " - ++ (show $ length ts) + ++ show suppliedArity ++ " arguments" ] - ts' <- mapM kindCheck ts + ts' <- + case ts of + [size, element] + | n == Name "array", + isNumericArraySize size -> + (size :) . (: []) <$> kindCheck element + _ -> + mapM kindCheck ts pure (TyCon n ts') kindCheck t = pure t +isNumericArraySize :: Ty -> Bool +isNumericArraySize (TyCon (Name digits) []) = + not (null digits) && all isDigit digits +isNumericArraySize _ = + False + -- Skolemization skolemise :: Scheme -> TcM ([Tyvar], Qual Ty) @@ -926,13 +947,13 @@ topLevelFunctionAnnotationError sig = (sigName sig) "incomplete signature" ["signature: " ++ pretty sig] - ["annotate every parameter (name : Type) and provide a return type (-> Type)"] + ["annotate every parameter (name: Type) and provide a return type (returns (Type))"] methodAnnotationError :: Signature Name -> TcM a methodAnnotationError sig = tcDiagnosticErrorAtName "SC0221" - "class and instance methods must have complete type signatures" + "trait and impl methods must have complete type signatures" (sigName sig) "incomplete method signature" ["signature: " ++ pretty sig] diff --git a/src/Solcore/Frontend/TypeInference/TcStmt.hs b/src/Solcore/Frontend/TypeInference/TcStmt.hs index 94dc775f4..e242d8902 100644 --- a/src/Solcore/Frontend/TypeInference/TcStmt.hs +++ b/src/Solcore/Frontend/TypeInference/TcStmt.hs @@ -91,6 +91,15 @@ tcStmtWithExpectedReturn' _ e@(Let ct n mt me) = extEnv n (monotype tf) let e' = Let ct (Id n tf) (Just tf) me' withCurrentSubst (e', psf, unit) +tcStmtWithExpectedReturn' _ stmt@(LetPattern ct pat mt value) = + do + (pat', value', ps, valueTy) <- tcLetPattern stmt pat mt value + lowered <- lowerLetPattern stmt ct pat' value' valueTy [] + let loweredStmt = + case lowered of + [single] -> single + stmts -> Block stmts + pure (loweredStmt, ps, unit) tcStmtWithExpectedReturn' mExpectedReturn (Block body) = withLocalCtx [] $ do (body', ps, t) <- tcBodyWithExpectedReturn mExpectedReturn body @@ -1490,6 +1499,12 @@ tcBody = tcBodyWithExpectedReturn Nothing tcBodyWithExpectedReturn :: Maybe Ty -> Body Name -> TcM (Body Id, [Pred], Ty) tcBodyWithExpectedReturn _ [] = pure ([], [], unit) +tcBodyWithExpectedReturn mExpectedReturn (stmt@(LetPattern ct pat mt value) : rest) = + do + (pat', value', ps, valueTy) <- tcLetPattern stmt pat mt value + (rest', restPreds, resultTy) <- tcBodyWithExpectedReturn mExpectedReturn rest + lowered <- lowerLetPattern stmt ct pat' value' valueTy rest' + pure (lowered, ps ++ restPreds, resultTy) tcBodyWithExpectedReturn mExpectedReturn [s] = do (s', ps', t') <- tcStmtWithExpectedReturn mExpectedReturn s @@ -1502,6 +1517,61 @@ tcBodyWithExpectedReturn mExpectedReturn (s : ss) = (bd', ps1, t1) <- tcBodyWithExpectedReturn mExpectedReturn ss pure (s' : bd', ps' ++ ps1, t1) +-- Type a tuple binding before its continuation, then make every bound leaf +-- available to that continuation. The body checker lowers the binding to an +-- irrefutable one-arm match so the existing decision-tree compiler performs +-- tuple projection without evaluating the initializer more than once. +tcLetPattern :: + Stmt Name -> + Pat Name -> + Maybe Ty -> + Exp Name -> + TcM (Pat Id, Exp Id, [Pred], Ty) +tcLetPattern stmt pat mt value = do + (value', preds, valueTy) <- + case mt of + Just annotatedTy -> do + checkedTy <- kindCheck annotatedTy `wrapError` stmt + let boundVars = bv checkedTy + skolems <- mapM (const freshTyVar) boundVars + let expectedTy = insts (zip boundVars skolems) checkedTy + (value'', preds', inferredTy) <- + tcExpWithExpected (Just expectedTy) value + matchedSubst <- tcmMatch inferredTy expectedTy `wrapError` stmt + _ <- extSubst matchedSubst + withCurrentSubst (value'', preds', expectedTy) + Nothing -> + tcExp value + (pat', _, bindings) <- tcPat valueTy pat `wrapError` stmt + bindings' <- withCurrentSubst bindings + mapM_ (uncurry extEnv) bindings' + (pat'', value'', preds') <- withCurrentSubst (pat', value', preds) + valueTy' <- withCurrentSubst valueTy + pure (pat'', value'', preds', valueTy') + +lowerLetPattern :: + Stmt Name -> + Bool -> + Pat Id -> + Exp Id -> + Ty -> + Body Id -> + TcM (Body Id) +lowerLetPattern source isComptime pat value valueTy continuation = do + let matchOn scrutinee = + locatedLike source locatedStmt $ + Match [scrutinee] [([pat], continuation)] + if isComptime + then do + temporaryName <- freshName + let temporary = Id temporaryName valueTy + bindTemporary = + locatedLike source locatedStmt $ + Let True temporary (Just valueTy) (Just value) + pure [bindTemporary, matchOn (Var temporary)] + else + pure [matchOn value] + tcCall :: Maybe (Exp Name) -> Name -> [Exp Name] -> TcM (Exp Id, [Pred], Ty) tcCall Nothing n args = do @@ -1901,6 +1971,7 @@ instance Vars (Stmt Id) where free (e1 := e2) = free [e1, e2] free (Let _ _ _ (Just e)) = free e free (Let _ _ _ _) = [] + free (LetPattern _ _ _ value) = free value free (Block body) = free body free (StmtExp e) = free e free (Return e) = free e @@ -1914,6 +1985,7 @@ instance Vars (Stmt Id) where free EmptyStmt = [] bound (Let _ n _ _) = [n] + bound (LetPattern _ pat _ _) = bound pat bound (Block _) = [] bound _ = [] diff --git a/src/Solcore/Frontend/TypeInference/TcSubst.hs b/src/Solcore/Frontend/TypeInference/TcSubst.hs index 92f4e6658..35aeb8ad4 100644 --- a/src/Solcore/Frontend/TypeInference/TcSubst.hs +++ b/src/Solcore/Frontend/TypeInference/TcSubst.hs @@ -258,6 +258,12 @@ instance (HasType a) => HasType (Stmt a) where (apply s v) (apply s <$> mt) (apply s <$> me) + apply s (LetPattern ct pat mt value) = + LetPattern + ct + (apply s pat) + (apply s <$> mt) + (apply s value) apply s (Block body) = Block (apply s body) apply s (StmtExp e) = @@ -292,6 +298,10 @@ instance (HasType a) => HasType (Stmt a) where fv v `union` (maybe [] fv mt) `union` (maybe [] fv me) + fv (LetPattern _ pat mt value) = + fv pat + `union` (maybe [] fv mt) + `union` fv value fv (Block body) = fv body fv (StmtExp e) = fv e fv (Return e) = fv e @@ -311,6 +321,10 @@ instance (HasType a) => HasType (Stmt a) where mv v `union` (maybe [] mv mt) `union` (maybe [] mv me) + mv (LetPattern _ pat mt value) = + mv pat + `union` (maybe [] mv mt) + `union` mv value mv (Block body) = mv body mv (StmtExp e) = mv e mv (Return e) = mv e @@ -330,6 +344,10 @@ instance (HasType a) => HasType (Stmt a) where bv v `union` (maybe [] bv mt) `union` (maybe [] bv me) + bv (LetPattern _ pat mt value) = + bv pat + `union` (maybe [] bv mt) + `union` bv value bv (Block body) = bv body bv (StmtExp e) = bv e bv (Return e) = bv e @@ -399,6 +417,8 @@ instance (HasType a) => HasType (ContractDecl a) where CFieldDecl (apply s fd) apply s (CFunDecl d) = CFunDecl (apply s d) + apply s (CSignatureDecl isPublic sig) = + CSignatureDecl isPublic (apply s sig) apply s (CMutualDecl cs) = CMutualDecl (apply s cs) apply s (CConstrDecl c) = @@ -407,18 +427,21 @@ instance (HasType a) => HasType (ContractDecl a) where fv (CFieldDecl d) = fv d fv (CFunDecl d) = fv d + fv (CSignatureDecl _ sig) = fv sig fv (CMutualDecl ds) = fv ds fv (CConstrDecl c) = fv c fv _ = [] mv (CFieldDecl d) = mv d mv (CFunDecl d) = mv d + mv (CSignatureDecl _ sig) = mv sig mv (CMutualDecl ds) = mv ds mv (CConstrDecl c) = mv c mv _ = [] bv (CFieldDecl d) = bv d bv (CFunDecl d) = bv d + bv (CSignatureDecl _ sig) = bv sig bv (CMutualDecl ds) = bv ds bv (CConstrDecl c) = bv c bv _ = [] diff --git a/src/Solcore/Pipeline/SolcorePipeline.hs b/src/Solcore/Pipeline/SolcorePipeline.hs index 6a3046527..f6333e573 100644 --- a/src/Solcore/Pipeline/SolcorePipeline.hs +++ b/src/Solcore/Pipeline/SolcorePipeline.hs @@ -523,17 +523,21 @@ declarationSearchTerms diagnostic = where declarationTerms raw = case words (stripContextPrefix (trim raw)) of - "function" : declName : _ -> [stripTrailingParens declName] - "contract" : declName : _ -> [stripTrailingParens declName] - "class" : _vars : ":" : declName : _ -> [stripTrailingParens declName] - "class" : declName : _ -> [stripTrailingParens declName] - "data" : declName : _ -> [stripTrailingParens declName] - "type" : declName : _ -> [stripTrailingParens declName] + "function" : declName : _ -> [stripDeclarationSuffix declName] + "contract" : declName : _ -> [stripDeclarationSuffix declName] + "trait" : declName : _ -> [stripDeclarationSuffix declName] + "enum" : declName : _ -> [stripDeclarationSuffix declName] + "type" : declName : _ -> [stripDeclarationSuffix declName] "constructor" : _ -> ["constructor"] - "instance" : _mainTy : ":" : instanceClassName : _ -> [stripTrailingParens instanceClassName, "instance"] - "instance" : instanceClassName : _ -> [stripTrailingParens instanceClassName, "instance"] + implToken : implName : _ + | "impl" `isPrefixOf` implToken -> [stripDeclarationSuffix implName, "impl"] + "default" : implToken : implName : _ + | "impl" `isPrefixOf` implToken -> [stripDeclarationSuffix implName, "impl"] _ -> [] + stripDeclarationSuffix = + takeWhile (\c -> c /= '(' && c /= '<' && c /= ',' && c /= ';' && c /= '{') + inContextSearchTerms :: Diagnostic -> [String] inContextSearchTerms diagnostic = concatMap contextTerms (allDiagnosticText diagnostic) @@ -553,10 +557,6 @@ stripContextPrefix raw = Just rest -> trim rest Nothing -> raw -stripTrailingParens :: String -> String -stripTrailingParens = - takeWhile (\c -> c /= '(' && c /= ',' && c /= ';' && c /= '{') - prefixedTerms :: [String] -> String -> [String] prefixedTerms prefixes body = [trim rest | prefix <- prefixes, Just rest <- [stripPrefix prefix body]] diff --git a/test/LocationTests.hs b/test/LocationTests.hs index 8c37e5baa..9be1b4e61 100644 --- a/test/LocationTests.hs +++ b/test/LocationTests.hs @@ -7,12 +7,14 @@ import Data.Generics (Data, everything, mkQ) import Data.Maybe (mapMaybe) import Data.Set qualified as Set import Solcore.Diagnostics (CompilerError, SourceSpan (..), compilerErrorText) +import Solcore.Frontend.ComptimeCheck (checkComptimeEarly) import Solcore.Frontend.Parser.SolcoreParser (parseCompUnitWithPath) import Solcore.Frontend.Syntax qualified as Typed import Solcore.Frontend.Syntax.Location import Solcore.Frontend.Syntax.NameResolution (nameResolution) import Solcore.Frontend.Syntax.SyntaxTree qualified as Parsed import Solcore.Frontend.TypeInference.SccAnalysis (sccAnalysis) +import Solcore.Frontend.TypeInference.Id (Id) import Solcore.Frontend.TypeInference.TcModule import Solcore.Pipeline.Options (stdOpt) import Test.Tasty @@ -26,7 +28,9 @@ locationTests = testCase "generated nodes are explicit" test_generatedNodesAreExplicit, testCase "name resolution preserves source locations" test_nameResolutionPreservesSourceLocations, testCase "SCC analysis preserves source locations" test_sccAnalysisPreservesSourceLocations, - testCase "type inference preserves source locations" test_typeInferencePreservesSourceLocations + testCase "type inference preserves source locations" test_typeInferencePreservesSourceLocations, + testCase "tuple destructuring binds typed and inferred recursive leaves" test_tupleDestructuringTypeChecks, + testCase "comptime tuple destructuring propagates and checks binding ctness" test_comptimeTupleDestructuring ] test_parsedNodesCarrySourceLocations :: Assertion @@ -72,6 +76,56 @@ test_typeInferencePreservesSourceLocations = do (typeInferModuleLocals stdOpt (moduleInputFromUnit resolved)) assertSpansPreserved "type inference" resolved typedUnit +test_tupleDestructuringTypeChecks :: Assertion +test_tupleDestructuringTypeChecks = do + parsed <- parseUnit "destructuring-let.solc" destructuringSource + resolved <- assertCompilerRight "name resolution" (nameResolution parsed) + _ <- + assertCompilerRight + "tuple destructuring type inference" + (typeInferModuleLocals stdOpt (moduleInputFromUnit resolved)) + badParsed <- parseUnit "destructuring-let-mismatch.solc" badDestructuringSource + badResolved <- assertCompilerRight "name resolution" (nameResolution badParsed) + badResult <- + typeInferModuleLocals stdOpt (moduleInputFromUnit badResolved) + case badResult of + Left _ -> pure () + Right _ -> + assertFailure "a tuple binding annotation must describe the complete initializer type" + +test_comptimeTupleDestructuring :: Assertion +test_comptimeTupleDestructuring = do + goodUnit <- inferUnit "comptime-destructuring-good.solc" comptimeDestructuringSource + assertEitherRight + "comptime tuple bindings should remain comptime in their continuation" + (checkComptimeEarly (sourceFunctionsOnly goodUnit)) + badUnit <- inferUnit "comptime-destructuring-bad.solc" runtimeDestructuringSource + case checkComptimeEarly (sourceFunctionsOnly badUnit) of + Left _ -> pure () + Right () -> + assertFailure "a comptime tuple binding must reject a runtime initializer" + propagatedUnit <- + inferUnit + "runtime-destructuring-propagation.solc" + runtimeDestructuringPropagationSource + case checkComptimeEarly (sourceFunctionsOnly propagatedUnit) of + Left _ -> pure () + Right () -> + assertFailure "runtime ctness must propagate through source tuple destructuring" + +sourceFunctionsOnly :: Typed.CompUnit Id -> Typed.CompUnit Id +sourceFunctionsOnly (Typed.CompUnit imps decls) = + Typed.CompUnit imps [decl | decl@(Typed.TFunDef _) <- decls] + +inferUnit :: FilePath -> String -> IO (Typed.CompUnit Id) +inferUnit path source = do + parsed <- parseUnit path source + resolved <- assertCompilerRight "name resolution" (nameResolution parsed) + fst + <$> assertCompilerRight + "type inference" + (typeInferModuleLocals stdOpt (moduleInputFromUnit resolved)) + hasSourceSpan :: (HasSourceSpan a) => a -> Bool hasSourceSpan = maybe False (const True) . sourceSpanOf @@ -152,12 +206,16 @@ sampleSpan = locatedSource :: String locatedSource = unlines - [ "data Bool = True | False;", - "function main(x : word) -> word {", - " let y : word = x + 1;", - " match Bool.True {", - " | Bool.True => return y;", - " | _ => return 0;", + [ "enum Bool { True, False }", + "function main(x: word) returns (word) {", + " let y: word = x + 1;", + " match (Bool.True) {", + " case Bool.True {", + " return y;", + " }", + " case Bool.False {", + " return 0;", + " }", " }", "}" ] @@ -165,10 +223,10 @@ locatedSource = transformSource :: String transformSource = unlines - [ "function id(x : word) -> word {", + [ "function id(x: word) returns (word) {", " return x;", "}", - "function passthrough(y : word) -> word {", + "function passthrough(y: word) returns (word) {", " return id(y);", "}" ] @@ -176,10 +234,65 @@ transformSource = mutualSource :: String mutualSource = unlines - [ "function first(x : word) -> word {", + [ "function first(x: word) returns (word) {", " return second(x);", "}", - "function second(x : word) -> word {", + "function second(x: word) returns (word) {", " return first(x);", "}" ] + +destructuringSource :: String +destructuringSource = + unlines + [ "function typed(value: (word, bool)) returns (word) {", + " let (amount, ok): (word, bool) = value;", + " if (ok) { return amount; } else { return amount; }", + "}", + "function nested(value: (word, (bool, word))) returns (word) {", + " let (amount, (ok, fallbackValue)) = value;", + " if (ok) { return amount; } else { return fallbackValue; }", + "}" + ] + +badDestructuringSource :: String +badDestructuringSource = + unlines + [ "function bad(value: (word, word)) returns (word) {", + " let (amount, ok): (word, bool) = value;", + " if (ok) { return amount; } else { return amount; }", + "}" + ] + +comptimeDestructuringSource :: String +comptimeDestructuringSource = + unlines + [ "function consume(comptime x: bool) returns (bool) {", + " return x;", + "}", + "function good() returns (bool) {", + " let comptime (left, right): (bool, bool) = (true, false);", + " return consume(left);", + "}" + ] + +runtimeDestructuringSource :: String +runtimeDestructuringSource = + unlines + [ "function bad(value: (word, word)) returns (word) {", + " let comptime (left, right) = value;", + " return left;", + "}" + ] + +runtimeDestructuringPropagationSource :: String +runtimeDestructuringPropagationSource = + unlines + [ "function consume(comptime value: word) returns (word) {", + " return value;", + "}", + "function bad(value: (word, word)) returns (word) {", + " let (left, right) = value;", + " return consume(left);", + "}" + ] diff --git a/test/ModuleTypeCheckTests.hs b/test/ModuleTypeCheckTests.hs index 53efa0f77..7959bdf99 100644 --- a/test/ModuleTypeCheckTests.hs +++ b/test/ModuleTypeCheckTests.hs @@ -4,7 +4,24 @@ module ModuleTypeCheckTests where import Solcore.Diagnostics (CompilerError, compilerErrorText) +import Solcore.Frontend.Module.Loader + ( ModuleGraph (entryModule), + ModuleTypeCheckSurface (moduleSurfaceImportedDecls), + loadModuleGraph, + moduleLocalTypeCheckSurface, + ) +import Solcore.Frontend.Parser.SolcoreParser (parseCompUnit) +import Solcore.Frontend.Pretty.TreePretty qualified as TreePretty import Solcore.Frontend.Syntax +import Solcore.Frontend.Syntax.NameResolution (nameResolution) +import Solcore.Frontend.Syntax.SyntaxTree qualified as Source +import Solcore.Frontend.TypeInference.Id (Id) +import Solcore.Frontend.TypeInference.TcContract + ( TopDeclCheck (..), + TopDeclCheckMode (CheckTopDeclBody), + typeInferTopDeclChecks, + ) +import Solcore.Frontend.TypeInference.TcEnv (TcEnv) import Solcore.Frontend.TypeInference.TcModule import Solcore.Pipeline.Options (stdOpt) import Test.Tasty @@ -55,7 +72,103 @@ moduleTypeCheckTests = typeInferModuleLocals stdOpt (moduleInput [ModuleInferenceDecl ModuleLocalDecl badImportedFun]) - assertLeft "local body should be checked" result + assertLeft "local body should be checked" result, + testCase "numeric fixed-array size survives resolution and kind checking" $ do + parsedResult <- + parseCompUnit $ + unlines + [ "enum array { array }", + "function accept(xs: word[4]) returns (()) {", + " return;", + "}" + ] + parsed <- + case parsedResult of + Left err -> assertFailure ("unexpected parse failure:\n" ++ err) + Right compUnit -> pure compUnit + resolvedResult <- nameResolution parsed + case resolvedResult of + Left err -> + assertFailure + ("unexpected name-resolution failure:\n" ++ compilerErrorText err) + Right (CompUnit resolvedImports resolvedDecls) -> do + checked <- + typeInferTopDeclChecks + stdOpt + resolvedImports + [] + [] + [ TopDeclCheck CheckTopDeclBody decl + | decl <- resolvedDecls + ] + assertRight + "numeric fixed-array size should be kind-correct" + checked, + testCase "interface signature has no body to typecheck" $ do + checked <- + typecheckSource $ + unlines + [ "interface Reader {", + " function read(key: word) external view returns (word);", + "}" + ] + case checked of + Left err -> + assertFailure + ("interface signature should typecheck:\n" ++ compilerErrorText err) + Right (CompUnit _ [TContr (Contract _ _ [CSignatureDecl isExternal sig])], _) -> do + assertBool "external visibility is preserved" isExternal + assertEqual "signature name" (Name "read") (sigName sig) + assertEqual "signature return" (Just wordTy) (sigReturn sig) + Right other -> + assertFailure ("unexpected typed interface shape: " ++ show (fst other)), + testCase "ordinary contract function still checks its empty body" $ do + checked <- + typecheckSource $ + unlines + [ "contract Reader {", + " function read(key: word) external view returns (word) {}", + "}" + ] + assertLeft "non-unit contract function with an empty body" checked, + testCase "selective struct import preserves source metadata and pretty round-trips" $ do + graphResult <- + loadModuleGraph + "test/imports" + Nothing + [] + "test/imports/struct_metadata_main.solc" + graph <- + case graphResult of + Left err -> assertFailure ("unexpected module load failure:\n" ++ err) + Right loadedGraph -> pure loadedGraph + surface <- + case moduleLocalTypeCheckSurface graph (entryModule graph) of + Left err -> assertFailure ("unexpected module surface failure:\n" ++ err) + Right loadedSurface -> pure loadedSurface + importedStruct <- + case + [ dt + | Source.TDataDef dt <- moduleSurfaceImportedDecls surface, + Source.dataName dt == "RenamedPair" + ] of + [dt] -> pure dt + unexpectedDecls -> + assertFailure + ("expected one imported RenamedPair declaration, got " ++ show unexpectedDecls) + assertEqual + "renamed struct retains kind, field names, and field types" + (Source.StructTy "RenamedPair" [] ["left", "right"] [Source.TyCon "word" [], Source.TyCon "bool" []]) + importedStruct + let rendered = TreePretty.pretty (Source.TDataDef importedStruct) + reparsed <- parseCompUnit rendered + case reparsed of + Left err -> assertFailure ("pretty-printed imported struct did not parse:\n" ++ err) + Right unit -> + assertEqual + ("round trip: " ++ rendered) + (Source.CompUnit [] [Source.TDataDef importedStruct]) + unit ] assertRight :: String -> Either CompilerError a -> Assertion @@ -68,6 +181,26 @@ assertLeft _ (Left _) = pure () assertLeft label (Right _) = assertFailure (label ++ ": expected failure") +typecheckSource :: String -> IO (Either CompilerError (CompUnit Id, TcEnv)) +typecheckSource source = do + parsedResult <- parseCompUnit source + parsed <- + case parsedResult of + Left err -> assertFailure ("unexpected parse failure:\n" ++ err) + Right compUnit -> pure compUnit + resolvedResult <- nameResolution parsed + case resolvedResult of + Left err -> pure (Left err) + Right (CompUnit resolvedImports resolvedDecls) -> + typeInferTopDeclChecks + stdOpt + resolvedImports + [] + [] + [ TopDeclCheck CheckTopDeclBody decl + | decl <- resolvedDecls + ] + moduleInput :: [ModuleInferenceDecl] -> ModuleTypeCheckInput moduleInput inferenceDecls = withPreparedModuleInferenceDecls (resolvedModuleInput inferenceDecls) inferenceDecls diff --git a/test/ParserTests.hs b/test/ParserTests.hs index 053d2d679..633c9cefc 100644 --- a/test/ParserTests.hs +++ b/test/ParserTests.hs @@ -3,14 +3,19 @@ module ParserTests (parserTests) where import Common.LightYear (Parser, runParserE) +import Data.List.NonEmpty (NonEmpty ((:|))) import Solcore.Frontend.Lexer.SolcoreLexer (sc) -import Solcore.Frontend.Parser.Decl (topDeclP) +import Solcore.Frontend.Parser.Decl (importP, topDeclP) import Solcore.Frontend.Parser.Expr (exprP) import Solcore.Frontend.Parser.Patterns (patP) import Solcore.Frontend.Parser.SolcoreTypes (predP, typeP) import Solcore.Frontend.Parser.Stmt (bodyP, stmtP) +import Solcore.Frontend.Pretty.TreePretty qualified as TreePretty +import Solcore.Frontend.Syntax.Contract qualified as Resolved import Solcore.Frontend.Syntax.Name +import Solcore.Frontend.Syntax.NameResolution (nameResolution) import Solcore.Frontend.Syntax.SyntaxTree +import Solcore.Frontend.Syntax.Ty qualified as ResolvedTy import Test.Tasty import Test.Tasty.HUnit import Text.Megaparsec (eof) @@ -27,6 +32,79 @@ parseFails p src = Left _ -> return () Right got -> assertFailure ("Expected failure but parsed: " ++ show got) +nameResolutionFails :: String -> Assertion +nameResolutionFails src = + case runParserE (sc *> topDeclP <* eof) "" src of + Left err -> assertFailure ("Parse error:\n" ++ err) + Right parsed -> do + resolved <- nameResolution (CompUnit [] [parsed]) + case resolved of + Left _ -> pure () + Right got -> + assertFailure + ("Expected name-resolution failure but resolved: " ++ show got) + +nameResolutionSucceeds :: String -> Assertion +nameResolutionSucceeds src = + case runParserE (sc *> topDeclP <* eof) "" src of + Left err -> assertFailure ("Parse error:\n" ++ err) + Right parsed -> do + resolved <- nameResolution (CompUnit [] [parsed]) + case resolved of + Left err -> assertFailure ("Name resolution failed: " ++ show err) + Right _ -> pure () + +roundTripsTopDecl :: String -> Assertion +roundTripsTopDecl src = + case runParserE (sc *> topDeclP <* eof) "" src of + Left err -> assertFailure ("Initial parse error:\n" ++ err) + Right parsed -> + let rendered = TreePretty.pretty parsed + in case runParserE (sc *> topDeclP <* eof) "" rendered of + Left err -> + assertFailure + ( "Pretty-printed declaration did not parse:\n" + ++ rendered + ++ "\n" + ++ err + ) + Right reparsed -> + assertEqual ("round trip: " ++ rendered) parsed reparsed + +roundTripsStmt :: String -> Assertion +roundTripsStmt src = + case runParserE (sc *> stmtP <* eof) "" src of + Left err -> assertFailure ("Initial parse error:\n" ++ err) + Right parsed -> + let rendered = TreePretty.pretty parsed + in case runParserE (sc *> stmtP <* eof) "" rendered of + Left err -> + assertFailure + ( "Pretty-printed statement did not parse:\n" + ++ rendered + ++ "\n" + ++ err + ) + Right reparsed -> + assertEqual ("round trip: " ++ rendered) parsed reparsed + +roundTripsType :: String -> Assertion +roundTripsType src = + case runParserE (sc *> typeP <* eof) "" src of + Left err -> assertFailure ("Initial parse error:\n" ++ err) + Right parsed -> + let rendered = TreePretty.pretty parsed + in case runParserE (sc *> typeP <* eof) "" rendered of + Left err -> + assertFailure + ( "Pretty-printed type did not parse:\n" + ++ rendered + ++ "\n" + ++ err + ) + Right reparsed -> + assertEqual ("round trip: " ++ rendered) parsed reparsed + expP :: Parser Exp expP = exprP bodyP @@ -40,7 +118,11 @@ parserTests = exprTests, stmtTests, declTests, - keywordPrefixTests + importTests, + pragmaTests, + declarationShellTests, + keywordPrefixTests, + legacySyntaxTests ] word :: Ty @@ -55,17 +137,70 @@ typeTests = "Types" [ testCase "simple named type" $ parsesAs typeP "word" word, - testCase "parameterized type" $ - parsesAs typeP "pair(word, bool)" (TyCon "pair" [word, bool]), - testCase "two-parameter type" $ - parsesAs typeP "map(word, bool)" (TyCon "map" [word, bool]), - testCase "arrow type" $ - parsesAs typeP "word -> bool" (TyCon "->" [word, bool]), - testCase "arrow is right-associative" $ + testCase "generic type" $ + parsesAs typeP "Option" (TyCon "Option" [word]), + testCase "generic type with two arguments" $ + parsesAs typeP "Result" (TyCon "Result" [word, bool]), + testCase "qualified generic type" $ + parsesAs + typeP + "pkg.Result" + (TyCon (QualName "pkg" "Result") [word, TyCon "Error" []]), + testCase "mapping type" $ + parsesAs + typeP + "mapping(address => word)" + (TyCon "mapping" [TyCon "address" [], word]), + testCase "dynamic array type" $ + parsesAs typeP "word[]" (TyCon "array" [word]), + testCase "nested dynamic array type" $ + parsesAs typeP "word[][]" (TyCon "array" [TyCon "array" [word]]), + testCase "fixed array stores size before element type" $ + parsesAs typeP "word[4]" (TyCon "array" [TyCon "4" [], word]), + testCase "fixed array accepts a type-level size" $ + parsesAs typeP "word[N]" (TyCon "array" [TyCon "N" [], word]), + testCase "data location follows the complete array type" $ + parsesAs + typeP + "word[] storage" + (TyCon "storage" [TyCon "array" [word]]), + testCase "function type" $ + parsesAs + typeP + "function(word) internal returns (bool)" + (FunctionTy [word] (Just FunctionTypeInternal) (Just [bool])), + testCase "multi-parameter function type retains external visibility" $ parsesAs typeP - "word -> bool -> word" - (TyCon "->" [word, TyCon "->" [bool, word]]), + "function(word, bool) external returns (word)" + (FunctionTy [word, bool] (Just FunctionTypeExternal) (Just [word])), + testCase "zero-arity function type remains distinct in the source AST" $ + parsesAs + typeP + "function() internal returns (word)" + (FunctionTy [] (Just FunctionTypeInternal) (Just [word])), + testCase "function type preserves an omitted visibility and returns clause" $ + parsesAs + typeP + "function()" + (FunctionTy [] Nothing Nothing), + testCase "function type preserves multiple return items" $ + parsesAs + typeP + "function() external returns (word, bool)" + (FunctionTy [] (Just FunctionTypeExternal) (Just [word, bool])), + testCase "function type accepts an array suffix" $ + parsesAs + typeP + "function(word) internal returns (bool)[]" + (TyCon "array" [FunctionTy [word] (Just FunctionTypeInternal) (Just [bool])]), + testCase "function type syntax survives source pretty-printing" $ + mapM_ + roundTripsType + [ "function() internal returns (word)", + "function(word, bool) external returns (word, bool)", + "function()" + ], testCase "unit type" $ parsesAs typeP "()" (TyCon "()" []), testCase "parenthesized single type" $ @@ -74,17 +209,13 @@ typeTests = parsesAs typeP "(word, bool)" (pairTy word bool), testCase "triple type in parens" $ parsesAs typeP "(word, bool, word)" (pairTy word (pairTy bool word)), - testCase "proxy type" $ - parsesAs typeP "@word" (TyCon "Proxy" [word]), testCase "qualified name in type" $ parsesAs typeP "Foo.Bar" (TyCon (QualName "Foo" "Bar") []), - testCase "arrow type in parens disambiguates" $ - parsesAs typeP "((word -> bool) -> word)" (TyCon "->" [TyCon "->" [word, bool], word]), -- Failure cases - testCase "bare arrow fails" $ - parseFails typeP "->", testCase "unclosed paren fails" $ - parseFails typeP "(word" + parseFails typeP "(word", + testCase "unclosed generic argument list fails" $ + parseFails typeP "Option" (InCls "Functor" (TyCon "t" []) [word]), testCase "predicate with two params" $ - parsesAs predP "t:Bifunctor(word,bool)" (InCls "Bifunctor" (TyCon "t" []) [word, bool]), + parsesAs predP "t:Bifunctor" (InCls "Bifunctor" (TyCon "t" []) [word, bool]), testCase "compound main type" $ parsesAs predP "(word,bool):Pair" (InCls "Pair" (pairTy word bool) []) ] @@ -139,6 +270,9 @@ lit = Lit . IntLit var :: String -> Exp var n = ExpVar Nothing (Name n) +unitExp :: Exp +unitExp = ExpName Nothing "()" [] + exprTests :: TestTree exprTests = testGroup @@ -167,12 +301,29 @@ exprTests = parsesAs expP "6 / 2" (ExpDivide (lit 6) (lit 2)), testCase "modulo" $ parsesAs expP "5 % 3" (ExpModulo (lit 5) (lit 3)), + testCase "exponentiation is right-associative" $ + parsesAs + expP + "2 ** 3 ** 4" + (ExpPower (lit 2) (ExpPower (lit 3) (lit 4))), testCase "mul binds tighter than add" $ parsesAs expP "1 + 2 * 3" (ExpPlus (lit 1) (ExpTimes (lit 2) (lit 3))), testCase "add then mul" $ parsesAs expP "1 * 2 + 3" (ExpPlus (ExpTimes (lit 1) (lit 2)) (lit 3)), testCase "subtraction is left-associative" $ parsesAs expP "3 - 2 - 1" (ExpMinus (ExpMinus (lit 3) (lit 2)) (lit 1)), + testCase "addition binds tighter than left shift" $ + parsesAs + expP + "x + y << n" + (ExpShiftL (ExpPlus (var "x") (var "y")) (var "n")), + testCase "addition on the right binds tighter than left shift" $ + parsesAs + expP + "x << n + 1" + (ExpShiftL (var "x") (ExpPlus (var "n") (lit 1))), + testCase "right shift" $ + parsesAs expP "x >> n" (ExpShiftR (var "x") (var "n")), testCase "less-than" $ parsesAs expP "x < y" (ExpLT (var "x") (var "y")), testCase "greater-than" $ @@ -205,10 +356,35 @@ exprTests = (ExpLAnd (ExpLT (var "a") (var "b")) (ExpGT (var "c") (var "d"))), testCase "ternary operator" $ parsesAs expP "x ? 1 : 2" (ExpCond (var "x") (lit 1) (lit 2)), - testCase "if-then-else expression" $ - parsesAs expP "if x then 1 else 2" (ExpCond (var "x") (lit 1) (lit 2)), - testCase "type annotation" $ - parsesAs expP "x : word" (TyExp (var "x") word), + testCase "explicit conversion" $ + parsesAs expP "x as word" (TyExp (var "x") word), + testCase "conversion accepts a qualified generic target" $ + parsesAs + expP + "x as pkg.Result" + (TyExp (var "x") (TyCon (QualName "pkg" "Result") [word, bool])), + testCase "conversion is left-associative" $ + parsesAs + expP + "x as word as bool" + (TyExp (TyExp (var "x") word) bool), + testCase "conversion binds tighter than addition" $ + parsesAs + expP + "x as word + y" + (ExpPlus (TyExp (var "x") word) (var "y")), + testCase "parentheses allow converting a complete addition" $ + parsesAs + expP + "(x + y) as word" + (TyExp (ExpPlus (var "x") (var "y")) word), + testCase "conversion is accepted in both ternary branches" $ + parsesAs + expP + "condition ? x as word : y as bool" + (ExpCond (var "condition") (TyExp (var "x") word) (TyExp (var "y") bool)), + testCase "function-style syntax remains an ordinary call" $ + parsesAs expP "word(x)" (ExpName Nothing "word" [var "x"]), testCase "field access" $ parsesAs expP "x.foo" (ExpVar (Just (var "x")) "foo"), testCase "method call" $ @@ -233,8 +409,6 @@ exprTests = expP "(a, b, c)" (ExpName Nothing "pair" [var "a", ExpName Nothing "pair" [var "b", var "c"]]), - testCase "proxy expression" $ - parsesAs expP "@word" (ExpAt word), testCase "dot name without args" $ parsesAs expP ".None" (ExpDotName "None" []), testCase "dot name with args" $ @@ -242,12 +416,12 @@ exprTests = testCase "lambda no params" $ parsesAs expP - "lam() -> word { return 0; }" + "lam() returns (word) { return 0; }" (Lam [] [Return (lit 0)] (Just word)), testCase "lambda with typed param" $ parsesAs expP - "lam(x:word) -> word { return x; }" + "lam(x:word) returns (word) { return x; }" (Lam [Typed False "x" word] [Return (var "x")] (Just word)), testCase "lambda without return type" $ parsesAs @@ -256,8 +430,8 @@ exprTests = (Lam [Typed False "x" word] [Return (var "x")] Nothing) ] --- | Identifiers that start with a keyword (e.g. `datavalue`, which begins with --- `data`) must not be mistaken for the keyword. The lexer's `keyword` parser is +-- | Identifiers that start with a keyword (e.g. `enumValue`, which begins with +-- `enum`) must not be mistaken for the keyword. The lexer's `keyword` parser is -- atomic, so a keyword tried as an alternative backtracks instead of consuming -- the prefix. keywordPrefixTests :: TestTree @@ -265,14 +439,14 @@ keywordPrefixTests = testGroup "Keyword prefixes" [ testCase "statement-initial assignment to keyword-prefixed name" $ - parsesAs stmtP "datavalue = 2;" (Assign (var "datavalue") (lit 2)), + parsesAs stmtP "enumValue = 2;" (Assign (var "enumValue") (lit 2)), testCase "statement-initial expression with keyword-prefixed name" $ - parsesAs stmtP "datavalue;" (StmtExp (var "datavalue")), + parsesAs stmtP "returnsValue;" (StmtExp (var "returnsValue")), testCase "contract field with keyword-prefixed name" $ parsesAs topDeclP - "contract C { datavalue : word; }" - (TContr (Contract "C" [] [CFieldDecl (Field "datavalue" word Nothing)])) + "contract C { traitValue : word; }" + (TContr (Contract "C" [] [CFieldDecl (Field "traitValue" word Nothing)])) ] stmtTests :: TestTree @@ -287,10 +461,66 @@ stmtTests = parsesAs stmtP "let x = 42;" (Let False "x" Nothing (Just (lit 42))), testCase "let with type and init" $ parsesAs stmtP "let x : word = 42;" (Let False "x" (Just word) (Just (lit 42))), + testCase "comptime let binding" $ + parsesAs stmtP "let comptime x : word = 42;" (Let True "x" (Just word) (Just (lit 42))), + testCase "typed tuple destructuring let" $ + parsesAs + stmtP + "let (amount, ok): (word, bool) = readResult();" + ( LetPattern + False + (Pat "pair" [Pat "amount" [], Pat "ok" []]) + (Just (TyCon "pair" [word, bool])) + (ExpName Nothing "readResult" []) + ), + testCase "untyped nested tuple destructuring let" $ + parsesAs + stmtP + "let (left, (middle, right)) = readNested();" + ( LetPattern + False + (Pat "pair" [Pat "left" [], Pat "pair" [Pat "middle" [], Pat "right" []]]) + Nothing + (ExpName Nothing "readNested" []) + ), + testCase "tuple destructuring pretty-prints as new syntax" $ + roundTripsStmt "let (amount, (ok, fallbackValue)): (word, (bool, word)) = readResult();", + testCase "tuple destructuring requires an initializer" $ + parseFails stmtP "let (left, right);", + testCase "tuple destructuring rejects a singleton pattern" $ + parseFails stmtP "let (only) = readResult();", + testCase "tuple destructuring rejects refutable constructor leaves" $ + parseFails stmtP "let (Some(value), rest) = readResult();", + testCase "tuple destructuring rejects duplicate binders recursively" $ + parseFails stmtP "let (x, (y, x)) = readResult();", + testCase "tuple destructuring allows repeated wildcards" $ + parsesAs + stmtP + "let (_, (_, x)) = readResult();" + ( LetPattern + False + (Pat "pair" [PWildcard, Pat "pair" [PWildcard, Pat "x" []]]) + Nothing + (ExpName Nothing "readResult" []) + ), + testCase "comptime tuple destructuring keeps its binding modifier" $ + parsesAs + stmtP + "let comptime (left, right) = readResult();" + ( LetPattern + True + (Pat "pair" [Pat "left" [], Pat "right" []]) + Nothing + (ExpName Nothing "readResult" []) + ), + testCase "comptime tuple destructuring pretty-prints as new syntax" $ + roundTripsStmt "let comptime (left, right): (word, word) = readResult();", testCase "return literal" $ parsesAs stmtP "return 0;" (Return (lit 0)), testCase "return expression" $ parsesAs stmtP "return x + 1;" (Return (ExpPlus (var "x") (lit 1))), + testCase "bare return produces the unit expression" $ + parsesAs stmtP "return;" (Return unitExp), testCase "assignment" $ parsesAs stmtP "x = 1;" (Assign (var "x") (lit 1)), testCase "plus-assign" $ @@ -302,8 +532,8 @@ stmtTests = stmtP "this.x = 1;" (Assign (ExpVar (Just (var "this")) "x") (lit 1)), - testCase "call as statement no semicolon" $ - parsesAs stmtP "f()" (StmtExp (ExpName Nothing "f" [])), + testCase "call as statement requires semicolon" $ + parseFails stmtP "f()", testCase "call as statement with semicolon" $ parsesAs stmtP "f();" (StmtExp (ExpName Nothing "f" [])), testCase "if without else" $ @@ -360,31 +590,62 @@ stmtTests = EmptyStmt [] ), + testCase "while loop remains distinct in the source AST" $ + parsesAs + stmtP + "while (condition) { continue; }" + (While (var "condition") [Continue]), + testCase "while loop survives source pretty-printing" $ + roundTripsStmt "while (condition) { continue; }", + testCase "unchecked block remains distinct in the source AST" $ + parsesAs + stmtP + "unchecked { let x = 1; }" + (Unchecked [Let False "x" Nothing (Just (lit 1))]), + testCase "unchecked block survives source pretty-printing" $ + roundTripsStmt "unchecked { let x = 1; }", + testCase "bare revert lowers to the revert operation" $ + parsesAs + stmtP + "revert;" + (StmtExp (ExpName Nothing "revert" [])), testCase "match one equation" $ parsesAs stmtP - "match x { | 0 => return 1; }" + "match (x) { case 0 { return 1; } }" (Match [var "x"] [([PLit (IntLit 0)], [Return (lit 1)])]), - testCase "match wildcard" $ + testCase "match default arm" $ parsesAs stmtP - "match x { | _ => return 0; }" + "match (x) { default { return 0; } }" (Match [var "x"] [([PWildcard], [Return (lit 0)])]), testCase "match constructor pattern" $ parsesAs stmtP - "match x { | Some(v) => return v; }" - (Match [var "x"] [([Pat "Some" [Pat "v" []]], [Return (var "v")])]), + "match (x) { case Option.Some(v) { return v; } }" + (Match [var "x"] [([Pat (QualName "Option" "Some") [Pat "v" []]], [Return (var "v")])]), testCase "match multiple equations" $ parsesAs stmtP - "match x { | 0 => return 0; | _ => return 1; }" + "match (x) { case 0 { return 0; } default { return 1; } }" ( Match [var "x"] [ ([PLit (IntLit 0)], [Return (lit 0)]), ([PWildcard], [Return (lit 1)]) ] ), + testCase "match multiple values" $ + parsesAs + stmtP + "match (x, y) { case (Some(a), Some(b)) { return a + b; } default { return 0; } }" + ( Match + [var "x", var "y"] + [ ( [Pat "Some" [Pat "a" []], Pat "Some" [Pat "b" []]], + [Return (ExpPlus (var "a") (var "b"))] + ), + ([PWildcard, PWildcard], [Return (lit 0)]) + ] + ), testCase "let without semicolon fails" $ parseFails stmtP "let x" ] @@ -396,7 +657,7 @@ declTests = [ testCase "nullary function" $ parsesAs topDeclP - "function answer() -> word { return 42; }" + "function answer() returns (word) { return 42; }" ( TFunDef ( FunDef False @@ -407,7 +668,7 @@ declTests = testCase "unary function" $ parsesAs topDeclP - "function id(x:word) -> word { return x; }" + "function id(x:word) returns (word) { return x; }" ( TFunDef ( FunDef False @@ -415,21 +676,53 @@ declTests = [Return (var "x")] ) ), - testCase "implicit return (single expr body)" $ + testCase "named return lowers to its declared type" $ parsesAs topDeclP - "function answer() -> word { 42 }" + "function namedResult() returns (result: word) { return 1; }" ( TFunDef ( FunDef False - (Signature [] [] "answer" [] False (Just word) False) - [Return (lit 42)] + ( SignatureWithSyntax + [] + [] + "namedResult" + [] + (Just [ReturnItem False (Just "result") word]) + [] + ) + [Return (lit 1)] + ) + ), + testCase "comptime parameter and return are recorded in the signature" $ + parsesAs + topDeclP + "function staged(comptime x:word) returns (comptime word) { return x; }" + ( TFunDef + ( FunDef + False + (Signature [] [] "staged" [Typed True "x" word] True (Just word) False) + [Return (var "x")] ) ), + testCase "multiple return types fold into the tuple AST" $ + parsesAs + topDeclP + "function pairValue() returns (word, bool) { return (1, true); }" + ( TFunDef + ( FunDef + False + (Signature [] [] "pairValue" [] False (Just (pairTy word bool)) False) + [Return (ExpName Nothing "pair" [lit 1, var "true"])] + ) + ), + testCase "named return items survive source pretty-printing" $ + roundTripsTopDecl + "function namedPair() returns (left: word, comptime right: bool) { return (1, true); }", testCase "polymorphic function" $ parsesAs topDeclP - "forall a. function id(x:a) -> a { return x; }" + "function id(x:a) returns (a) { return x; }" ( TFunDef ( FunDef False @@ -448,7 +741,7 @@ declTests = testCase "constrained function" $ parsesAs topDeclP - "forall a. a:Eq => function eqSelf(x:a) -> bool { return x == x; }" + "function eqSelf(x:a) returns (bool) where a:Eq { return x == x; }" ( TFunDef ( FunDef False @@ -464,20 +757,36 @@ declTests = [Return (ExpEE (var "x") (var "x"))] ) ), - testCase "empty data type" $ + testCase "legacy declaration word is reusable as an identifier" $ parsesAs topDeclP - "data Void;" + "function data() returns (()) { return; }" + ( TFunDef + ( FunDef + False + (Signature [] [] "data" [] False (Just (TyCon "()" [])) False) + [Return unitExp] + ) + ), + testCase "empty enum" $ + parsesAs + topDeclP + "enum Void { }" (TDataDef (DataTy "Void" [] [])), - testCase "data type with nullary constructors" $ + testCase "enum with nullary constructors" $ parsesAs topDeclP - "data Bool = True | False;" + "enum Bool { True, False }" (TDataDef (DataTy "Bool" [] [Constr "True" [], Constr "False" []])), - testCase "data type with parameterized constructor" $ + testCase "data location word remains available as a value constructor name" $ + parsesAs + topDeclP + "enum Location { storage }" + (TDataDef (DataTy "Location" [] [Constr "storage" []])), + testCase "generic enum with payload constructor" $ parsesAs topDeclP - "data Option(a) = Some(a) | None;" + "enum Option { Some(a), None }" ( TDataDef ( DataTy "Option" @@ -485,15 +794,15 @@ declTests = [Constr "Some" [TyCon "a" []], Constr "None" []] ) ), - testCase "type synonym no params" $ + testCase "user-defined value type" $ parsesAs topDeclP - "type Word = word;" + "type Word is word;" (TSym (TySym "Word" [] word)), - testCase "type synonym with params" $ + testCase "generic user-defined value type" $ parsesAs topDeclP - "type Pair(a, b) = (a, b);" + "type Pair is (a, b);" ( TSym ( TySym "Pair" @@ -501,10 +810,10 @@ declTests = (pairTy (TyCon "a" []) (TyCon "b" [])) ) ), - testCase "class with one method" $ + testCase "trait with one method" $ parsesAs topDeclP - "forall a. class a:Eq { function eq(x:a, y:a) -> bool; }" + "trait Eq { function eq(x:a, y:a) returns (bool); }" ( TClassDef ( Class [TyCon "a" []] @@ -523,10 +832,10 @@ declTests = ] ) ), - testCase "class with context" $ + testCase "trait with where clause" $ parsesAs topDeclP - "forall a. a:Eq => class a:Ord { function cmp(x:a, y:a) -> word; }" + "trait Ord where a:Eq { function cmp(x:a, y:a) returns (word); }" ( TClassDef ( Class [TyCon "a" []] @@ -545,10 +854,10 @@ declTests = ] ) ), - testCase "instance with one method" $ + testCase "impl with one method" $ parsesAs topDeclP - "instance word:Eq { function eq(x:word, y:word) -> bool { return x == y; } }" + "impl Eq { function eq(x:word, y:word) returns (bool) { return x == y; } }" ( TInstDef ( Instance False @@ -564,10 +873,29 @@ declTests = ] ) ), - testCase "polymorphic instance" $ + testCase "default impl records its default status" $ parsesAs topDeclP - "forall a. a:Eq => instance pair(a,a):Eq { function eq(x:pair(a,a), y:pair(a,a)) -> bool { return 0; } }" + "default impl Eq { function eq(x:word, y:word) returns (bool) { return x == y; } }" + ( TInstDef + ( Instance + True + [] + [] + "Eq" + [] + word + [ FunDef + False + (Signature [] [] "eq" [Typed False "x" word, Typed False "y" word] False (Just bool) False) + [Return (ExpEE (var "x") (var "y"))] + ] + ) + ), + testCase "generic impl with where clause" $ + parsesAs + topDeclP + "impl Eq> where a:Eq { function eq(x:pair, y:pair) returns (bool) { return 0; } }" ( TInstDef ( Instance False @@ -611,7 +939,7 @@ declTests = testCase "contract with function" $ parsesAs topDeclP - "contract C { function get() -> word { return x; } }" + "contract C { function get() returns (word) { return x; } }" ( TContr ( Contract "C" @@ -628,7 +956,7 @@ declTests = testCase "contract with public function" $ parsesAs topDeclP - "contract C { public function get() -> word { return x; } }" + "contract C { function get() public returns (word) { return x; } }" ( TContr ( Contract "C" @@ -636,17 +964,464 @@ declTests = [ CFunDecl ( FunDef True - (Signature [] [] "get" [] False (Just word) False) + ( SignatureWithSyntax + [] + [] + "get" + [] + (Just [ReturnItem False Nothing word]) + [VisibilityModifier VisibilityPublic] + ) [Return (var "x")] ) ] ) ), - -- `public` is only meaningful inside a contract; reject it elsewhere. - testCase "top-level public function fails" $ - parseFails topDeclP "public function get() -> word { return 0; }", + testCase "contract with public payable function" $ + parsesAs + topDeclP + "contract C { function pay() public payable returns (word) { return 0; } }" + ( TContr + ( Contract + "C" + [] + [ CFunDecl + ( FunDef + True + ( SignatureWithSyntax + [] + [] + "pay" + [] + (Just [ReturnItem False Nothing word]) + [ VisibilityModifier VisibilityPublic, + MutabilityModifier MutabilityPayable + ] + ) + [Return (lit 0)] + ) + ] + ) + ), + testCase "contract functions accept pure, view, private, internal, and external modifiers" $ + parsesAs + topDeclP + ( "contract C {" + ++ " function pureFn() pure { return; }" + ++ " function viewFn() view { return; }" + ++ " function privateFn() private { return; }" + ++ " function internalFn() internal { return; }" + ++ " function externalFn() external { return; }" + ++ " }" + ) + ( TContr + ( Contract + "C" + [] + [ CFunDecl + ( FunDef + False + (SignatureWithSyntax [] [] "pureFn" [] Nothing [MutabilityModifier MutabilityPure]) + [Return unitExp] + ), + CFunDecl + ( FunDef + False + (SignatureWithSyntax [] [] "viewFn" [] Nothing [MutabilityModifier MutabilityView]) + [Return unitExp] + ), + CFunDecl + ( FunDef + False + (SignatureWithSyntax [] [] "privateFn" [] Nothing [VisibilityModifier VisibilityPrivate]) + [Return unitExp] + ), + CFunDecl + ( FunDef + False + (SignatureWithSyntax [] [] "internalFn" [] Nothing [VisibilityModifier VisibilityInternal]) + [Return unitExp] + ), + CFunDecl + ( FunDef + True + (SignatureWithSyntax [] [] "externalFn" [] Nothing [VisibilityModifier VisibilityExternal]) + [Return unitExp] + ) + ] + ) + ), + testCase "contract constructor" $ + parsesAs + topDeclP + "contract C { constructor(x:word) { return; } }" + ( TContr + ( Contract + "C" + [] + [CConstrDecl (Constructor [Typed False "x" word] [Return unitExp] False)] + ) + ), + testCase "payable modifier follows constructor parameters" $ + parsesAs + topDeclP + "contract C { constructor(x:word) payable { return; } }" + ( TContr + ( Contract + "C" + [] + [CConstrDecl (Constructor [Typed False "x" word] [Return unitExp] True)] + ) + ), + testCase "external payable fallback" $ + parsesAs + topDeclP + "contract C { fallback() external payable { return; } }" + ( TContr + ( Contract + "C" + [] + [ CFunDecl + ( FunDef + False + ( SignatureWithSyntax + [] + [] + "fallback" + [] + Nothing + [ VisibilityModifier VisibilityExternal, + MutabilityModifier MutabilityPayable + ] + ) + [Return unitExp] + ) + ] + ) + ), + testCase "fallback without external visibility fails" $ + parseFails + topDeclP + "contract C { fallback() { return; } }", + testCase "multiple visibility modifiers fail" $ + parseFails + topDeclP + "contract C { function f() public external { return; } }", + testCase "multiple mutability modifiers fail" $ + parseFails + topDeclP + "contract C { function f() pure view { return; } }", + -- Contract visibility modifiers are not meaningful on impl methods. testCase "public instance method fails" $ parseFails topDeclP - "instance word:Eq { public function eq(x:word, y:word) -> bool { return x == y; } }" + "impl Eq { function eq(x:word, y:word) public returns (bool) { return x == y; } }" + ] + +importTests :: TestTree +importTests = + testGroup + "Imports" + [ testCase "dotted module import" $ + parsesAs + importP + "import std.dispatch;" + (ImportModule (RelativePath (QualName "std" "dispatch"))), + testCase "namespace alias import" $ + parsesAs + importP + "import * as dispatch from std.dispatch;" + (ImportAlias (RelativePath (QualName "std" "dispatch")) "dispatch"), + testCase "selective import" $ + parsesAs + importP + "import {address, uint256 as U256} from std;" + ( ImportOnly + (RelativePath "std") + (SelectItems [SelectItem "address", SelectItemAs "uint256" "U256"] []) + ), + testCase "selective import from external module" $ + parsesAs + importP + "import {foo, bar as baz} from @ext.foo.bar;" + ( ImportOnly + (ExternalPath "ext" (QualName "foo" "bar")) + (SelectItems [SelectItem "foo", SelectItemAs "bar" "baz"] []) + ) + ] + +pragmaTests :: TestTree +pragmaTests = + testGroup + "Pragmas" + [ testCase "Solidity compatibility pragma retains its value" $ + parsesAs + topDeclP + "pragma solidity ^0.8.23;" + (TPragmaDecl (Pragma (SolidityPragma "^0.8.23") Enabled)), + testCase "ABI coder pragma retains its value" $ + parsesAs + topDeclP + "pragma abicoder v2;" + (TPragmaDecl (Pragma (AbiCoderPragma "v2") Enabled)), + testCase "disable coverage condition" $ + parsesAs + topDeclP + "pragma solcore noCoverageCondition;" + (TPragmaDecl (Pragma NoCoverageCondition DisableAll)), + testCase "disable Patterson condition" $ + parsesAs + topDeclP + "pragma solcore noPattersonCondition;" + (TPragmaDecl (Pragma NoPattersonCondition DisableAll)), + testCase "disable bound-variable condition" $ + parsesAs + topDeclP + "pragma solcore noBoundVariableCondition;" + (TPragmaDecl (Pragma NoBoundVariableCondition DisableAll)), + testCase "disable generic instance generation for a type" $ + parsesAs + topDeclP + "pragma solcore noGenericInstanceFor MyType;" + (TPragmaDecl (Pragma NoGenericInstanceFor (DisableFor ("MyType" :| [])))) + ] + +legacySyntaxTests :: TestTree +legacySyntaxTests = + testGroup + "Legacy syntax is rejected" + [ testCase "parenthesized generic type arguments" $ + parseFails typeP "pair(word, bool)", + testCase "arrow function type" $ + parseFails typeP "word -> bool", + testCase "at-sign proxy type" $ + parseFails typeP "@word", + testCase "at-sign proxy expression" $ + parseFails expP "@word", + testCase "expression colon annotation" $ + parseFails expP "x : word", + testCase "arrow function return" $ + parseFails topDeclP "function answer() -> word { return 42; }", + testCase "forall generic prefix" $ + parseFails topDeclP "forall a. function id(x:a) returns (a) { return x; }", + testCase "data declaration" $ + parseFails topDeclP "data Bool = True | False;", + testCase "class declaration" $ + parseFails topDeclP "forall a. class a:Eq { function eq(x:a, y:a) -> bool; }", + testCase "instance declaration" $ + parseFails topDeclP "instance word:Eq { function eq(x:word, y:word) -> bool { return x == y; } }", + testCase "leading public modifier" $ + parseFails + topDeclP + "contract C { public function get() returns (word) { return x; } }", + testCase "leading payable constructor modifier" $ + parseFails + topDeclP + "contract C { payable constructor(x:word) { return; } }", + testCase "pipe match equations" $ + parseFails stmtP "match x { | 0 => return 1; }", + testCase "old selective import ordering" $ + parseFails importP "import std.{address, uint256 as U256};", + testCase "old namespace alias ordering" $ + parseFails importP "import std.dispatch as dispatch;", + testCase "string-path import" $ + parseFails importP "import \"M/N.sol\";", + testCase "hyphenated solcore pragma" $ + parseFails topDeclP "pragma no-coverage-condition;", + testCase "equals type declaration" $ + parseFails topDeclP "type Word = word;", + testCase "lambda arrow return" $ + parseFails expP "lam() -> word { return 0; }" + ] + +declarationShellTests :: TestTree +declarationShellTests = + testGroup + "Struct, interface, and library declarations" + [ testCase "top-level struct retains field names and types" $ + parsesAs + topDeclP + "struct Pair { left: a; right: word; }" + (TDataDef (StructTy "Pair" [TyCon "a" []] ["left", "right"] [TyCon "a" [], word])), + testCase "contract-local struct is a data declaration" $ + parsesAs + topDeclP + "contract C { struct Entry { key: word; value: bool; } }" + ( TContr + ( ContractShell + ContractKind + "C" + [] + [CDataDecl (StructTy "Entry" [] ["key", "value"] [word, bool])] + ) + ), + testCase "interface contains body-less function signatures" $ + parsesAs + topDeclP + "interface Oracle { function read(key: word) external view returns (word); }" + ( TContr + ( ContractShell + InterfaceKind + "Oracle" + [] + [ CSignatureDecl + True + ( SignatureWithSyntax + [] + [] + "read" + [Typed False "key" word] + (Just [ReturnItem False Nothing word]) + [ VisibilityModifier VisibilityExternal, + MutabilityModifier MutabilityView + ] + ) + ] + ) + ), + testCase "interface rejects a function body" $ + parseFails + topDeclP + "interface Oracle { function read() external returns (word) { return 0; } }", + testCase "interface rejects state fields" $ + parseFails topDeclP "interface Oracle { value: word; }", + testCase "library accepts contract-like fields, structs, and functions" $ + parsesAs + topDeclP + ( "library Math {" + ++ " factor: word;" + ++ " struct Result { value: word; }" + ++ " function twice(x: word) internal pure returns (word) { return x + x; }" + ++ " }" + ) + ( TContr + ( ContractShell + LibraryKind + "Math" + [] + [ CFieldDecl (Field "factor" word Nothing), + CDataDecl (StructTy "Result" [] ["value"] [word]), + CFunDecl + ( FunDef + False + ( SignatureWithSyntax + [] + [] + "twice" + [Typed False "x" word] + (Just [ReturnItem False Nothing word]) + [ VisibilityModifier VisibilityInternal, + MutabilityModifier MutabilityPure + ] + ) + [Return (ExpPlus (var "x") (var "x"))] + ) + ] + ) + ), + testCase "library rejects constructors" $ + parseFails + topDeclP + "library Math { constructor() { return; } }", + testCase "name resolution rejects duplicate contract fields" $ + nameResolutionFails + "contract C { value: word; value: bool; }", + testCase "contract fields and functions retain separate namespaces" $ + nameResolutionSucceeds + "contract C { value: word; function value() returns (word) { return 0; } }", + testCase "contract fields and functions with distinct names do not collide" $ + nameResolutionSucceeds + "contract C { value: word; function read() returns (word) { return value; } }", + testCase "name resolution lowers a struct to a one-constructor data type" $ + case runParserE (sc *> topDeclP <* eof) "" "struct Box { value: word; }" of + Left err -> assertFailure ("Parse error:\n" ++ err) + Right parsed -> do + resolved <- nameResolution (CompUnit [] [parsed]) + case resolved of + Right + ( Resolved.CompUnit + _ + [ Resolved.TDataDef + (Resolved.DataTy "Box" [] [Resolved.Constr (QualName "Box" "Box") [_]]) + ] + ) -> + pure () + Right got -> assertFailure ("Unexpected lowering result: " ++ show got) + Left err -> assertFailure ("Name resolution failed: " ++ show err), + testCase "name resolution preserves an interface signature without a body" $ + case runParserE (sc *> topDeclP <* eof) "" "interface I { function f() external; }" of + Left err -> assertFailure ("Parse error:\n" ++ err) + Right parsed -> do + resolved <- nameResolution (CompUnit [] [parsed]) + case resolved of + Right + ( Resolved.CompUnit + _ + [ Resolved.TContr + (Resolved.Contract "I" [] [Resolved.CSignatureDecl True _]) + ] + ) -> + pure () + Right got -> assertFailure ("Unexpected lowering result: " ++ show got) + Left err -> assertFailure ("Name resolution failed: " ++ show err), + testCase "name resolution lowers source modifiers and named returns" $ + case + runParserE + (sc *> topDeclP <* eof) + "" + "contract C { function pair() external payable returns (left: word, right: bool) { return (1, 0); } }" + of + Left err -> assertFailure ("Parse error:\n" ++ err) + Right parsed -> do + resolved <- nameResolution (CompUnit [] [parsed]) + case resolved of + Right + ( Resolved.CompUnit + _ + [ Resolved.TContr + (Resolved.Contract "C" [] [Resolved.CFunDecl (Resolved.FunDef isPublic sig _)]) + ] + ) -> do + assertBool "external lowers to the semantic public bit" isPublic + assertBool "payable lowers to the semantic payable bit" (Resolved.sigPayable sig) + assertEqual + "return names are discarded only at semantic lowering" + ( Just + ( ResolvedTy.TyCon + "pair" + [ResolvedTy.TyCon "word" [], ResolvedTy.TyCon "bool" []] + ) + ) + (Resolved.sigReturn sig) + Right got -> assertFailure ("Unexpected lowering result: " ++ show got) + Left err -> assertFailure ("Name resolution failed: " ++ show err), + testCase "name resolution lowers a zero-arity function type explicitly" $ + case + runParserE + (sc *> topDeclP <* eof) + "" + "type Callback is function() external returns (word);" + of + Left err -> assertFailure ("Parse error:\n" ++ err) + Right parsed -> do + resolved <- nameResolution (CompUnit [] [parsed]) + case resolved of + Right + ( Resolved.CompUnit + _ + [Resolved.TSym (Resolved.TySym "Callback" [] callbackTy)] + ) -> + assertEqual + "the existing semantic AST represents a nullary function by its result" + (ResolvedTy.TyCon "word" []) + callbackTy + Right got -> assertFailure ("Unexpected lowering result: " ++ show got) + Left err -> assertFailure ("Name resolution failed: " ++ show err), + testCase "new declaration shells survive source pretty-printing" $ + mapM_ + roundTripsTopDecl + [ "struct Pair { x: word; y: bool; }", + "interface Oracle { function read(key: word) external view returns (word); }", + "library Math { function twice(x: word) internal pure returns (word) { return x + x; } }" + ] ] diff --git a/test/imports/struct_metadata_lib.solc b/test/imports/struct_metadata_lib.solc new file mode 100644 index 000000000..4d681d6a6 --- /dev/null +++ b/test/imports/struct_metadata_lib.solc @@ -0,0 +1,6 @@ +export { Pair(*) }; + +struct Pair { + left: word; + right: bool; +} diff --git a/test/imports/struct_metadata_main.solc b/test/imports/struct_metadata_main.solc new file mode 100644 index 000000000..eae084533 --- /dev/null +++ b/test/imports/struct_metadata_main.solc @@ -0,0 +1 @@ +import {Pair as RenamedPair} from struct_metadata_lib; From 2720c9d078d597cd0637b2043f78a02e1a836764 Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Thu, 23 Jul 2026 18:32:15 +0900 Subject: [PATCH 02/33] Migrate source corpus to new syntax --- blog-post/erc20.sol | 20 +- blog-post/payment.sol | 19 +- blog-post/sum.sol | 32 +- concept-art/has-field.sol | 40 +- scripts/gen-std-opcodes.py | 2 +- scripts/migrate_new_syntax.py | 2253 +++++++++++++++++ scripts/test_migrate_new_syntax.py | 104 + src/Solcore/Primitives/Primitives.solc | 16 +- std/ABIGeneric.solc | 112 +- std/Generic.solc | 13 +- std/StorageGeneric.solc | 173 +- std/dispatch.solc | 221 +- std/opcodes.solc | 162 +- std/std.solc | 1500 ++++++----- test/DiagnosticCliTests.hs | 108 +- test/DiagnosticTests.hs | 16 +- test/diagnostics/duplicate-definition.solc | 6 +- test/diagnostics/not-polymorphic-enough.solc | 2 +- test/diagnostics/parse-error.solc | 2 +- test/diagnostics/type-mismatch.solc | 2 +- test/diagnostics/undefined-name.solc | 2 +- test/examples/Convertible.solc | 97 +- test/examples/cases/Ackermann.solc | 14 +- test/examples/cases/Add1.solc | 2 +- test/examples/cases/BadInstance.solc | 20 +- test/examples/cases/BoolNot.solc | 12 +- test/examples/cases/Compose.solc | 4 +- test/examples/cases/Compose3.solc | 8 +- test/examples/cases/CondExp.solc | 8 +- test/examples/cases/DupFun.solc | 6 +- test/examples/cases/DuplicateFun.solc | 16 +- test/examples/cases/EitherModule.solc | 24 +- test/examples/cases/Enum.solc | 22 +- test/examples/cases/Eq.solc | 20 +- test/examples/cases/EqQual.solc | 24 +- test/examples/cases/EvenOdd.solc | 26 +- test/examples/cases/Filter.solc | 56 +- test/examples/cases/Foo.solc | 4 +- test/examples/cases/GetSet.solc | 4 +- test/examples/cases/GoodInstance.solc | 36 +- test/examples/cases/Id.solc | 4 +- test/examples/cases/IncompleteInstDef.solc | 14 +- test/examples/cases/Invokable.solc | 12 +- test/examples/cases/KindTest.solc | 6 +- test/examples/cases/ListModule.solc | 28 +- test/examples/cases/Logic.solc | 48 +- test/examples/cases/MatchCall.solc | 14 +- test/examples/cases/Memory1.solc | 8 +- test/examples/cases/Memory2.solc | 6 +- test/examples/cases/Mutuals.solc | 4 +- test/examples/cases/NegPair.solc | 60 +- test/examples/cases/Option.solc | 16 +- test/examples/cases/Pair.solc | 34 +- test/examples/cases/PairMatch1.solc | 4 +- test/examples/cases/PairMatch2.solc | 8 +- test/examples/cases/Peano.solc | 14 +- test/examples/cases/PeanoMatch.solc | 14 +- test/examples/cases/Ref.solc | 16 +- test/examples/cases/RefDeref.solc | 12 +- test/examples/cases/SillyReturn.solc | 14 +- test/examples/cases/SimpleInvoke.solc | 12 +- test/examples/cases/SimpleLambda.solc | 6 +- test/examples/cases/SingleFun.solc | 2 +- test/examples/cases/StructMembers.solc | 107 +- test/examples/cases/Uncurry.solc | 8 +- test/examples/cases/abigeneric.solc | 110 +- test/examples/cases/add-moritz.solc | 78 +- test/examples/cases/another-subst.solc | 12 +- test/examples/cases/app.solc | 12 +- .../cases/array-elem-no-storagecopy.solc | 14 +- .../cases/array-push-no-canstore.solc | 14 +- test/examples/cases/array.solc | 80 +- test/examples/cases/asm-assign-no-return.solc | 2 +- test/examples/cases/asm-assign-non-word.solc | 4 +- test/examples/cases/asm-let-bool-lit.solc | 2 +- test/examples/cases/asm-let-no-return.solc | 2 +- test/examples/cases/asm-let-uninit.solc | 2 +- test/examples/cases/asm-match-tuple-read.solc | 10 +- .../cases/asm-match-tuple-write-read.solc | 10 +- test/examples/cases/assembly.solc | 10 +- test/examples/cases/bal.solc | 33 +- test/examples/cases/bar.solc | 20 +- test/examples/cases/bitwise.solc | 14 +- test/examples/cases/bool-elim.solc | 16 +- test/examples/cases/bound-merge-case.solc | 2 +- test/examples/cases/bound-minimal.solc | 8 +- test/examples/cases/bound-only-test.solc | 8 +- test/examples/cases/bound-with-pragma.solc | 12 +- .../cases/bug-import-default-inst-shadow.solc | 13 +- test/examples/cases/bug-rep-name-capture.solc | 12 +- test/examples/cases/bug-spec-generic-let.solc | 44 +- test/examples/cases/catch-all.solc | 16 +- test/examples/cases/catenable-err.solc | 4 +- test/examples/cases/class-context.solc | 5 +- .../cases/class-return-type-miss.solc | 6 +- .../cases/class-type-name-collision.solc | 7 +- test/examples/cases/closure-capture-only.solc | 4 +- .../cases/closure-free-bound-test.solc | 2 +- .../cases/closure-free-var-local.solc | 6 +- test/examples/cases/closure-free-var-std.solc | 12 +- test/examples/cases/closure-free-var.solc | 14 +- test/examples/cases/closure.solc | 2 +- test/examples/cases/comparisons.solc | 12 +- test/examples/cases/complexproxy.solc | 27 +- test/examples/cases/compose0.solc | 2 +- test/examples/cases/compose_desugared.solc | 38 +- test/examples/cases/const-array.solc | 72 +- test/examples/cases/const.solc | 4 +- .../cases/constrained-instance-context.solc | 24 +- test/examples/cases/constrained-instance.solc | 24 +- .../examples/cases/constructor-weak-args.solc | 6 +- test/examples/cases/copytomem.solc | 12 +- .../cases/cyclical-defs-inferred.solc | 6 +- test/examples/cases/cyclical-defs.solc | 10 +- test/examples/cases/default-inst.solc | 19 +- .../cases/default-instance-missing.solc | 17 +- .../examples/cases/default-instance-weak.solc | 21 +- .../cases/derive-generic-excluded.solc | 45 +- test/examples/cases/derive-generic-sum.solc | 44 +- test/examples/cases/dispatch.solc | 200 +- .../dot-expression-assignment-context.solc | 6 +- .../dot-expression-call-arg-context.solc | 14 +- .../cases/dot-expression-constructor.solc | 14 +- .../cases/dot-expression-match-return.solc | 18 +- .../cases/dot-expression-nested-context.solc | 4 +- .../cases/dot-expression-no-context-fail.solc | 4 +- .../cases/dot-expression-unknown-fail.solc | 4 +- .../cases/dot-pattern-constructor.solc | 14 +- .../cases/dot-pattern-nested-constructor.solc | 22 +- .../cases/dot-primitive-constructor.solc | 10 +- test/examples/cases/duplicated-type-name.solc | 4 +- test/examples/cases/empty-asm.solc | 10 +- test/examples/cases/encoder.solc | 32 +- test/examples/cases/encoder1.solc | 22 +- test/examples/cases/fallback-with-args.solc | 6 +- test/examples/cases/fallback-with-return.solc | 6 +- .../cases/false-redundant-warning.solc | 18 +- test/examples/cases/field-access.solc | 8 +- .../cases/field-helper-cxt-collision.solc | 12 +- test/examples/cases/field-name-error.solc | 10 +- test/examples/cases/foo-class.solc | 5 +- test/examples/cases/for-body-shadow.solc | 4 +- test/examples/cases/for-break.solc | 4 +- test/examples/cases/for-continue.solc | 4 +- test/examples/cases/for-empty-init.solc | 4 +- test/examples/cases/for-init-shadow.solc | 4 +- test/examples/cases/for-inner-block.solc | 4 +- test/examples/cases/for-let-post.solc | 4 +- test/examples/cases/for-let.solc | 4 +- test/examples/cases/for-loop.solc | 4 +- test/examples/cases/for-multi-init.solc | 4 +- test/examples/cases/for-multi-post.solc | 4 +- .../examples/cases/fresh-pat-arg-synonym.solc | 6 +- test/examples/cases/fresh-pat-arg.solc | 6 +- .../cases/fresh-variable-shadowing.solc | 16 +- .../cases/generic-manual-no-pragma.solc | 16 +- .../cases/generic-product-no-pragma.solc | 26 +- .../examples/cases/generic-sum-no-pragma.solc | 38 +- test/examples/cases/if-examples.solc | 22 +- test/examples/cases/import-std.solc | 8 +- test/examples/cases/inc-closure.solc | 4 +- test/examples/cases/index-example.solc | 49 +- ...instance-closure-error-invalid-member.solc | 8 +- .../cases/instance-closure-error.solc | 8 +- .../cases/instance-context-wrong-kind.solc | 6 +- test/examples/cases/instance-synonym-int.solc | 13 +- test/examples/cases/instance-synonym.solc | 12 +- test/examples/cases/instance-wrong-sig.solc | 22 +- test/examples/cases/invokable-issue.solc | 17 +- test/examples/cases/ixa.solc | 108 +- test/examples/cases/join.solc | 30 +- test/examples/cases/joinErr.solc | 26 +- test/examples/cases/listeq.solc | 8 +- test/examples/cases/listid.solc | 14 +- test/examples/cases/ltimp.solc | 4 +- test/examples/cases/ltproxy.solc | 4 +- test/examples/cases/mainproxy.solc | 16 +- test/examples/cases/match-bitwise.solc | 24 +- .../cases/match-compiler-undef-asm.solc | 10 +- test/examples/cases/match-yul.solc | 12 +- test/examples/cases/memory.solc | 6 +- test/examples/cases/missing-instance.solc | 18 +- test/examples/cases/mod-example.solc | 10 +- test/examples/cases/modifier.solc | 6 +- test/examples/cases/modulo.solc | 12 +- test/examples/cases/monomorphic-require.solc | 19 +- test/examples/cases/morefun.solc | 10 +- test/examples/cases/mptc-both-templates.solc | 24 +- test/examples/cases/mptc-chain-phantom.solc | 31 +- .../cases/mptc-guard-extras-concrete.solc | 18 +- test/examples/cases/mptc-multi-instance.solc | 32 +- test/examples/cases/mptc-nop-mainty-free.solc | 18 +- .../examples/cases/mptc-partial-instance.solc | 28 +- test/examples/cases/mptc-template-a-only.solc | 18 +- test/examples/cases/mptc-template-b-only.solc | 18 +- test/examples/cases/multi-stmt-var-leaf.solc | 10 +- test/examples/cases/nano-desugared.solc | 366 +-- test/examples/cases/nid.solc | 4 +- test/examples/cases/noclosure.solc | 2 +- test/examples/cases/noconstr.solc | 8 +- test/examples/cases/notif.solc | 4 +- test/examples/cases/option2.solc | 44 +- .../cases/overlap-synonym-detected.solc | 14 +- .../cases/overlap-synonym-missed-order.solc | 14 +- .../overlap-synonym-missed-two-synonyms.solc | 16 +- test/examples/cases/overlapping-heads.solc | 16 +- test/examples/cases/pair-bug.solc | 4 +- test/examples/cases/pars.solc | 2 +- test/examples/cases/patterson-bug.solc | 49 +- .../cases/payable-toplevel-function.solc | 2 +- .../cases/phantom-type-return-con.solc | 12 +- test/examples/cases/polymatch-error.solc | 16 +- test/examples/cases/polymorphic-require.solc | 15 +- test/examples/cases/pragma_merge_base.solc | 38 +- .../cases/pragma_merge_fail_coverage.solc | 8 +- .../cases/pragma_merge_fail_patterson.solc | 4 +- test/examples/cases/pragma_merge_import.solc | 12 +- test/examples/cases/pragma_merge_verify.solc | 6 +- .../examples/cases/pragma_test_patterson.solc | 8 +- test/examples/cases/proxy-desugar.solc | 14 +- test/examples/cases/proxy.solc | 11 +- test/examples/cases/proxy1.solc | 10 +- test/examples/cases/public-constructor.solc | 8 +- test/examples/cases/public-fallback.solc | 6 +- .../cases/public-top-level-function.solc | 6 +- test/examples/cases/rec.solc | 10 +- test/examples/cases/redundant-match.solc | 16 +- .../cases/reference-encoding-good.solc | 185 +- .../cases/reference-encoding-good1.solc | 185 +- test/examples/cases/reference-encoding.solc | 167 +- test/examples/cases/reference-test.solc | 51 +- test/examples/cases/reference.solc | 28 +- test/examples/cases/references-daniel.solc | 220 +- .../require-annotation-contract-method.solc | 4 +- .../require-annotation-missing-param.solc | 2 +- .../cases/require-annotation-mutual.solc | 2 +- test/examples/cases/return-fun-adder.solc | 6 +- test/examples/cases/return-fun-bad-arity.solc | 4 +- test/examples/cases/return-fun-bad-param.solc | 4 +- .../examples/cases/return-fun-bad-return.solc | 4 +- test/examples/cases/return-fun-bad-sig.solc | 4 +- test/examples/cases/return-fun-const.solc | 4 +- test/examples/cases/return-fun-eq.solc | 6 +- test/examples/cases/return-fun-instance.solc | 10 +- test/examples/cases/return-fun-not-fun.solc | 2 +- .../same-name-constructor-qualifier.solc | 20 +- test/examples/cases/signature.solc | 6 +- test/examples/cases/simpleDiscount.solc | 26 +- test/examples/cases/simpleIfExpr.solc | 2 +- test/examples/cases/simpleIfStmt.solc | 2 +- test/examples/cases/simpleid.solc | 2 +- test/examples/cases/single-lambda.solc | 4 +- test/examples/cases/skolem-let.solc | 6 +- test/examples/cases/snds.solc | 8 +- test/examples/cases/spec-fail-ungrounded.solc | 8 +- .../cases/storage-adt-mapping-field-fail.solc | 10 +- .../cases/storage-adt-recursive-fail.solc | 10 +- .../cases/storage-adt-recursive-ok.solc | 24 +- test/examples/cases/strange-unbound.solc | 6 +- test/examples/cases/string-const.solc | 2 +- test/examples/cases/subject-index.solc | 49 +- test/examples/cases/subject-reduction.solc | 47 +- .../cases/subsumption-constraint.solc | 12 +- test/examples/cases/subsumption-test.solc | 4 +- test/examples/cases/sum-match-default.solc | 14 +- .../cases/super-class-cycle-fail.solc | 16 +- test/examples/cases/super-class-cycle.solc | 14 +- test/examples/cases/super-class-num.solc | 74 +- .../cases/super-class-recursive-arg.solc | 16 +- test/examples/cases/super-class.solc | 48 +- .../cases/synonym-arity-mismatch.solc | 4 +- test/examples/cases/synonym-basic.solc | 18 +- test/examples/cases/synonym-in-function.solc | 26 +- test/examples/cases/synonym-long-cycle.solc | 8 +- test/examples/cases/synonym-nested.solc | 24 +- test/examples/cases/synonym-param.solc | 14 +- test/examples/cases/synonym-recursive.solc | 6 +- .../cases/synonym-self-recursive.solc | 4 +- test/examples/cases/tabled-answer-reuse.solc | 16 +- test/examples/cases/tabled-cycle-fail.solc | 18 +- .../cases/tabled-default-instance.solc | 12 +- test/examples/cases/tabled-given-order.solc | 22 +- .../cases/tabled-left-recursive-fail.solc | 12 +- test/examples/cases/tabled-mutual-chain.solc | 20 +- .../examples/cases/tabled-residual-given.solc | 18 +- test/examples/cases/td.solc | 21 +- test/examples/cases/tiamat.solc | 99 +- test/examples/cases/toplevel-fallback.solc | 2 +- test/examples/cases/tuple-trick.solc | 42 +- test/examples/cases/tuva.solc | 49 +- test/examples/cases/tyexp.solc | 4 +- test/examples/cases/type-synonym-arg.solc | 6 +- test/examples/cases/typedef.solc | 9 +- test/examples/cases/ufcs-no-conflict.solc | 19 +- test/examples/cases/uintdesugared.solc | 338 ++- test/examples/cases/unbound-instance-var.solc | 13 +- .../cases/unconstrained-instance.solc | 24 +- test/examples/cases/undefined.solc | 6 +- test/examples/cases/unit.solc | 30 +- test/examples/cases/user-op-lambda.solc | 17 +- test/examples/cases/vartyped.solc | 2 +- test/examples/cases/weirdfoo.solc | 6 +- test/examples/cases/word-match-default.solc | 12 +- test/examples/cases/word-match.solc | 13 +- test/examples/cases/xref.solc | 102 +- test/examples/cases/yul-asm-for-body.solc | 8 +- test/examples/cases/yul-asm-switch-body.solc | 8 +- test/examples/cases/yul-deposit-example.solc | 6 +- test/examples/cases/yul-for.solc | 2 +- test/examples/cases/yul-function-typing.solc | 2 +- .../cases/yul-multi-return-arity-fail.solc | 2 +- test/examples/cases/yul-multi-return.solc | 2 +- test/examples/cases/yul-return.solc | 2 +- test/examples/comptime/CondExpr.solc | 14 +- test/examples/comptime/CondStmt.solc | 14 +- test/examples/comptime/OneOne.solc | 10 +- test/examples/comptime/OneTwo.solc | 10 +- test/examples/comptime/Plus.solc | 16 +- test/examples/comptime/Size.solc | 34 +- test/examples/comptime/StdSize.solc | 35 +- test/examples/comptime/comptime_syntax.solc | 8 +- test/examples/comptime/counter.solc | 14 +- test/examples/comptime/ct_asm_mem.solc | 6 +- test/examples/comptime/ct_asm_ret.solc | 4 +- test/examples/comptime/ct_chain_ok.solc | 6 +- test/examples/comptime/ct_let_ok.solc | 6 +- test/examples/comptime/ct_let_runtime.solc | 6 +- test/examples/comptime/ct_overloaded_bad.solc | 12 +- test/examples/comptime/ct_overloaded_ok.solc | 16 +- test/examples/comptime/ct_param_ok.solc | 4 +- .../comptime/ct_param_poly_runtime.solc | 12 +- test/examples/comptime/ct_param_runtime.solc | 6 +- test/examples/comptime/ct_runtime_arg.solc | 6 +- test/examples/comptime/fib.solc | 12 +- test/examples/comptime/fib2.solc | 8 +- test/examples/comptime/fib3.solc | 8 +- test/examples/comptime/fromInt.solc | 55 +- test/examples/comptime/fromInt2.solc | 36 +- test/examples/comptime/fromInt3.solc | 32 +- test/examples/comptime/fromLit.solc | 25 +- test/examples/comptime/int-untyped-let.solc | 8 +- test/examples/comptime/integer-basic.solc | 2 +- test/examples/comptime/integer-fib.solc | 4 +- .../comptime/integer-from-integer.solc | 10 +- test/examples/comptime/integer-lit-class.solc | 10 +- test/examples/comptime/integer-lit-cond.solc | 4 +- test/examples/comptime/integer-lit-pat.solc | 32 +- test/examples/comptime/integer-lit-poly.solc | 4 +- test/examples/comptime/integer-lit-safe.solc | 8 +- .../comptime/integer-lit-word-site.solc | 2 +- test/examples/comptime/integer-lit.solc | 8 +- test/examples/comptime/match_labels.solc | 20 +- test/examples/comptime/string-lit-keccak.solc | 8 +- test/examples/comptime/string-lit-len.solc | 8 +- test/examples/comptime/string-lit-ops.solc | 12 +- test/examples/comptime/uint256-lit.solc | 4 +- test/examples/dispatch/Revert.solc | 10 +- test/examples/dispatch/array_copy.solc | 24 +- test/examples/dispatch/array_nested.solc | 30 +- test/examples/dispatch/array_ops.solc | 16 +- test/examples/dispatch/array_string.solc | 22 +- test/examples/dispatch/assembly.solc | 6 +- test/examples/dispatch/basic.solc | 56 +- test/examples/dispatch/concat.solc | 22 +- test/examples/dispatch/counter.solc | 6 +- test/examples/dispatch/ecrecover.solc | 8 +- test/examples/dispatch/empty.solc | 4 +- .../dispatch/empty_no_constructor.solc | 4 +- test/examples/dispatch/fallback.solc | 8 +- test/examples/dispatch/fib.solc | 6 +- test/examples/dispatch/forloops.solc | 22 +- test/examples/dispatch/generic_product.solc | 34 +- test/examples/dispatch/generic_sum.solc | 62 +- test/examples/dispatch/hashes.solc | 16 +- test/examples/dispatch/memory.solc | 10 +- test/examples/dispatch/miniERC20.solc | 38 +- test/examples/dispatch/neg.solc | 68 +- test/examples/dispatch/nonpayable_ctor.solc | 6 +- test/examples/dispatch/ownable.solc | 10 +- test/examples/dispatch/payable.solc | 10 +- test/examples/dispatch/payable_ctor.solc | 8 +- test/examples/dispatch/slices.solc | 26 +- .../dispatch/specialise_sum_of_product.solc | 61 +- test/examples/dispatch/storage.solc | 8 +- test/examples/dispatch/storage_adt_abi.solc | 42 +- test/examples/dispatch/storage_adt_bool.solc | 66 +- test/examples/dispatch/storage_adt_enum.solc | 67 +- test/examples/dispatch/storage_adt_field.solc | 86 +- .../dispatch/storage_adt_mapping.solc | 78 +- test/examples/dispatch/storage_array.solc | 18 +- .../dispatch/storage_dynamic_field.solc | 38 +- test/examples/dispatch/stringid.solc | 12 +- test/examples/dispatch/sum_wide_product.solc | 16 +- test/examples/dispatch/ufcs_array.solc | 18 +- test/examples/dispatch/weth9.solc | 36 +- test/examples/invokable/021nid.solc | 8 +- test/examples/invokable/022nid-invoke.solc | 16 +- test/examples/invokable/024lamid.solc | 4 +- test/examples/invokable/025lamid-invoke.solc | 14 +- test/examples/invokable/026capture.solc | 22 +- test/examples/invokable/027retfun.solc | 22 +- test/examples/invokable/028modifier.solc | 30 +- test/examples/invokable/031enum.solc | 44 +- test/examples/opcodes/all-shapes.solc | 10 +- test/examples/pragmas/bound.solc | 16 +- test/examples/pragmas/coverage.solc | 10 +- test/examples/pragmas/patterson.solc | 18 +- test/examples/spec/00answer.solc | 2 +- test/examples/spec/010answer.solc | 2 +- test/examples/spec/011id.solc | 8 +- test/examples/spec/012nid.solc | 8 +- test/examples/spec/013comp.solc | 8 +- test/examples/spec/01id.solc | 8 +- test/examples/spec/021not.solc | 22 +- test/examples/spec/022add.solc | 4 +- test/examples/spec/024arith.solc | 16 +- test/examples/spec/027sstore.solc | 2 +- test/examples/spec/02nid.solc | 8 +- test/examples/spec/031maybe.solc | 16 +- test/examples/spec/032simplejoin.solc | 44 +- test/examples/spec/033join.solc | 26 +- test/examples/spec/034cojoin.solc | 42 +- test/examples/spec/035padding.solc | 14 +- test/examples/spec/036wildcard.solc | 14 +- test/examples/spec/037dwarves.solc | 22 +- test/examples/spec/038food0.solc | 20 +- test/examples/spec/039food.solc | 30 +- test/examples/spec/041pair.solc | 10 +- test/examples/spec/042triple.solc | 10 +- test/examples/spec/043fstsnd.solc | 26 +- test/examples/spec/047rgb.solc | 14 +- test/examples/spec/048rgb2.solc | 16 +- test/examples/spec/049rgb3.solc | 18 +- test/examples/spec/051expreturn.solc | 34 +- test/examples/spec/051negBool.solc | 28 +- test/examples/spec/052negPair.solc | 54 +- test/examples/spec/052return.solc | 30 +- test/examples/spec/053return.solc | 24 +- test/examples/spec/06comp.solc | 6 +- test/examples/spec/09not.solc | 22 +- test/examples/spec/101struct1Field.solc | 185 +- test/examples/spec/102uintField.solc | 181 +- test/examples/spec/103struct3Fields.solc | 187 +- test/examples/spec/105nestedStruct.solc | 234 +- test/examples/spec/10negBool.solc | 30 +- test/examples/spec/111storageStruct.solc | 179 +- test/examples/spec/112ContractStorage.solc | 12 +- test/examples/spec/113counter.solc | 6 +- test/examples/spec/11negPair.solc | 60 +- test/examples/spec/120basicCounter.solc | 4 +- test/examples/spec/121counter.solc | 8 +- test/examples/spec/122counters.solc | 4 +- test/examples/spec/123stackAndStorage.solc | 4 +- test/examples/spec/126nanoerc20.solc | 46 +- test/examples/spec/127microerc20.solc | 56 +- test/examples/spec/128minierc20.solc | 32 +- test/examples/spec/129arraystorage.solc | 12 +- test/examples/spec/130arrayfield.solc | 12 +- test/examples/spec/131constructor.solc | 6 +- test/examples/spec/131localindex.solc | 12 +- test/examples/spec/132nestedarray.solc | 12 +- test/examples/spec/133arraystring.solc | 14 +- test/examples/spec/135aliaspush.solc | 14 +- test/examples/spec/135cons3.solc | 17 +- test/examples/spec/903badassign.solc | 30 +- test/examples/spec/939badfood.solc | 22 +- test/examples/spec/SimpleField.solc | 12 +- test/examples/spec/StorageLib.solc | 184 +- test/examples/spec/attic/051expreturn.solc | 34 +- test/examples/spec/attic/052return.solc | 30 +- test/examples/spec/attic/053return.solc | 24 +- test/imports/alias_dup.solc | 6 +- test/imports/alias_hides_original_fail.solc | 4 +- .../alias_unqualified_constr_fail.solc | 4 +- test/imports/alias_unqualified_fun_fail.solc | 4 +- test/imports/alias_unqualified_type_fail.solc | 4 +- test/imports/ambA.solc | 2 +- test/imports/ambB.solc | 2 +- test/imports/amb_main.solc | 6 +- test/imports/amb_ok.solc | 2 +- test/imports/boolalias.solc | 4 +- test/imports/boolalias_open_fail.solc | 4 +- test/imports/boolaliastype.solc | 4 +- test/imports/boolconselect_fail.solc | 4 +- test/imports/boolconselect_ok.solc | 4 +- test/imports/booldef.solc | 22 +- test/imports/boolmain.solc | 2 +- test/imports/boolqualified.solc | 2 +- test/imports/boolqualifiedtype.solc | 2 +- test/imports/boolselect.solc | 4 +- test/imports/cycleA.solc | 2 +- test/imports/cycleB.solc | 2 +- test/imports/cycle_main.solc | 2 +- test/imports/dot_context_expr.solc | 12 +- test/imports/dot_left.solc | 2 +- test/imports/dot_right.solc | 2 +- test/imports/dupqual_a.solc | 2 +- test/imports/dupqual_b.solc | 2 +- test/imports/dupqual_main.solc | 6 +- test/imports/dupqual_module_main.solc | 2 +- test/imports/export_item_dup_fail.solc | 2 +- test/imports/export_module_dup_fail.solc | 2 +- test/imports/external_lib_alias_main.solc | 4 +- test/imports/external_lib_main.solc | 2 +- test/imports/extlib/math/api.solc | 2 +- test/imports/extlib/math/internals/add.solc | 4 +- test/imports/extlib/util.solc | 2 +- test/imports/foo.solc | 2 +- test/imports/foo/bar.solc | 2 +- test/imports/foo/bar/baz.solc | 2 +- test/imports/glob_amb_a.solc | 2 +- test/imports/glob_amb_b.solc | 2 +- test/imports/glob_amb_main_fail.solc | 6 +- test/imports/glob_export_mixed.solc | 2 +- test/imports/glob_hiding_amb_ok.solc | 6 +- test/imports/glob_import_dup.solc | 4 +- test/imports/glob_import_hiding.solc | 10 +- .../glob_import_hiding_unknown_fail.solc | 4 +- test/imports/glob_import_mixed.solc | 4 +- test/imports/glob_import_ok.solc | 10 +- test/imports/globlib.solc | 6 +- test/imports/hidden_ctor_dot_fail.solc | 4 +- test/imports/hidden_ctor_expr_fail.solc | 4 +- test/imports/hidden_ctor_lib.solc | 6 +- .../hidden_ctor_nonexhaustive_fail.solc | 10 +- test/imports/hidden_ctor_pattern_fail.solc | 12 +- test/imports/hidden_ctor_wildcard_ok.solc | 12 +- test/imports/import_std_minimal.solc | 2 +- test/imports/leak_a.solc | 2 +- test/imports/leak_b.solc | 2 +- test/imports/leak_main.solc | 2 +- test/imports/mirror/helper.solc | 2 +- test/imports/module_name_shadow.solc | 6 +- .../imports/module_qualified_constructor.solc | 2 +- .../module_qualified_constructor_alias.solc | 4 +- .../module_qualified_constructor_pattern.solc | 10 +- .../module_unqualified_constr_fail.solc | 2 +- test/imports/module_unqualified_fun_fail.solc | 2 +- .../imports/module_unqualified_type_fail.solc | 2 +- test/imports/nested_alias.solc | 4 +- test/imports/nested_deep_qualifier.solc | 2 +- test/imports/nested_direct_qualifier.solc | 2 +- test/imports/nested_foo_and_bar.solc | 4 +- test/imports/nested_select.solc | 4 +- test/imports/ns_constr_dup.solc | 6 +- test/imports/ns_cross_ok.solc | 4 +- test/imports/opaque_alias_leak_fail.solc | 4 +- test/imports/opaque_alias_main.solc | 4 +- test/imports/opaque_alias_mid.solc | 4 +- .../opaque_alias_qualifier_leak_fail.solc | 4 +- test/imports/opaque_dep_base.solc | 4 +- test/imports/opaque_select_alias_main.solc | 4 +- test/imports/opaque_select_alias_mid.solc | 4 +- .../opaque_select_direct_leak_fail.solc | 4 +- test/imports/opaque_select_direct_mid.solc | 4 +- test/imports/pragma_scope_lib.solc | 4 +- test/imports/pragma_scope_main.solc | 6 +- test/imports/private_bad_lib.solc | 4 +- test/imports/private_bad_main.solc | 2 +- test/imports/private_helper_a.solc | 4 +- test/imports/private_helper_main.solc | 2 +- .../reexport_ctor_expr_hidden_fail.solc | 2 +- test/imports/reexport_ctor_expr_ok.solc | 2 +- test/imports/reexport_ctor_pattern.solc | 10 +- test/imports/reexport_items/pkg/util.solc | 18 +- test/imports/reexport_items_main.solc | 4 +- test/imports/reexport_module/pkg/util.solc | 18 +- test/imports/reexport_module_alias_main.solc | 2 +- test/imports/reexport_module_main.solc | 2 +- test/imports/reexport_select_alias_main.solc | 4 +- .../reexport_select_alias_wrapper.solc | 2 +- test/imports/reexport_select_base.solc | 2 +- test/imports/reexport_select_main.solc | 4 +- test/imports/reexport_select_wrapper.solc | 2 +- test/imports/rootcheck/nested/main.solc | 2 +- test/imports/rootcheck/nested/provider.solc | 2 +- .../nested/relative_and_lib_main.solc | 4 +- test/imports/rootcheck/provider.solc | 2 +- test/imports/select_alias_item_ok.solc | 4 +- test/imports/select_alias_multi_ok.solc | 4 +- test/imports/select_alias_tail_fail.solc | 4 +- test/imports/select_dup_item.solc | 4 +- test/imports/select_fail.solc | 4 +- test/imports/select_hiding_fail.solc | 4 +- test/imports/select_hiding_ok.solc | 4 +- test/imports/select_ok.solc | 4 +- test/imports/select_shadow_local.solc | 6 +- test/imports/select_shadow_param_ok.solc | 4 +- test/imports/select_unknown.solc | 4 +- .../imports/selective_unqualified_fun_ok.solc | 4 +- test/imports/selectlib.solc | 4 +- test/imports/selfcycle.solc | 2 +- test/imports/strict_open_fail.solc | 2 +- test/imports/symlink_identity_fail.solc | 6 +- test/imports/transitive_dep_base.solc | 2 +- test/imports/transitive_dep_main_module.solc | 4 +- test/imports/transitive_dep_main_select.solc | 4 +- test/imports/transitive_dep_mid.solc | 4 +- test/imports/type_collision_a.solc | 4 +- test/imports/type_collision_b.solc | 4 +- test/imports/type_collision_main.solc | 2 +- test/imports/unordered_imports_lib.solc | 12 +- test/imports/unordered_imports_main.solc | 4 +- test/imports/vendor/math/helper.solc | 2 +- test/imports/wildA.solc | 2 +- test/imports/wildB.solc | 2 +- test/imports/wild_main.solc | 2 +- test/imports/wrapper_shadow_success.solc | 4 +- 608 files changed, 8899 insertions(+), 6904 deletions(-) create mode 100755 scripts/migrate_new_syntax.py create mode 100644 scripts/test_migrate_new_syntax.py diff --git a/blog-post/erc20.sol b/blog-post/erc20.sol index aa6a73697..fe5264130 100644 --- a/blog-post/erc20.sol +++ b/blog-post/erc20.sol @@ -1,6 +1,6 @@ import assign; -function caller() -> address { +function caller() returns (address) { let res: word; assembly { res := caller() @@ -8,7 +8,7 @@ function caller() -> address { return address(res); } -function myrevert(msg: word) -> () { +function myrevert(msg: word) returns (()) { assembly { mstore(0, msg) revert(0, 32) } } @@ -21,21 +21,21 @@ contract MiniERC20 { owner : address; decimals : uint; totalSupply : uint; - balances : mapping(address,uint); - allowance : mapping(address, mapping(address, uint)); + balances : mapping(address => uint); + allowance : mapping(address => mapping(address => uint)); - function mint(amount:uint) -> () { + function mint(amount:uint) returns (()) { balances[owner] = Num.add(balances[owner], amount); totalSupply = Num.add(totalSupply, amount); } - function transferFrom(src:address, dst:address, amt:uint) -> bool { + function transferFrom(src:address, dst:address, amt:uint) returns (bool) { let msg_sender = caller(); require( balances[src] >= amt /* "token/insufficient-balance" */ , 0x746f6b656e2f696e73756666696369656e742d62616c616e6365 ); - if (src != msg_sender && allowance[src][msg_sender] != (Num.maxVal():uint)) { + if (src != msg_sender && allowance[src][msg_sender] != (Num.maxVal() as uint)) { require( allowance[src][msg_sender] >= amt /* "token/insufficient-allowance" */ , 0x746f6b656e2f696e73756666696369656e742d616c6c6f77616e6365 ); @@ -46,18 +46,18 @@ contract MiniERC20 { return true; } - function approve(usr: address, amt: uint) -> bool { + function approve(usr: address, amt: uint) returns (bool) { let msg_sender = caller(); allowance[msg_sender][usr] = amt; return true; } - function init() -> () { + function init() returns (()) { owner = address(0x123456789abcdef); decimals = Num.fromWord(18); } - function main() -> uint { + function main() returns (uint) { let msg_sender = caller(); init(); mint(uint(1000)); diff --git a/blog-post/payment.sol b/blog-post/payment.sol index 271603621..310fba14a 100644 --- a/blog-post/payment.sol +++ b/blog-post/payment.sol @@ -1,21 +1,18 @@ -data address = address(word); +enum address { address(word) } -data tokenid = tokenid(word); +enum tokenid { tokenid(word) } -data Payment = - Native(address, word) - | ERC20 (address, address, address, word) - | ERC721(address, address, address, tokenid); +enum Payment { Native(address, word), ERC20(address, address, address, word), ERC721(address, address, address, tokenid) } function processPayment(payment : Payment) { - match payment { - | Native(to, amount) => + match (payment ) { + case Native(to, amount) { transfer(to, amount); - | ERC20(token, from, to, amount) => + } case ERC20(token, from, to, amount) { transferFromERC20(from, to, amount); - | ERC721(token, from, to, tokenId) => + } case ERC721(token, from, to, tokenId) { transferFromERC721(from, to, tokenId); - } + } } } function transfer (to : address, amount : word) { diff --git a/blog-post/sum.sol b/blog-post/sum.sol index e390353b2..8585684c0 100644 --- a/blog-post/sum.sol +++ b/blog-post/sum.sol @@ -1,33 +1,33 @@ -data uint128 = uint128(word); +enum uint128 { uint128(word) } -forall T . class T : Sum { - function sum (x : T, y : T) -> T; +trait Sum { + function sum (x : T, y : T) returns (T); } -instance uint128 : Sum { - function sum(x : uint128, y : uint128) -> uint128 { +impl Sum { + function sum(x : uint128, y : uint128) returns (uint128) { let res : word; - match x, y { - | uint128(n), uint128(m) => + match (x, y ) { + case (uint128(n), uint128(m) ) { assembly { - res := add(n,m); + res := add(n,m) if lt(res, n) { - revert(0,0); + revert(0,0) } if gt(res, 0xffffffffffffffffffffffffffffffff) { - revert(0,0); + revert(0,0) } } - } + } } return uint128(res); } } -forall T1 T2 . T1 : Sum, T2 : Sum => instance (T1,T2) : Sum { - function sum (p1 : (T1, T2), p2 (T1, T2)) -> (T1,T2) { - match p1, p2 { - | (x1,y1), (x2,y2) => +impl Sum<(T1, T2)> where T1: Sum, T2: Sum { + function sum (p1 : (T1, T2), p2 : (T1, T2)) returns ((T1, T2)) { + match (p1, p2 ) { + case ((x1,y1), (x2,y2) ) { return (Sum.sum(x1,x2), Sum.sum(y1,y2)); - } + } } } } diff --git a/concept-art/has-field.sol b/concept-art/has-field.sol index 707b185ca..12a7af263 100644 --- a/concept-art/has-field.sol +++ b/concept-art/has-field.sol @@ -1,15 +1,15 @@ -data Unit = Unit -data Pair(a, b) = Pair(a,b) +enum Unit { Unit } +enum Pair { Pair(a, b) } -type uint = word -type string = word -type bool = word +type uint is word; +type string is word; +type bool is word; -data Memory(t) = Memory(Word) +enum Memory { Memory(Word) } // this lets us link a given field in a struct to its position in it's // underlying generic representation as a tuple. -class self:Field(prevTypes, ty) {} +trait Field {} // this struct should desugar into the following //struct S { @@ -19,34 +19,34 @@ class self:Field(prevTypes, ty) {} //} // a type abstraction over tuples -type s = Pair(uint, Pair(string, bool)) +type s is Pair>; // unique types identifying each field -type sf1 = Unit -type sf2 = Unit -type sf3 = Unit +type sf1 is Unit; +type sf2 is Unit; +type sf3 is Unit; // Field instances linking each field to it's position in the underlying tuple -instance Pair(s, sf1):Field(Unit, uint) {} -instance Pair(s, sf2):Field(uint, string) {} -instance Pair(s, sf3):Field(Pair(uint, string), bool) {} +impl Field, Unit, uint> {} +impl Field, uint, string> {} +impl Field, Pair, bool> {} // struct field member access desugars into calls to this class -class self:HasField(fieldType) { - function getField(x:self) -> fieldType; +trait HasField { + function getField(x:self) returns (fieldType); } // we instantiate generic instances for references to types that implement Field -instance (Pair(t, fieldName):Field(prevTypes, fieldType), fieldType:ValueType) => Pair(Memory(t), fieldName):HasField(Memory(fieldType)) { - function getField(x : Pair(Memory(T), fieldName)) -> fieldType { +impl HasField, fieldName>, Memory> where Pair: Field, fieldType: ValueType { + function getField(x : Pair, fieldName>) returns (fieldType) { // TODO: define this function... - let x : Proxy(prevTypes) = Proxy; + let x : Proxy = Proxy; let sz : Word = getMemorySize(x); let ret : fieldType = ValueType.abs(0); assembly { ret := mload(add(rep(fst(x)), sz)) - }; + } return ret; } } diff --git a/scripts/gen-std-opcodes.py b/scripts/gen-std-opcodes.py index 46a0d1b75..91e7c2a40 100755 --- a/scripts/gen-std-opcodes.py +++ b/scripts/gen-std-opcodes.py @@ -128,7 +128,7 @@ def gen_function(op): call_args = ", ".join(args) ret_type = "word" if op["output"] == 1 else "()" - lines = [f"function {fname}({params}) -> {ret_type} {{"] + lines = [f"function {fname}({params}) returns ({ret_type}) {{"] if op["output"] == 1: lines.append(" let res;") lines.append(" assembly {") diff --git a/scripts/migrate_new_syntax.py b/scripts/migrate_new_syntax.py new file mode 100755 index 000000000..d3ff2b3c4 --- /dev/null +++ b/scripts/migrate_new_syntax.py @@ -0,0 +1,2253 @@ +#!/usr/bin/env python3 +"""Migrate the tracked Solcore source corpus to the Solidity-style syntax. + +The migration is deliberately token-aware: + +* comments and string literals are never searched or rewritten as source code; +* parenthesized calls are changed to angle-bracket type applications only in a + syntactic type position; +* only git-tracked ``.solc`` files and the explicitly listed Core ``.sol`` + sources are eligible for the default corpus migration. + +The unresolved export grammar and contextual ``.Constructor`` shorthand are +intentionally preserved. Legacy proxy shorthand is migrated to +``Proxy`` in types and ``Proxy as Proxy`` in expressions. +""" + +from __future__ import annotations + +import argparse +import dataclasses +import pathlib +import re +import subprocess +import sys +from collections.abc import Iterable, Sequence + + +REPO_ROOT = pathlib.Path(__file__).resolve().parents[1] + +CORE_SOL_FILES = ( + "blog-post/adjust.sol", + "blog-post/erc20.sol", + "blog-post/payment.sol", + "blog-post/sum.sol", + "concept-art/has-field.sol", +) + +CLASSIC_SOL_FILES = frozenset( + { + "blog-post/PaymentHandler.sol", + "lib/StdAssertions.sol", + "lib/Vm.sol", + "lib/console.sol", + "lib/stdlib.sol", + "test/examples/dispatch/fib.sol", + } +) + +SPECIAL_FIXTURES = { + # Keep this diagnostic fixture parse-invalid, but make the malformed input + # use only new-syntax tokens so the failure is reported at the real fault + # rather than after declaration-parser backtracking. + pathlib.Path("test/diagnostics/parse-error.solc"): ( + "enum Broken { Value(word }\n" + ), + # This negative trait fixture intentionally has no implementation. Give + # its declaration a complete new-syntax signature so regeneration from + # HEAD preserves the original semantic failure instead of stopping at the + # removed arrow syntax (or at a missing signature terminator). + pathlib.Path("test/examples/cases/catenable-err.solc"): ( + "trait Catenable {\n" + " function cat(x: t) returns (bytes memory);\n" + "}\n" + ), +} + +FILE_FIXUPS = { + # The old source accidentally omitted the colon on its second parameter. + # Preserve the hand-corrected new-syntax declaration when regenerating the + # corpus from HEAD. + pathlib.Path("blog-post/sum.sol"): ( + ( + "function sum (p1 : (T1, T2), p2 (T1, T2)) " + "returns ((T1, T2)) {", + "function sum (p1 : (T1, T2), p2 : (T1, T2)) " + "returns ((T1, T2)) {", + ), + ), +} + +MODIFIERS = frozenset( + {"public", "private", "external", "internal", "pure", "view", "payable"} +) + +TRIVIA_KINDS = frozenset({"space", "comment"}) + + +@dataclasses.dataclass(frozen=True) +class Token: + kind: str + text: str + start: int + end: int + + +@dataclasses.dataclass(frozen=True) +class Edit: + start: int + end: int + replacement: str + + +def tokenize(source: str) -> list[Token]: + """Lex enough of Solcore to distinguish code from comments and strings.""" + + tokens: list[Token] = [] + i = 0 + n = len(source) + multi = ( + "->", + "=>", + ":=", + "==", + "!=", + "<=", + ">=", + "&&", + "||", + "+=", + "-=", + "^=", + "&=", + "|=", + "%=", + "**", + ) + + while i < n: + start = i + ch = source[i] + + if ch.isspace(): + i += 1 + while i < n and source[i].isspace(): + i += 1 + tokens.append(Token("space", source[start:i], start, i)) + continue + + if source.startswith("//", i): + newline = source.find("\n", i + 2) + i = n if newline < 0 else newline + tokens.append(Token("comment", source[start:i], start, i)) + continue + + if source.startswith("/*", i): + close = source.find("*/", i + 2) + i = n if close < 0 else close + 2 + tokens.append(Token("comment", source[start:i], start, i)) + continue + + if ch in {'"', "'"}: + quote = ch + i += 1 + while i < n: + if source[i] == "\\": + i = min(i + 2, n) + elif source[i] == quote: + i += 1 + break + else: + i += 1 + tokens.append(Token("string", source[start:i], start, i)) + continue + + if ch.isalpha() or ch == "_": + i += 1 + while i < n and (source[i].isalnum() or source[i] == "_"): + i += 1 + tokens.append(Token("ident", source[start:i], start, i)) + continue + + if ch.isdigit(): + i += 1 + while i < n and ( + source[i].isalnum() or source[i] in {"_", "."} + ): + i += 1 + tokens.append(Token("number", source[start:i], start, i)) + continue + + op = next((candidate for candidate in multi if source.startswith(candidate, i)), None) + if op is not None: + i += len(op) + tokens.append(Token("symbol", op, start, i)) + continue + + i += 1 + tokens.append(Token("symbol", ch, start, i)) + + return tokens + + +def significant(source: str) -> list[Token]: + return [token for token in tokenize(source) if token.kind not in TRIVIA_KINDS] + + +def apply_edits(source: str, edits: Iterable[Edit]) -> str: + """Apply non-overlapping edits, coalescing insertions at one position.""" + + ordered = sorted(edits, key=lambda edit: (edit.start, edit.end)) + coalesced: list[Edit] = [] + for edit in ordered: + if edit.start > edit.end: + raise ValueError(f"invalid edit: {edit}") + if coalesced and edit.start == edit.end == coalesced[-1].start == coalesced[-1].end: + previous = coalesced.pop() + coalesced.append( + Edit(edit.start, edit.end, previous.replacement + edit.replacement) + ) + continue + if coalesced and edit.start < coalesced[-1].end: + raise ValueError(f"overlapping edits: {coalesced[-1]} and {edit}") + coalesced.append(edit) + + result = source + for edit in reversed(coalesced): + result = result[: edit.start] + edit.replacement + result[edit.end :] + return result + + +def matching_token(tokens: Sequence[Token], open_index: int) -> int | None: + pairs = {"(": ")", "[": "]", "{": "}", "<": ">"} + opener = tokens[open_index].text + closer = pairs.get(opener) + if closer is None: + return None + depth = 0 + for index in range(open_index, len(tokens)): + text = tokens[index].text + if text == opener: + depth += 1 + elif text == closer: + depth -= 1 + if depth == 0: + return index + return None + + +def assembly_token_indexes(tokens: Sequence[Token]) -> set[int]: + """Return token indexes belonging to embedded Yul assembly blocks.""" + + result: set[int] = set() + for index, token in enumerate(tokens): + if token.text != "assembly": + continue + open_assembly = index + 1 + while ( + open_assembly < len(tokens) + and tokens[open_assembly].text not in {"{", ";", "}"} + ): + open_assembly += 1 + if ( + open_assembly >= len(tokens) + or tokens[open_assembly].text != "{" + ): + continue + close_assembly = matching_token(tokens, open_assembly) + if close_assembly is not None: + result.update(range(open_assembly, close_assembly + 1)) + return result + + +def split_top_level( + tokens: Sequence[Token], start: int, end: int, separator: str = "," +) -> list[tuple[int, int]]: + result: list[tuple[int, int]] = [] + segment_start = start + stack: list[str] = [] + pairs = {"(": ")", "[": "]", "<": ">"} + closing = frozenset(pairs.values()) + + for index in range(start, end): + text = tokens[index].text + if text in pairs: + stack.append(pairs[text]) + elif text in closing and stack and text == stack[-1]: + stack.pop() + elif text == separator and not stack: + result.append((segment_start, index)) + segment_start = index + 1 + result.append((segment_start, end)) + return [(left, right) for left, right in result if left < right] + + +class TypeParser: + """Small parser for the old and new surface type grammars.""" + + def __init__(self, tokens: Sequence[Token]): + self.tokens = tokens + + def parse(self, index: int) -> tuple[str, int] | None: + left = self.parse_atom(index) + if left is None: + return None + rendered, index = left + if index < len(self.tokens) and self.tokens[index].text == "->": + right = self.parse(index + 1) + if right is None: + return None + right_text, index = right + rendered = f"function({rendered}) internal returns ({right_text})" + return rendered, index + + def parse_atom(self, index: int) -> tuple[str, int] | None: + if index >= len(self.tokens): + return None + + token = self.tokens[index] + text = token.text + + if text == "comptime": + parsed = self.parse_atom(index + 1) + if parsed is None: + return None + rendered, index = parsed + return f"comptime {rendered}", index + + if text == "@": + parsed = self.parse_atom(index + 1) + if parsed is None: + return None + rendered, index = parsed + return f"Proxy<{rendered}>", index + + if text == "(": + close = matching_token(self.tokens, index) + if close is None: + return None + if close == index + 1: + rendered = "()" + else: + parts: list[str] = [] + for left, right in split_top_level(self.tokens, index + 1, close): + parsed = self.parse(left) + if parsed is None or parsed[1] != right: + return None + parts.append(parsed[0]) + rendered = parts[0] if len(parts) == 1 else f"({', '.join(parts)})" + return self.parse_array_suffix(rendered, close + 1) + + if token.kind != "ident": + return None + + name_parts = [text] + index += 1 + while ( + index + 1 < len(self.tokens) + and self.tokens[index].text == "." + and self.tokens[index + 1].kind == "ident" + ): + name_parts.extend((".", self.tokens[index + 1].text)) + index += 2 + name = "".join(name_parts) + + if name == "function" and index < len(self.tokens) and self.tokens[index].text == "(": + close = matching_token(self.tokens, index) + if close is None: + return None + args = self.parse_type_list(index + 1, close) + if args is None: + return None + index = close + 1 + attributes: list[str] = [] + while index < len(self.tokens) and self.tokens[index].text in { + "internal", + "external", + "pure", + "view", + "payable", + }: + attributes.append(self.tokens[index].text) + index += 1 + if index >= len(self.tokens) or self.tokens[index].text != "returns": + return None + if index + 1 >= len(self.tokens) or self.tokens[index + 1].text != "(": + return None + ret_close = matching_token(self.tokens, index + 1) + if ret_close is None: + return None + returns = self.parse_type_list(index + 2, ret_close) + if returns is None: + return None + attr_text = " ".join(attributes or ["internal"]) + rendered = ( + f"function({', '.join(args)}) {attr_text} " + f"returns ({', '.join(returns)})" + ) + return self.parse_array_suffix(rendered, ret_close + 1) + + has_old_bracket_args = ( + index < len(self.tokens) + and self.tokens[index].text == "[" + and name in {"Memory", "Stack", "Ref"} + ) + if ( + index < len(self.tokens) + and self.tokens[index].text in {"(", "<"} + ) or has_old_bracket_args: + opener = self.tokens[index].text + close = matching_token(self.tokens, index) + if close is None: + return None + args = self.parse_type_list(index + 1, close) + if args is None: + return None + if name == "mapping" and len(args) == 2: + rendered = f"mapping({args[0]} => {args[1]})" + elif name in {"memory", "storage", "calldata"} and len(args) == 1: + rendered = f"{args[0]} {name}" + elif name == "array" and len(args) == 1: + rendered = f"{args[0]}[]" + elif name == "array" and len(args) == 2: + rendered = f"{args[1]}[{args[0]}]" + else: + rendered = f"{name}<{', '.join(args)}>" + index = close + 1 + else: + rendered = name + + return self.parse_array_suffix(rendered, index) + + def parse_array_suffix(self, rendered: str, index: int) -> tuple[str, int]: + while index < len(self.tokens) and self.tokens[index].text == "[": + close = matching_token(self.tokens, index) + if close is None: + break + size = "".join(token.text for token in self.tokens[index + 1 : close]) + rendered += f"[{size}]" + index = close + 1 + return rendered, index + + def parse_type_list(self, start: int, end: int) -> list[str] | None: + if start == end: + return [] + rendered: list[str] = [] + for left, right in split_top_level(self.tokens, start, end): + parsed = self.parse(left) + if parsed is None or parsed[1] != right: + return None + rendered.append(parsed[0]) + return rendered + + +def normalize_type_fragment(source: str) -> str: + tokens = significant(source) + if not tokens: + return source.strip() + parsed = TypeParser(tokens).parse(0) + if parsed is None or parsed[1] != len(tokens): + return "".join(token.text for token in tokens) + return parsed[0] + + +def normalize_predicate_fragment(source: str) -> str: + tokens = significant(source.strip()) + if tokens and tokens[0].text == "(": + close = matching_token(tokens, 0) + if close == len(tokens) - 1: + tokens = tokens[1:close] + + colon = top_level_token(tokens, ":") + if colon is None: + return "".join(token.text for token in tokens) + + subject_tokens = tokens[:colon] + class_tokens = tokens[colon + 1 :] + subject = TypeParser(subject_tokens).parse(0) + if subject is None or subject[1] != len(subject_tokens): + subject_text = "".join(token.text for token in subject_tokens) + else: + subject_text = subject[0] + + if not class_tokens: + return f"{subject_text}:" + + name_parts: list[str] = [] + index = 0 + while index < len(class_tokens): + if class_tokens[index].kind != "ident": + break + name_parts.append(class_tokens[index].text) + index += 1 + if ( + index + 1 < len(class_tokens) + and class_tokens[index].text == "." + and class_tokens[index + 1].kind == "ident" + ): + name_parts.append(".") + index += 1 + continue + break + class_name = "".join(name_parts) + params: list[str] = [] + if index < len(class_tokens) and class_tokens[index].text in {"(", "[", "<"}: + close = matching_token(class_tokens, index) + if close is not None: + parsed_params = TypeParser(class_tokens).parse_type_list(index + 1, close) + if parsed_params is not None: + params = parsed_params + index = close + 1 + class_text = class_name + if params: + class_text += f"<{', '.join(params)}>" + if index < len(class_tokens): + class_text += "".join(token.text for token in class_tokens[index:]) + return f"{subject_text}: {class_text}" + + +def normalize_predicate_list(source: str) -> str: + tokens = significant(source) + if not tokens: + return "" + if tokens[0].text == "(": + close = matching_token(tokens, 0) + if close == len(tokens) - 1: + tokens = tokens[1:close] + predicates: list[str] = [] + for left, right in split_top_level(tokens, 0, len(tokens)): + fragment_start = tokens[left].start + fragment_end = tokens[right - 1].end + predicates.append( + normalize_predicate_fragment(source[fragment_start:fragment_end]) + ) + return ", ".join(predicate for predicate in predicates if predicate) + + +def top_level_token(tokens: Sequence[Token], wanted: str) -> int | None: + stack: list[str] = [] + pairs = {"(": ")", "[": "]", "<": ">"} + for index, token in enumerate(tokens): + text = token.text + if text in pairs: + stack.append(pairs[text]) + elif stack and text == stack[-1]: + stack.pop() + elif not stack and text == wanted: + return index + return None + + +def preserved_comments(source: str, start: int, end: int) -> str: + comments = [ + token.text + for token in tokenize(source[start:end]) + if token.kind == "comment" + ] + if not comments: + return "" + return "\n".join(comments) + "\n" + + +def declaration_end( + source: str, tokens: Sequence[Token], start_index: int +) -> tuple[int, bool]: + stack: list[str] = [] + pairs = {"(": ")", "[": "]", "<": ">"} + for index in range(start_index, len(tokens)): + text = tokens[index].text + if text in pairs: + stack.append(pairs[text]) + elif stack and text == stack[-1]: + stack.pop() + elif not stack and text == ";": + return index, True + elif not stack and text in {"{", "}"}: + break + + line_end = source.find("\n", tokens[start_index].start) + if line_end < 0: + line_end = len(source) + last = start_index + while last + 1 < len(tokens) and tokens[last + 1].start < line_end: + last += 1 + return last, False + + +def transform_imports(source: str) -> str: + tokens = significant(source) + edits: list[Edit] = [] + for index, token in enumerate(tokens): + if token.text != "import": + continue + if ( + index + 1 >= len(tokens) + or tokens[index + 1].text in {"{", "*"} + ): + continue + end = index + 1 + while end < len(tokens) and tokens[end].text != ";": + end += 1 + if end >= len(tokens): + continue + + dot_brace = None + stack: list[str] = [] + for cursor in range(index + 1, end): + text = tokens[cursor].text + if text in {"(", "[", "<"}: + stack.append({"(": ")", "[": "]", "<": ">"}[text]) + elif stack and text == stack[-1]: + stack.pop() + elif ( + not stack + and text == "." + and cursor + 1 < end + and tokens[cursor + 1].text == "{" + ): + dot_brace = cursor + break + + if dot_brace is not None: + brace = dot_brace + 1 + close = matching_token(tokens, brace) + if close is None or close > end: + continue + path = source[tokens[index + 1].start : tokens[dot_brace].start].strip() + selection = source[tokens[brace].start : tokens[close].end] + tail = source[tokens[close].end : tokens[end].start].strip() + replacement = f"import {selection} from {path}" + if tail: + replacement += f" {tail}" + replacement += ";" + edits.append(Edit(token.start, tokens[end].end, replacement)) + continue + + alias = None + for cursor in range(index + 1, end): + if tokens[cursor].text == "as": + alias = cursor + break + if alias is not None and alias + 1 < end: + path = source[tokens[index + 1].start : tokens[alias].start].strip() + alias_name = tokens[alias + 1].text + replacement = f"import * as {alias_name} from {path};" + edits.append(Edit(token.start, tokens[end].end, replacement)) + return apply_edits(source, edits) + + +def transform_pragmas(source: str) -> str: + replacements = { + ("no", "-", "coverage", "-", "condition"): "solcore noCoverageCondition", + ("no", "-", "patterson", "-", "condition"): "solcore noPattersonCondition", + ( + "no", + "-", + "bounded", + "-", + "variable", + "-", + "condition", + ): "solcore noBoundVariableCondition", + ( + "no", + "-", + "generic", + "-", + "instance", + "-", + "for", + ): "solcore noGenericInstanceFor", + } + tokens = significant(source) + edits: list[Edit] = [] + for index, token in enumerate(tokens): + if token.text != "pragma": + continue + for parts, replacement in replacements.items(): + candidate = tuple( + item.text for item in tokens[index + 1 : index + 1 + len(parts)] + ) + if candidate == parts: + edits.append( + Edit( + tokens[index + 1].start, + tokens[index + len(parts)].end, + replacement, + ) + ) + break + return apply_edits(source, edits) + + +def transform_data_declarations(source: str) -> str: + tokens = significant(source) + yul = assembly_token_indexes(tokens) + scopes: list[tuple[str, ...]] = [] + scope_stack: list[str] = [] + for token_index, token in enumerate(tokens): + scopes.append(tuple(scope_stack)) + if token.text == "{": + scope_stack.append(brace_header_kind(tokens, token_index)) + elif token.text == "}" and scope_stack: + scope_stack.pop() + + edits: list[Edit] = [] + index = 0 + while index < len(tokens): + if ( + tokens[index].text != "data" + or index in yul + or index + 1 >= len(tokens) + or tokens[index + 1].kind != "ident" + ): + index += 1 + continue + + # ``data`` is no longer reserved, so only recognize the old + # declaration at top level or directly inside a contract. In + # particular, do not reinterpret parameters, fields, locals, member + # access, or Yul identifiers named ``data``. + scope = scopes[index] + if scope not in {(), ("contract",)}: + index += 1 + continue + at_declaration_boundary = index == 0 + if index > 0: + previous = tokens[index - 1] + gap = source[previous.end : tokens[index].start] + at_declaration_boundary = ( + previous.text in {"{", "}", ";"} or "\n" in gap + ) + if not at_declaration_boundary: + index += 1 + continue + + start = index + name = tokens[index + 1].text + cursor = index + 2 + params: list[str] = [] + if cursor < len(tokens) and tokens[cursor].text in {"(", "[", "<"}: + close = matching_token(tokens, cursor) + if close is None: + index += 1 + continue + parsed = TypeParser(tokens).parse_type_list(cursor + 1, close) + if parsed is None: + index += 1 + continue + params = parsed + cursor = close + 1 + + if ( + cursor >= len(tokens) + or tokens[cursor].text not in {"=", ";"} + ): + index += 1 + continue + + end_index, had_semicolon = declaration_end(source, tokens, cursor) + equals = None + for probe in range(cursor, end_index + 1): + if tokens[probe].text == "=": + equals = probe + break + + constructors: list[str] = [] + if equals is not None: + constructor_end = end_index if had_semicolon else end_index + 1 + for left, right in split_top_level(tokens, equals + 1, constructor_end, "|"): + if left >= right or tokens[left].kind != "ident": + continue + constructor = tokens[left].text + payload_index = left + 1 + if payload_index < right and tokens[payload_index].text == "(": + close = matching_token(tokens, payload_index) + if close is None or close >= right + 1: + continue + payload = TypeParser(tokens).parse_type_list( + payload_index + 1, close + ) + if payload is None: + continue + constructor += f"({', '.join(payload)})" + constructors.append(constructor) + + params_text = f"<{', '.join(params)}>" if params else "" + replacement = f"enum {name}{params_text} {{" + if constructors: + replacement += f" {', '.join(constructors)} " + replacement += "}" + replacement = ( + preserved_comments( + source, tokens[start].start, tokens[end_index].end + ) + + replacement + ) + edits.append( + Edit(tokens[start].start, tokens[end_index].end, replacement) + ) + index = end_index + 1 + return apply_edits(source, edits) + + +def transform_type_declarations(source: str) -> str: + tokens = significant(source) + edits: list[Edit] = [] + index = 0 + while index < len(tokens): + if tokens[index].text != "type" or index + 1 >= len(tokens): + index += 1 + continue + start = index + name = tokens[index + 1].text + cursor = index + 2 + params: list[str] = [] + if cursor < len(tokens) and tokens[cursor].text in {"(", "[", "<"}: + close = matching_token(tokens, cursor) + if close is None: + index += 1 + continue + parsed = TypeParser(tokens).parse_type_list(cursor + 1, close) + if parsed is None: + index += 1 + continue + params = parsed + cursor = close + 1 + if cursor >= len(tokens) or tokens[cursor].text not in {"=", "is"}: + index += 1 + continue + end_index, _ = declaration_end(source, tokens, cursor + 1) + rhs_end = end_index if tokens[end_index].text == ";" else end_index + 1 + parsed_rhs = TypeParser(tokens).parse(cursor + 1) + if parsed_rhs is None or parsed_rhs[1] != rhs_end: + index += 1 + continue + params_text = f"<{', '.join(params)}>" if params else "" + replacement = f"type {name}{params_text} is {parsed_rhs[0]};" + replacement = ( + preserved_comments( + source, tokens[start].start, tokens[end_index].end + ) + + replacement + ) + edits.append( + Edit(tokens[start].start, tokens[end_index].end, replacement) + ) + index = end_index + 1 + return apply_edits(source, edits) + + +@dataclasses.dataclass(frozen=True) +class SignaturePrefix: + start: int + end: int + variables: tuple[str, ...] + context: str + + +def declaration_boundary(tokens: Sequence[Token], index: int) -> int: + cursor = index - 1 + while cursor >= 0: + if tokens[cursor].text in {"{", "}", ";"}: + return cursor + 1 + cursor -= 1 + return 0 + + +def infer_constraint_variables(context: str) -> tuple[str, ...]: + concrete = { + "word", + "bool", + "integer", + "string", + "bytes", + "address", + "memory", + "storage", + "calldata", + "returndata", + "mapping", + "array", + "pair", + "sum", + "function", + "comptime", + } + result: list[str] = [] + tokens = significant(context) + for index, token in enumerate(tokens): + if token.kind != "ident" or token.text in concrete: + continue + if token.text[0].isupper(): + continue + if index > 0 and tokens[index - 1].text in {":", "."}: + continue + if token.text not in result: + result.append(token.text) + return tuple(result) + + +def find_signature_prefix( + source: str, tokens: Sequence[Token], keyword_index: int +) -> SignaturePrefix | None: + boundary = declaration_boundary(tokens, keyword_index) + forall = None + for index in range(boundary, keyword_index): + if tokens[index].text == "forall": + forall = index + break + if forall is None: + return None + + dot = None + stack: list[str] = [] + pairs = {"(": ")", "[": "]", "<": ">"} + for index in range(forall + 1, keyword_index): + text = tokens[index].text + if text in pairs: + stack.append(pairs[text]) + elif stack and text == stack[-1]: + stack.pop() + elif not stack and text == ".": + dot = index + break + if dot is None: + return None + + arrow = None + stack.clear() + for index in range(dot + 1, keyword_index): + text = tokens[index].text + if text in pairs: + stack.append(pairs[text]) + elif stack and text == stack[-1]: + stack.pop() + elif not stack and text == "=>": + arrow = index + break + + before = tokens[forall + 1 : dot] + ordinary_vars = all( + token.kind == "ident" or token.text == "," for token in before + ) + if ordinary_vars: + variables = tuple( + token.text for token in before if token.kind == "ident" + ) + context = ( + source[tokens[dot + 1].start : tokens[arrow].start].strip() + if arrow is not None and dot + 1 < arrow + else "" + ) + else: + context_start = tokens[forall + 1].start + context_end = tokens[dot].start + first_context = source[context_start:context_end].strip() + second_context = ( + source[tokens[dot + 1].start : tokens[arrow].start].strip() + if arrow is not None and dot + 1 < arrow + else "" + ) + context = ", ".join( + fragment for fragment in (first_context, second_context) if fragment + ) + variables = infer_constraint_variables(context) + + end_index = arrow if arrow is not None else dot + return SignaturePrefix( + start=tokens[forall].start, + end=tokens[end_index].end, + variables=variables, + context=normalize_predicate_list(context) if context else "", + ) + + +def parse_qualified_name( + tokens: Sequence[Token], index: int +) -> tuple[str, int] | None: + if index >= len(tokens) or tokens[index].kind != "ident": + return None + parts = [tokens[index].text] + index += 1 + while ( + index + 1 < len(tokens) + and tokens[index].text == "." + and tokens[index + 1].kind == "ident" + ): + parts.extend((".", tokens[index + 1].text)) + index += 2 + return "".join(parts), index + + +def transform_traits_and_impls(source: str) -> str: + tokens = significant(source) + edits: list[Edit] = [] + for keyword_index, token in enumerate(tokens): + if token.text not in {"class", "instance"}: + continue + is_trait = token.text == "class" + prefix = find_signature_prefix(source, tokens, keyword_index) + start_offset = prefix.start if prefix else token.start + variables = list(prefix.variables if prefix else ()) + contexts: list[str] = [prefix.context] if prefix and prefix.context else [] + + if ( + not is_trait + and prefix is None + and keyword_index > 0 + and tokens[keyword_index - 1].text == "default" + ): + start_offset = tokens[keyword_index - 1].start + + cursor = keyword_index + 1 + if ( + not is_trait + and cursor < len(tokens) + and tokens[cursor].text == "(" + ): + close = matching_token(tokens, cursor) + if ( + close is not None + and close + 1 < len(tokens) + and tokens[close + 1].text == "=>" + ): + context_text = source[ + tokens[cursor + 1].start : tokens[close].start + ] + contexts.append(normalize_predicate_list(context_text)) + cursor = close + 2 + + parsed_subject = TypeParser(tokens).parse(cursor) + if parsed_subject is None: + continue + subject, cursor = parsed_subject + if cursor >= len(tokens) or tokens[cursor].text != ":": + continue + parsed_name = parse_qualified_name(tokens, cursor + 1) + if parsed_name is None: + continue + class_name, cursor = parsed_name + + params: list[str] = [] + if cursor < len(tokens) and tokens[cursor].text in {"(", "[", "<"}: + close = matching_token(tokens, cursor) + if close is None: + continue + parsed_params = TypeParser(tokens).parse_type_list(cursor + 1, close) + if parsed_params is None: + continue + params = parsed_params + cursor = close + 1 + + while cursor < len(tokens) and tokens[cursor].text not in {"{", ";"}: + cursor += 1 + if cursor >= len(tokens): + continue + + all_args = [subject, *params] + args_text = f"<{', '.join(all_args)}>" + if is_trait: + header = f"trait {class_name}{args_text}" + else: + default = ( + keyword_index > 0 and tokens[keyword_index - 1].text == "default" + ) + generic_text = f"<{', '.join(variables)}>" if variables else "" + header = f"{'default ' if default else ''}impl{generic_text} {class_name}{args_text}" + context = ", ".join(part for part in contexts if part) + if context: + header += f" where {context}" + header += " " + header = preserved_comments(source, start_offset, tokens[cursor].start) + header + edits.append(Edit(start_offset, tokens[cursor].start, header)) + return apply_edits(source, edits) + + +def function_header_end(tokens: Sequence[Token], index: int) -> int | None: + stack: list[str] = [] + pairs = {"(": ")", "[": "]", "<": ">"} + for cursor in range(index, len(tokens)): + text = tokens[cursor].text + if text in pairs: + stack.append(pairs[text]) + elif stack and text == stack[-1]: + stack.pop() + elif not stack and text in {"{", ";"}: + return cursor + return None + + +def transform_functions(source: str) -> str: + tokens = significant(source) + yul = assembly_token_indexes(tokens) + edits: list[Edit] = [] + for keyword_index, token in enumerate(tokens): + if token.text not in {"function", "constructor", "fallback", "lam"}: + continue + if keyword_index in yul: + continue + keyword = token.text + prefix = ( + find_signature_prefix(source, tokens, keyword_index) + if keyword == "function" + else None + ) + variables = list(prefix.variables if prefix else ()) + context = prefix.context if prefix else "" + if prefix is not None: + prefix_end = prefix.end + first_header_token = keyword_index + while ( + first_header_token > 0 + and tokens[first_header_token - 1].text in MODIFIERS + ): + first_header_token -= 1 + gap_end = tokens[first_header_token].start + if source[prefix.end:gap_end].strip() == "": + prefix_end = gap_end + edits.append( + Edit( + prefix.start, + prefix_end, + preserved_comments(source, prefix.start, prefix_end), + ) + ) + + modifiers: list[str] = [] + cursor = keyword_index - 1 + while cursor >= 0 and tokens[cursor].text in MODIFIERS: + modifiers.insert(0, tokens[cursor].text) + cursor -= 1 + if modifiers: + modifier_start = cursor + 1 + edits.append( + Edit( + tokens[modifier_start].start, + token.start, + preserved_comments( + source, tokens[modifier_start].start, token.start + ), + ) + ) + + if keyword == "function": + if keyword_index + 1 >= len(tokens): + continue + name_index = keyword_index + 1 + cursor = name_index + 1 + if ( + cursor < len(tokens) + and tokens[cursor].text == "<" + and matching_token(tokens, cursor) is not None + ): + cursor = matching_token(tokens, cursor) + 1 # type: ignore[operator] + if variables and ( + name_index + 1 >= len(tokens) + or tokens[name_index + 1].text != "<" + ): + edits.append( + Edit( + tokens[name_index].end, + tokens[name_index].end, + f"<{', '.join(variables)}>", + ) + ) + else: + cursor = keyword_index + 1 + + if cursor >= len(tokens) or tokens[cursor].text != "(": + continue + close = matching_token(tokens, cursor) + if close is None: + continue + header_end = function_header_end(tokens, close + 1) + if header_end is None: + continue + + existing_postfix_modifiers = { + tokens[probe].text + for probe in range(close + 1, header_end) + if tokens[probe].text in MODIFIERS + } + if ( + keyword == "fallback" + and "external" not in modifiers + and "external" not in existing_postfix_modifiers + ): + modifiers.insert(0, "external") + if modifiers: + edits.append( + Edit( + tokens[close].end, + tokens[close].end, + " " + " ".join(modifiers), + ) + ) + + arrow = None + for probe in range(close + 1, header_end): + if tokens[probe].text == "->": + arrow = probe + break + if arrow is not None: + type_start = arrow + 1 + return_comptime = False + if ( + type_start < header_end + and tokens[type_start].text == "comptime" + ): + return_comptime = True + type_start += 1 + parsed_return = TypeParser(tokens).parse(type_start) + if parsed_return is not None: + return_type, return_end = parsed_return + if return_end <= header_end: + if keyword == "fallback" and return_type == "()": + replacement = "" + else: + comptime = "comptime " if return_comptime else "" + replacement = f"returns ({comptime}{return_type})" + edits.append( + Edit( + tokens[arrow].start, + tokens[return_end - 1].end, + replacement, + ) + ) + + if context: + edits.append( + Edit( + tokens[header_end].start, + tokens[header_end].start, + f" where {context} ", + ) + ) + return apply_edits(source, edits) + + +def transform_let_comptime(source: str) -> str: + tokens = significant(source) + edits: list[Edit] = [] + for index, token in enumerate(tokens): + if ( + token.text == "let" + and index + 3 < len(tokens) + and tokens[index + 1].kind == "ident" + and tokens[index + 2].text == ":" + and tokens[index + 3].text == "comptime" + ): + edits.append( + Edit(token.end, token.end, " comptime") + ) + edits.append( + Edit(tokens[index + 3].start, tokens[index + 3].end, "") + ) + return apply_edits(source, edits) + + +def transform_return_unit(source: str) -> str: + tokens = significant(source) + edits: list[Edit] = [] + for index, token in enumerate(tokens): + if ( + token.text == "return" + and index + 3 < len(tokens) + and tokens[index + 1].text == "(" + and tokens[index + 2].text == ")" + and tokens[index + 3].text == ";" + ): + edits.append( + Edit(token.end, tokens[index + 2].end, "") + ) + return apply_edits(source, edits) + + +def transform_matches(source: str) -> str: + tokens = significant(source) + edits: list[Edit] = [] + brace_depth: list[int] = [0] * len(tokens) + depth = 0 + for index, token in enumerate(tokens): + brace_depth[index] = depth + if token.text == "{": + depth += 1 + elif token.text == "}": + depth -= 1 + + for match_index, token in enumerate(tokens): + if token.text != "match": + continue + open_brace = None + stack: list[str] = [] + pairs = {"(": ")", "[": "]", "<": ">"} + for cursor in range(match_index + 1, len(tokens)): + text = tokens[cursor].text + if text in pairs: + stack.append(pairs[text]) + elif stack and text == stack[-1]: + stack.pop() + elif not stack and text == "{": + open_brace = cursor + break + elif not stack and text == ";": + break + if open_brace is None: + continue + close_brace = matching_token(tokens, open_brace) + if close_brace is None: + continue + + head_start = match_index + 1 + already_parenthesized = ( + head_start < open_brace + and tokens[head_start].text == "(" + and matching_token(tokens, head_start) == open_brace - 1 + ) + if not already_parenthesized: + edits.append( + Edit(tokens[head_start].start, tokens[head_start].start, "(") + ) + edits.append( + Edit(tokens[open_brace].start, tokens[open_brace].start, ") ") + ) + + arm_bars: list[int] = [] + paren_depth = bracket_depth = 0 + for cursor in range(open_brace + 1, close_brace): + text = tokens[cursor].text + if text == "(": + paren_depth += 1 + elif text == ")": + paren_depth -= 1 + elif text == "[": + bracket_depth += 1 + elif text == "]": + bracket_depth -= 1 + elif ( + text == "|" + and paren_depth == 0 + and bracket_depth == 0 + and brace_depth[cursor] == brace_depth[open_brace] + 1 + ): + arm_bars.append(cursor) + if not arm_bars: + continue + + # In the old grammar, ``match (a, b)`` could mean matching one tuple + # expression, while new syntax uses that spelling for two scrutinees. + # A single old arm pattern disambiguates the former; retain it by + # adding one more pair of parentheses. + if already_parenthesized: + first_bar = arm_bars[0] + first_arm_end = ( + arm_bars[1] if len(arm_bars) > 1 else close_brace + ) + first_arrow = next( + ( + cursor + for cursor in range(first_bar + 1, first_arm_end) + if tokens[cursor].text == "=>" + ), + None, + ) + head_close = matching_token(tokens, head_start) + if first_arrow is not None and head_close is not None: + head_parts = split_top_level( + tokens, head_start + 1, head_close + ) + pattern_parts = split_top_level( + tokens, first_bar + 1, first_arrow + ) + if len(head_parts) > 1 and len(pattern_parts) == 1: + edits.append( + Edit( + tokens[head_start].start, + tokens[head_start].start, + "(", + ) + ) + edits.append( + Edit( + tokens[head_close].end, + tokens[head_close].end, + ")", + ) + ) + + for position, bar in enumerate(arm_bars): + arm_end = ( + arm_bars[position + 1] + if position + 1 < len(arm_bars) + else close_brace + ) + arrow = None + stack = [] + for cursor in range(bar + 1, arm_end): + text = tokens[cursor].text + if text in {"(", "[", "<"}: + stack.append({"(": ")", "[": "]", "<": ">"}[text]) + elif stack and text == stack[-1]: + stack.pop() + elif not stack and text == "=>": + arrow = cursor + break + if arrow is None: + continue + + pattern_ranges = split_top_level(tokens, bar + 1, arrow) + wildcard_default = bool(pattern_ranges) and all( + right == left + 1 and tokens[left].text == "_" + for left, right in pattern_ranges + ) + if wildcard_default: + edits.append( + Edit(tokens[bar].start, tokens[arrow].end, "default {") + ) + else: + edits.append(Edit(tokens[bar].start, tokens[bar].end, "case")) + if len(pattern_ranges) > 1: + edits.append( + Edit( + tokens[bar + 1].start, + tokens[bar + 1].start, + "(", + ) + ) + edits.append( + Edit(tokens[arrow].start, tokens[arrow].start, ") ") + ) + edits.append( + Edit(tokens[arrow].start, tokens[arrow].end, "{") + ) + edits.append( + Edit(tokens[arm_end].start, tokens[arm_end].start, "} ") + ) + return apply_edits(source, edits) + + +def ternary_colons(tokens: Sequence[Token]) -> set[int]: + questions: dict[tuple[int, int, int], list[int]] = {} + paren = bracket = brace = 0 + result: set[int] = set() + for index, token in enumerate(tokens): + text = token.text + scope = (paren, bracket, brace) + if text == "?": + questions.setdefault(scope, []).append(index) + elif text == ":" and questions.get(scope): + questions[scope].pop() + result.add(index) + if text == "(": + paren += 1 + elif text == ")": + paren -= 1 + elif text == "[": + bracket += 1 + elif text == "]": + bracket -= 1 + elif text == "{": + brace += 1 + elif text == "}": + brace -= 1 + return result + + +def transform_types_in_colon_positions(source: str) -> str: + tokens = significant(source) + parser = TypeParser(tokens) + ternary = ternary_colons(tokens) + edits: list[Edit] = [] + for index, token in enumerate(tokens): + if token.text != ":" or index in ternary or index + 1 >= len(tokens): + continue + parsed = parser.parse(index + 1) + if parsed is None: + continue + rendered, end = parsed + if end <= index + 1: + continue + old = source[tokens[index + 1].start : tokens[end - 1].end] + if old.strip() != rendered: + edits.append( + Edit(tokens[index + 1].start, tokens[end - 1].end, rendered) + ) + + return apply_edits(source, edits) + + +def transform_proxy_expressions(source: str) -> str: + tokens = significant(source) + parser = TypeParser(tokens) + edits: list[Edit] = [] + for index, token in enumerate(tokens): + if token.text != "@" or index + 1 >= len(tokens): + continue + boundary = declaration_boundary(tokens, index) + if any( + candidate.text == "import" + for candidate in tokens[boundary:index] + ): + continue + parsed = parser.parse(index + 1) + if parsed is None: + continue + rendered, end = parsed + edits.append( + Edit( + token.start, + tokens[end - 1].end, + f"Proxy as Proxy<{rendered}>", + ) + ) + return apply_edits(source, edits) + + +def protected_annotation_colons(tokens: Sequence[Token]) -> set[int]: + protected = set(ternary_colons(tokens)) + + # Function, constructor, fallback, and lambda parameter declarations. + for index, token in enumerate(tokens): + if token.text not in {"function", "constructor", "fallback", "lam"}: + continue + cursor = index + 1 + if token.text == "function": + cursor += 1 + if cursor < len(tokens) and tokens[cursor].text == "<": + close = matching_token(tokens, cursor) + if close is not None: + cursor = close + 1 + if cursor < len(tokens) and tokens[cursor].text == "(": + close = matching_token(tokens, cursor) + if close is not None: + for probe in range(cursor + 1, close): + if tokens[probe].text == ":": + protected.add(probe) + + # Named return items, including ``comptime name: Type``. A returns clause + # is entirely a type/declaration context, so none of its colons denote the + # removed expression-annotation syntax. + for index, token in enumerate(tokens): + if ( + token.text != "returns" + or index + 1 >= len(tokens) + or tokens[index + 1].text != "(" + ): + continue + close = matching_token(tokens, index + 1) + if close is not None: + for probe in range(index + 2, close): + if tokens[probe].text == ":": + protected.add(probe) + + # Let binding annotations. The complete (possibly nested) binding pattern + # precedes the colon. + for index, token in enumerate(tokens): + if token.text != "let": + continue + cursor = index + 1 + if cursor < len(tokens) and tokens[cursor].text == "comptime": + cursor += 1 + if cursor >= len(tokens): + continue + if tokens[cursor].text == "(": + close = matching_token(tokens, cursor) + if close is None: + continue + annotation = close + 1 + elif tokens[cursor].kind == "ident" or tokens[cursor].text == "_": + annotation = cursor + 1 + else: + continue + if ( + annotation < len(tokens) + and tokens[annotation].text == ":" + ): + protected.add(annotation) + + # Trait/impl/function constraints. + for index, token in enumerate(tokens): + if token.text != "where": + continue + cursor = index + 1 + stack: list[str] = [] + while cursor < len(tokens): + text = tokens[cursor].text + if text in {"(", "[", "<"}: + stack.append({"(": ")", "[": "]", "<": ">"}[text]) + elif stack and text == stack[-1]: + stack.pop() + elif not stack and text in {"{", ";"}: + break + elif text == ":": + protected.add(cursor) + cursor += 1 + + # Name-first fields and deliberately incomplete name-first declarations. + for index, token in enumerate(tokens): + if token.text != ":" or index == 0: + continue + if tokens[index - 1].kind != "ident": + continue + before = tokens[index - 2].text if index >= 2 else None + if before in {"{", "}", ";"}: + protected.add(index) + + return protected + + +def transform_expression_annotations(source: str) -> str: + tokens = significant(source) + protected = protected_annotation_colons(tokens) + edits: list[Edit] = [] + for index, token in enumerate(tokens): + if token.text == ":" and index not in protected: + edits.append(Edit(token.start, token.end, " as ")) + return apply_edits(source, edits) + + +def normalize_as_spacing(source: str) -> str: + tokens = significant(source) + edits: list[Edit] = [] + for index, token in enumerate(tokens): + if token.text != "as" or index == 0 or index + 1 >= len(tokens): + continue + before = source[tokens[index - 1].end : token.start] + after = source[token.end : tokens[index + 1].start] + if before.strip() == "" and "\n" not in before and before != " ": + edits.append(Edit(tokens[index - 1].end, token.start, " ")) + if after.strip() == "" and "\n" not in after and after != " ": + edits.append(Edit(token.end, tokens[index + 1].start, " ")) + return apply_edits(source, edits) + + +def brace_header_kind(tokens: Sequence[Token], open_brace: int) -> str: + boundary = declaration_boundary(tokens, open_brace) + header = {token.text for token in tokens[boundary:open_brace]} + for kind in ( + "assembly", + "enum", + "export", + "import", + "hiding", + "struct", + "contract", + "trait", + "impl", + "function", + "constructor", + "fallback", + "lam", + "if", + "for", + "while", + "unchecked", + "case", + "default", + "match", + ): + if kind in header: + return kind + return "block" + + +def remove_block_statement_semicolons(source: str) -> str: + """Drop the old optional semicolon after a braced statement.""" + + tokens = significant(source) + edits: list[Edit] = [] + for close, token in enumerate(tokens): + if ( + token.text != "}" + or close + 1 >= len(tokens) + or tokens[close + 1].text != ";" + ): + continue + open_brace = None + depth = 0 + for probe in range(close, -1, -1): + if tokens[probe].text == "}": + depth += 1 + elif tokens[probe].text == "{": + depth -= 1 + if depth == 0: + open_brace = probe + break + if open_brace is None: + continue + if brace_header_kind(tokens, open_brace) in { + "assembly", + "match", + "if", + "for", + "while", + "unchecked", + }: + edits.append( + Edit(tokens[close + 1].start, tokens[close + 1].end, "") + ) + return apply_edits(source, edits) + + +def terminate_trailing_expression_statements(source: str) -> str: + """Add the semicolon required by the new statement grammar. + + The old parser accepted a bare final expression in a body. Limiting the + edit to the token immediately before a non-declaration closing brace keeps + enum members, contract members, comments, and Yul assembly untouched. + """ + + tokens = significant(source) + edits: list[Edit] = [] + expression_end = {")", "]"} + yul = assembly_token_indexes(tokens) + + for close, token in enumerate(tokens): + if token.text != "}" or close in yul: + continue + open_brace = None + depth = 0 + for probe in range(close, -1, -1): + if tokens[probe].text == "}": + depth += 1 + elif tokens[probe].text == "{": + depth -= 1 + if depth == 0: + open_brace = probe + break + if open_brace is None or close == open_brace + 1: + continue + kind = brace_header_kind(tokens, open_brace) + if kind in { + "assembly", + "enum", + "export", + "import", + "hiding", + "struct", + "contract", + "trait", + "impl", + "match", + }: + continue + previous = tokens[close - 1] + if ( + previous.kind in {"ident", "number", "string"} + or previous.text in expression_end + ): + top_level_tokens: list[Token] = [] + nested_braces = 0 + for probe in range(open_brace + 1, close): + candidate = tokens[probe] + if candidate.text == "{": + nested_braces += 1 + elif candidate.text == "}": + nested_braces -= 1 + elif nested_braces == 0: + top_level_tokens.append(candidate) + is_single_function_expression = ( + kind in {"function", "fallback"} + and ";" not in {candidate.text for candidate in top_level_tokens} + and not { + "return", + "let", + "if", + "for", + "match", + "assembly", + "break", + "continue", + } + .intersection(candidate.text for candidate in top_level_tokens) + ) + if is_single_function_expression and top_level_tokens: + if ( + len(top_level_tokens) == 2 + and top_level_tokens[0].text == "(" + and top_level_tokens[1].text == ")" + ): + edits.append( + Edit( + top_level_tokens[0].start, + top_level_tokens[1].end, + "return;", + ) + ) + else: + edits.append( + Edit( + top_level_tokens[0].start, + top_level_tokens[0].start, + "return ", + ) + ) + edits.append(Edit(previous.end, previous.end, ";")) + else: + edits.append(Edit(previous.end, previous.end, ";")) + return apply_edits(source, edits) + + +def terminate_semicolonless_call_statements(source: str) -> str: + """Terminate old call statements that are followed by another statement. + + The old grammar made the semicolon on an expression statement optional. + ``terminate_trailing_expression_statements`` handles a final expression + immediately before ``}``; this pass handles the remaining common form: + an outermost call followed by the next statement on a later line. + """ + + tokens = significant(source) + yul = assembly_token_indexes(tokens) + edits: list[Edit] = [] + header_words = frozenset( + { + "case", + "constructor", + "enum", + "fallback", + "for", + "function", + "if", + "impl", + "match", + "trait", + "while", + } + ) + continuation_tokens = frozenset( + { + ")", + "]", + "}", + ",", + ".", + ";", + "+", + "-", + "*", + "/", + "%", + "**", + "<<", + ">>", + "<", + ">", + "<=", + ">=", + "==", + "!=", + "&&", + "||", + "&", + "|", + "^", + "?", + ":", + "as", + } + ) + + for close, token in enumerate(tokens[:-1]): + if token.text != ")" or close in yul: + continue + following = tokens[close + 1] + gap = source[token.end : following.start] + if "\n" not in gap or following.text in continuation_tokens: + continue + + boundary = close - 1 + paren_depth = 1 + while boundary >= 0: + text = tokens[boundary].text + if text == ")": + paren_depth += 1 + elif text == "(": + paren_depth -= 1 + if paren_depth == 0: + break + boundary -= 1 + if boundary < 1: + continue + + statement_start = declaration_boundary(tokens, boundary) + statement_tokens = tokens[statement_start : close + 1] + if not statement_tokens: + continue + if any(candidate.text in header_words for candidate in statement_tokens): + continue + if statement_tokens[0].kind != "ident": + continue + + edits.append(Edit(token.end, token.end, ";")) + + return apply_edits(source, edits) + + +def _parse_expression_segment( + tokens: Sequence[Token], + start: int, + stops: frozenset[str], + records: dict[int, tuple[int, int, int]], +) -> int: + index = start + while index < len(tokens): + text = tokens[index].text + if text in stops: + return index + if text == "if": + parsed = _parse_if_expression(tokens, index, stops, records) + if parsed is None: + index += 1 + else: + index = parsed + continue + if text in {"(", "["}: + close = matching_token(tokens, index) + if close is None: + return index + _parse_expression_segment( + tokens, + index + 1, + frozenset({tokens[close].text}), + records, + ) + index = close + 1 + continue + index += 1 + return index + + +def _parse_if_expression( + tokens: Sequence[Token], + index: int, + outer_stops: frozenset[str], + records: dict[int, tuple[int, int, int]], +) -> int | None: + then = _parse_expression_segment( + tokens, index + 1, frozenset({"then"}), records + ) + if then >= len(tokens) or tokens[then].text != "then": + return None + otherwise = _parse_expression_segment( + tokens, then + 1, frozenset({"else"}), records + ) + if otherwise >= len(tokens) or tokens[otherwise].text != "else": + return None + end = _parse_expression_segment(tokens, otherwise + 1, outer_stops, records) + records[index] = (then, otherwise, end) + return end + + +def transform_if_expressions(source: str) -> str: + tokens = significant(source) + records: dict[int, tuple[int, int, int]] = {} + consumed_until = 0 + for index, token in enumerate(tokens): + if index < consumed_until or token.text != "if": + continue + end = _parse_if_expression( + tokens, + index, + frozenset({";", "}", ",", ")"}), + records, + ) + if end is not None: + consumed_until = max(consumed_until, end) + + edits: list[Edit] = [] + for start, (then, otherwise, end) in records.items(): + edits.append(Edit(tokens[start].start, tokens[start].end, "(")) + edits.append(Edit(tokens[then].start, tokens[then].end, "?")) + edits.append(Edit(tokens[otherwise].start, tokens[otherwise].end, ":")) + end_offset = tokens[end].start if end < len(tokens) else len(source) + edits.append(Edit(end_offset, end_offset, ")")) + return apply_edits(source, edits) + + +def parenthesize_control_conditions(source: str) -> str: + """Parenthesize old ``if cond`` / ``while cond`` statements.""" + + tokens = significant(source) + yul = assembly_token_indexes(tokens) + edits: list[Edit] = [] + for index, token in enumerate(tokens): + if token.text not in {"if", "while"} or index in yul: + continue + condition_start = index + 1 + if ( + condition_start >= len(tokens) + or tokens[condition_start].text == "(" + ): + continue + stack: list[str] = [] + open_body = None + for cursor in range(condition_start, len(tokens)): + text = tokens[cursor].text + if text in {"(", "["}: + stack.append({"(": ")", "[": "]"}[text]) + elif stack and text == stack[-1]: + stack.pop() + elif not stack and text == "{": + open_body = cursor + break + elif not stack and text in {";", "}"}: + break + if open_body is None: + continue + edits.append( + Edit( + tokens[condition_start].start, + tokens[condition_start].start, + "(", + ) + ) + edits.append( + Edit(tokens[open_body].start, tokens[open_body].start, ") ") + ) + return apply_edits(source, edits) + + +def transform_legacy_user_operators(source: str) -> str: + """Lower the removed infix declaration form to ordinary helper calls. + + The tracked corpus has one such declaration and one simple use. Keep this + deliberately narrow rather than retaining an unspecified operator grammar. + """ + + tokens = significant(source) + declarations: list[tuple[int, int, tuple[str, ...], str]] = [] + for index, token in enumerate(tokens): + if token.text not in {"infixl", "infixr", "infix"}: + continue + end = index + 1 + while end < len(tokens) and tokens[end].text != ";": + end += 1 + if end >= len(tokens): + continue + open_paren = next( + ( + cursor + for cursor in range(index + 1, end) + if tokens[cursor].text == "(" + ), + None, + ) + arrow = next( + ( + cursor + for cursor in range(index + 1, end) + if tokens[cursor].text == "=>" + ), + None, + ) + if open_paren is None or arrow is None: + continue + close_paren = matching_token(tokens, open_paren) + if ( + close_paren is None + or close_paren >= arrow + or arrow + 1 >= end + or tokens[arrow + 1].kind != "ident" + ): + continue + operator = tuple( + candidate.text + for candidate in tokens[open_paren + 1 : close_paren] + ) + if not operator: + continue + declarations.append( + (index, end, operator, tokens[arrow + 1].text) + ) + + if not declarations: + return source + + edits: list[Edit] = [] + declaration_indexes: set[int] = set() + for start, end, _, _ in declarations: + declaration_indexes.update(range(start, end + 1)) + line_start = source.rfind("\n", 0, tokens[start].start) + 1 + line_end = source.find("\n", tokens[end].end) + if line_end < 0: + line_end = len(source) + else: + line_end += 1 + edits.append(Edit(line_start, line_end, "")) + + for _, _, operator, helper in declarations: + width = len(operator) + for index in range(1, len(tokens) - width): + if index in declaration_indexes: + continue + if tuple( + candidate.text + for candidate in tokens[index : index + width] + ) != operator: + continue + left = tokens[index - 1] + right_index = index + width + right = tokens[right_index] + if ( + left.kind not in {"ident", "number"} + or right.kind not in {"ident", "number"} + ): + continue + edits.append( + Edit( + left.start, + right.end, + f"{helper}({left.text}, {right.text})", + ) + ) + return apply_edits(source, edits) + + +def remove_yul_semicolons(source: str) -> str: + """Yul statements are whitespace-delimited; ``;`` is not valid there.""" + + tokens = significant(source) + yul = assembly_token_indexes(tokens) + edits = [ + Edit(token.start, token.end, "") + for index, token in enumerate(tokens) + if index in yul and token.text == ";" + ] + return apply_edits(source, edits) + + +def migrate_source(source: str) -> str: + passes = ( + transform_imports, + transform_pragmas, + transform_data_declarations, + transform_type_declarations, + transform_traits_and_impls, + transform_functions, + transform_let_comptime, + transform_return_unit, + transform_matches, + transform_if_expressions, + parenthesize_control_conditions, + transform_legacy_user_operators, + transform_types_in_colon_positions, + transform_proxy_expressions, + transform_expression_annotations, + normalize_as_spacing, + remove_yul_semicolons, + remove_block_statement_semicolons, + terminate_semicolonless_call_statements, + terminate_trailing_expression_statements, + ) + migrated = source + for migration_pass in passes: + migrated = migration_pass(migrated) + return migrated + + +def apply_file_fixups(relative: pathlib.Path, source: str) -> str: + fixed = source + for old, new in FILE_FIXUPS.get(relative, ()): + fixed = fixed.replace(old, new) + return fixed + + +def tracked_core_sources() -> list[pathlib.Path]: + process = subprocess.run( + ["git", "ls-files", "-z", "--", "*.solc"], + cwd=REPO_ROOT, + check=True, + stdout=subprocess.PIPE, + ) + tracked_solc = [ + path + for raw in process.stdout.split(b"\0") + if raw + for path in [pathlib.Path(raw.decode("utf-8"))] + ] + paths = [*tracked_solc, *(pathlib.Path(path) for path in CORE_SOL_FILES)] + return sorted(dict.fromkeys(paths)) + + +def eligible_paths(arguments: Sequence[str]) -> list[pathlib.Path]: + allowed = frozenset(tracked_core_sources()) + if not arguments: + return sorted(allowed) + result: list[pathlib.Path] = [] + for argument in arguments: + candidate = pathlib.Path(argument) + if candidate.is_absolute(): + candidate = candidate.resolve().relative_to(REPO_ROOT) + if candidate not in allowed: + raise ValueError( + f"refusing to migrate non-Core or untracked source: {candidate}" + ) + result.append(candidate) + return sorted(dict.fromkeys(result)) + + +def main(argv: Sequence[str] | None = None) -> int: + parser = argparse.ArgumentParser() + mode = parser.add_mutually_exclusive_group(required=True) + mode.add_argument("--write", action="store_true", help="rewrite eligible files") + mode.add_argument( + "--check", + action="store_true", + help="exit nonzero when eligible files still need migration", + ) + parser.add_argument( + "--from-head", + action="store_true", + help="with --write, regenerate eligible files from their HEAD versions", + ) + parser.add_argument("paths", nargs="*") + args = parser.parse_args(argv) + if args.from_head and not args.write: + parser.error("--from-head requires --write") + + try: + paths = eligible_paths(args.paths) + except (ValueError, subprocess.CalledProcessError) as error: + parser.error(str(error)) + + changed: list[pathlib.Path] = [] + for relative in paths: + path = REPO_ROOT / relative + # The import-resolution fixtures contain tracked symlink aliases. Their + # tracked blob is the link target, not Solcore source, and writing via + # pathlib would overwrite the target file. The target itself is also a + # tracked eligible source and is migrated independently. + if path.is_symlink(): + continue + current = path.read_text() + if args.from_head: + source = subprocess.run( + ["git", "show", f"HEAD:{relative.as_posix()}"], + cwd=REPO_ROOT, + check=True, + stdout=subprocess.PIPE, + text=True, + ).stdout + else: + source = current + migrated = SPECIAL_FIXTURES.get(relative) + if migrated is None: + migrated = migrate_source(source) + migrated = apply_file_fixups(relative, migrated) + if migrated == current: + continue + changed.append(relative) + if args.write: + path.write_text(migrated) + + action = "updated" if args.write else "needs migration" + for path in changed: + print(f"{action}: {path}") + print(f"{len(changed)} of {len(paths)} eligible files {action}") + return 1 if args.check and changed else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/test_migrate_new_syntax.py b/scripts/test_migrate_new_syntax.py new file mode 100644 index 000000000..a1e0c40d2 --- /dev/null +++ b/scripts/test_migrate_new_syntax.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +"""Focused regression tests for the source-corpus migration.""" + +from __future__ import annotations + +import importlib.util +import pathlib +import sys +import unittest + + +SCRIPT = pathlib.Path(__file__).with_name("migrate_new_syntax.py") +SPEC = importlib.util.spec_from_file_location("migrate_new_syntax", SCRIPT) +assert SPEC is not None and SPEC.loader is not None +migration = importlib.util.module_from_spec(SPEC) +sys.modules[SPEC.name] = migration +SPEC.loader.exec_module(migration) + + +class NewSyntaxStabilityTests(unittest.TestCase): + def assert_stable(self, source: str) -> None: + self.assertEqual(migration.migrate_source(source), source) + + def test_named_and_comptime_returns_are_stable(self) -> None: + self.assert_stable( + "function namedPair() returns " + "(left: uint256, comptime right: bool) " + "{ return (1, true); }\n" + ) + + def test_recursive_tuple_binding_is_stable(self) -> None: + for comptime in ("", "comptime "): + with self.subTest(comptime=comptime): + self.assert_stable( + "function unpack() { let " + f"{comptime}(a, (b, c)): " + "(uint256, (bool, word)) = readResult(); }\n" + ) + + def test_data_identifiers_are_stable(self) -> None: + cases = ( + "function f(data: word) returns (word) { return data; }\n", + "struct S { data: word; }\n", + "contract C { data: word; }\n", + "function f() { let data: word = 1; data = data + 1; }\n", + "function f(x: S) { x.data; }\n", + "function f() { assembly { let data := calldataload(0) } }\n", + ( + "// data Fake = Fake;\n" + 'function f() returns (string) ' + '{ return "data Fake = Fake;"; }\n' + ), + ) + for source in cases: + with self.subTest(source=source): + self.assert_stable(source) + + +class LegacyMigrationTests(unittest.TestCase): + def test_expression_annotation_still_becomes_conversion(self) -> None: + source = ( + "function convert(x: word) returns (word) " + "{ return x : word; }\n" + ) + expected = ( + "function convert(x: word) returns (word) " + "{ return x as word; }\n" + ) + self.assertEqual(migration.migrate_source(source), expected) + + def test_top_level_and_contract_data_declarations_migrate(self) -> None: + source = ( + "data Option(a) = None | Some(a);\n" + "contract C {\n" + " data Pair(a, b) = Pair(a, b);\n" + "}\n" + ) + expected = ( + "enum Option { None, Some(a) }\n" + "contract C {\n" + " enum Pair { Pair(a, b) }\n" + "}\n" + ) + self.assertEqual(migration.migrate_source(source), expected) + + def test_sum_parameter_fixup_is_reproducible(self) -> None: + generated = ( + "function sum (p1 : (T1, T2), p2 (T1, T2)) " + "returns ((T1, T2)) {" + ) + expected = ( + "function sum (p1 : (T1, T2), p2 : (T1, T2)) " + "returns ((T1, T2)) {" + ) + self.assertEqual( + migration.apply_file_fixups( + pathlib.Path("blog-post/sum.sol"), generated + ), + expected, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/src/Solcore/Primitives/Primitives.solc b/src/Solcore/Primitives/Primitives.solc index 562a3fbe5..a8e0ca64c 100644 --- a/src/Solcore/Primitives/Primitives.solc +++ b/src/Solcore/Primitives/Primitives.solc @@ -1,15 +1,15 @@ - data Unit = Unit - type Memory[a] = Word + enum Unit { Unit } + type Memory is Word; - class ref : Ref[deref] { - function load (r : ref) -> deref ; + trait Ref { + function load (r : ref) returns (deref) ; function store (r : ref, v : deref) ; } - type Stack [a] = a + type Stack is a; - instance Stack[a] : Ref [Memory[a]] { - function load (r : Stack[a]) -> Memory[a] {} + impl Ref, Memory> { + function load (r : Stack) returns (Memory) {} - function store(r : Stack[a], v : Memory[a]) {} + function store(r : Stack, v : Memory) {} } diff --git a/std/ABIGeneric.solc b/std/ABIGeneric.solc index ee4450502..e22837f02 100644 --- a/std/ABIGeneric.solc +++ b/std/ABIGeneric.solc @@ -1,6 +1,6 @@ -pragma no-patterson-condition ABIAttribs, ABIEncode, ABIDecode; -pragma no-bounded-variable-condition ABIAttribs, ABIEncode, ABIDecode; -pragma no-coverage-condition ABIDecode; +pragma solcore noPattersonCondition ABIAttribs, ABIEncode, ABIDecode; +pragma solcore noBoundVariableCondition ABIAttribs, ABIEncode, ABIDecode; +pragma solcore noCoverageCondition ABIDecode; export { ABIDeriving, @@ -8,9 +8,9 @@ export { decode }; -import std.{*}; -import std.opcodes.{mstore}; -import std.Generic.{*}; +import {*} from std; +import {mstore} from std.opcodes; +import {*} from std.Generic; // Marker class. Importing this module brings ABIDeriving into scope, which is // the signal DeriveGeneric looks for to auto-derive a per-type ABIDecode @@ -19,28 +19,27 @@ import std.Generic.{*}; // instance (its decode returns the head variable `a` via Generic.to, a // result-position type variable the specializer cannot monomorphize), so a // concrete per-type instance is emitted instead — exactly as for storage. -forall self. class self : ABIDeriving {} +trait ABIDeriving {} -function maxWord(a : word, b : word) -> word { - match gtWord(a, b) { - | true => return a; - | false => return b; - } +function maxWord(a : word, b : word) returns (word) { + match (gtWord(a, b) ) { + case true { return a; + } case false { return b; + } } } // ─── ABIAttribs for the primitive sum(f, g) type ───────────────────────── // headSize = 32 (tag word) + max(headSize(f), headSize(g)) -forall f g . f:ABIAttribs, g:ABIAttribs => -instance sum(f, g) : ABIAttribs { - function headSize(ty : Proxy(sum(f, g))) -> word { - let pf : Proxy(f); - let pg : Proxy(g); +impl ABIAttribs> where f: ABIAttribs, g: ABIAttribs { + function headSize(ty : Proxy>) returns (word) { + let pf : Proxy; + let pg : Proxy; return 32 + maxWord(ABIAttribs.headSize(pf), ABIAttribs.headSize(pg)); } - function isStatic(ty : Proxy(sum(f, g))) -> bool { - let pf : Proxy(f); - let pg : Proxy(g); + function isStatic(ty : Proxy>) returns (bool) { + let pf : Proxy; + let pg : Proxy; return and(ABIAttribs.isStatic(pf), ABIAttribs.isStatic(pg)); } } @@ -50,63 +49,55 @@ instance sum(f, g) : ABIAttribs { // [offset + 0 .. offset + 31] : tag word (0 = inl, 1 = inr) // [offset + 32 .. ] : encoded branch payload -forall f g . f:ABIAttribs, f:ABIEncode, g:ABIAttribs, g:ABIEncode => -instance sum(f, g) : ABIEncode { - function encodeInto(x : sum(f, g), basePtr : word, offset : word, tail : word) -> word { - match x { - | inl(v) => +impl ABIEncode> where f: ABIAttribs, f: ABIEncode, g: ABIAttribs, g: ABIEncode { + function encodeInto(x : sum, basePtr : word, offset : word, tail : word) returns (word) { + match (x ) { + case inl(v) { mstore(basePtr + offset, 0); return ABIEncode.encodeInto(v, basePtr, offset + 32, tail); - | inr(v) => + } case inr(v) { mstore(basePtr + offset, 1); return ABIEncode.encodeInto(v, basePtr, offset + 32, tail); - } + } } } } // ─── ABIDecode for sum(f, g) ───────────────────────────────────────────── // Reads the tag word at headOffset; dispatches to f or g decoder at headOffset + 32. -forall f g reader . - reader : WordReader, - f : ABIAttribs, - ABIDecoder(f, reader) : ABIDecode(f), - ABIDecoder(g, reader) : ABIDecode(g) => -instance ABIDecoder(sum(f, g), reader) : ABIDecode(sum(f, g)) { - function decode(ptr : ABIDecoder(sum(f, g), reader), headOffset : word) -> sum(f, g) { - match ptr { - | ABIDecoder(rdr) => +impl ABIDecode, reader>, sum> where reader: WordReader, f: ABIAttribs, ABIDecoder: ABIDecode, ABIDecoder: ABIDecode { + function decode(ptr : ABIDecoder, reader>, headOffset : word) returns (sum) { + match (ptr ) { + case ABIDecoder(rdr) { let tag = WordReader.read(WordReader.advance(rdr, headOffset)); - match tag { - | 0 => - let dec_f : ABIDecoder(f, reader) = ABIDecoder(rdr); + match (tag ) { + case 0 { + let dec_f : ABIDecoder = ABIDecoder(rdr); return inl(ABIDecode.decode(dec_f, headOffset + 32)); - | _ => - let dec_g : ABIDecoder(g, reader) = ABIDecoder(rdr); + } default { + let dec_g : ABIDecoder = ABIDecoder(rdr); return inr(ABIDecode.decode(dec_g, headOffset + 32)); - } - } + } } + } } } } // ─── Default bridges: ABIAttribs and ABIEncode via Generic ─────────────── // Any type 'a' with Generic(rep) inherits its ABI layout from rep. -forall a rep . a:Generic(rep), rep:ABIAttribs => -default instance a : ABIAttribs { - function headSize(ty : Proxy(a)) -> word { - let prx : Proxy(rep); +default impl ABIAttribs where a: Generic, rep: ABIAttribs { + function headSize(ty : Proxy) returns (word) { + let prx : Proxy; return ABIAttribs.headSize(prx); } - function isStatic(ty : Proxy(a)) -> bool { - let prx : Proxy(rep); + function isStatic(ty : Proxy) returns (bool) { + let prx : Proxy; return ABIAttribs.isStatic(prx); } } -forall a rep . a:Generic(rep), rep:ABIAttribs, rep:ABIEncode => -default instance a : ABIEncode { - function encodeInto(x : a, basePtr : word, offset : word, tail : word) -> word { +default impl ABIEncode where a: Generic, rep: ABIAttribs, rep: ABIEncode { + function encodeInto(x : a, basePtr : word, offset : word, tail : word) returns (word) { return ABIEncode.encodeInto(Generic.from(x), basePtr, offset, tail); } } @@ -115,8 +106,7 @@ default instance a : ABIEncode { // Serialises any 'a' that has a Generic(rep) instance. // Only the Generic instance is required — ABIEncode is resolved via the bridge. -forall a rep . a:Generic(rep), rep:ABIAttribs, rep:ABIEncode => -function encode(x : a, basePtr : word, offset : word, tail : word) -> word { +function encode(x : a, basePtr : word, offset : word, tail : word) returns (word) where a: Generic, rep: ABIAttribs, rep: ABIEncode { let xrep : rep = Generic.from(x); return ABIEncode.encodeInto(xrep, basePtr, offset, tail); } @@ -125,14 +115,10 @@ function encode(x : a, basePtr : word, offset : word, tail : word) -> word { // Deserialises any 'a' that has a Generic(rep) instance. // Only the Generic instance is required — ABIDecode is resolved via the bridge. -forall a rep reader . - a : Generic(rep), - reader : WordReader, - ABIDecoder(rep, reader) : ABIDecode(rep) => -function decode(ptr : ABIDecoder(a, reader), headOffset : word) -> a { - match ptr { - | ABIDecoder(rdr) => - let rep_ptr : ABIDecoder(rep, reader) = ABIDecoder(rdr); +function decode(ptr : ABIDecoder, headOffset : word) returns (a) where a: Generic, reader: WordReader, ABIDecoder: ABIDecode { + match (ptr ) { + case ABIDecoder(rdr) { + let rep_ptr : ABIDecoder = ABIDecoder(rdr); return Generic.to(ABIDecode.decode(rep_ptr, headOffset)); - } + } } } diff --git a/std/Generic.solc b/std/Generic.solc index ba30049d1..5f468040d 100644 --- a/std/Generic.solc +++ b/std/Generic.solc @@ -1,17 +1,16 @@ -pragma no-patterson-condition; -pragma no-bounded-variable-condition; +pragma solcore noPattersonCondition; +pragma solcore noBoundVariableCondition; export { Generic }; -import std.{*}; +import {*} from std; // MPTC: isomorphism between a user type and its SOP representation. // The representation 'rep' is built from primitive Solcore types: // sum(f, g) with constructors inl / inr // (f, g) pair (product) // () unit -forall a rep. -class a : Generic(rep) { - function from(x : a) -> rep; - function to(x : rep) -> a; +trait Generic { + function from(x : a) returns (rep); + function to(x : rep) returns (a); } diff --git a/std/StorageGeneric.solc b/std/StorageGeneric.solc index bee021064..1924a39ce 100644 --- a/std/StorageGeneric.solc +++ b/std/StorageGeneric.solc @@ -1,5 +1,5 @@ -pragma no-patterson-condition StorageType; -pragma no-bounded-variable-condition StorageType; +pragma solcore noPattersonCondition StorageType; +pragma solcore noBoundVariableCondition StorageType; export { StorageDeriving, @@ -7,15 +7,15 @@ export { storeGeneric }; -import std.{*}; -import std.opcodes.{sload, sstore}; -import std.Generic.{*}; +import {*} from std; +import {sload, sstore} from std.opcodes; +import {*} from std.Generic; // Marker class. Importing this module brings StorageDeriving into scope, which // is the signal DeriveGeneric looks for to auto-derive StorageSize / CanStore // instances for local data types (alongside their Generic instance). It carries // no methods — its mere visibility enables storage derivation. -forall self. class self : StorageDeriving {} +trait StorageDeriving {} // ─── Storage layout for algebraic data types ───────────────────────────── // @@ -29,11 +29,11 @@ forall self. class self : StorageDeriving {} // layouts. `Generic` instances are auto-derived for local data types, so // no per-type boilerplate is needed at the use site. -function maxWord(a : word, b : word) -> word { - match gtWord(a, b) { - | true => return a; - | false => return b; - } +function maxWord(a : word, b : word) returns (word) { + match (gtWord(a, b) ) { + case true { return a; + } case false { return b; + } } } // ─── StorageSize for the primitive sum(f, g) type ──────────────────────── @@ -41,11 +41,10 @@ function maxWord(a : word, b : word) -> word { // largest branch: size = 1 + max(size(f), size(g)). // (StorageSize for () and (a, b) is already provided by std.) -forall f g . f:StorageSize, g:StorageSize => -instance sum(f, g):StorageSize { - function size(x : Proxy(sum(f, g))) -> word { - let f_sz : word = StorageSize.size(Proxy : Proxy(f)); - let g_sz : word = StorageSize.size(Proxy : Proxy(g)); +impl StorageSize> where f: StorageSize, g: StorageSize { + function size(x : Proxy>) returns (word) { + let f_sz : word = StorageSize.size(Proxy as Proxy); + let g_sz : word = StorageSize.size(Proxy as Proxy); return 1 + maxWord(f_sz, g_sz); } } @@ -53,12 +52,12 @@ instance sum(f, g):StorageSize { // ─── StorageType for () ────────────────────────────────────────────────── // The unit type occupies no slots, so load/store are no-ops. -instance ():StorageType { - function load(ptr : word) -> () { - return (); +impl StorageType<()> { + function load(ptr : word) returns (()) { + return; } - function store(ptr : word, value : ()) -> () { - return (); + function store(ptr : word, value : ()) returns (()) { + return; } } @@ -66,21 +65,20 @@ instance ():StorageType { // Layout: [ptr .. ptr + size(a) - 1] : a // [ptr + size(a) .. ] : b -forall a b . a:StorageType, a:StorageSize, b:StorageType => -instance (a, b):StorageType { - function load(ptr : word) -> (a, b) { - let a_sz : word = StorageSize.size(Proxy : Proxy(a)); +impl StorageType<(a, b)> where a: StorageType, a: StorageSize, b: StorageType { + function load(ptr : word) returns ((a, b)) { + let a_sz : word = StorageSize.size(Proxy as Proxy); let x : a = StorageType.load(ptr); let y : b = StorageType.load(ptr + a_sz); return (x, y); } - function store(ptr : word, value : (a, b)) -> () { - match value { - | (x, y) => - let a_sz : word = StorageSize.size(Proxy : Proxy(a)); + function store(ptr : word, value : (a, b)) returns (()) { + match (value ) { + case (x, y) { + let a_sz : word = StorageSize.size(Proxy as Proxy); StorageType.store(ptr, x); StorageType.store(ptr + a_sz, y); - } + } } } } @@ -89,28 +87,27 @@ instance (a, b):StorageType { // [ptr] : tag word (0 = inl, 1 = inr) // [ptr + 1 .. ] : encoded branch payload -forall f g . f:StorageType, g:StorageType => -instance sum(f, g):StorageType { - function load(ptr : word) -> sum(f, g) { +impl StorageType> where f: StorageType, g: StorageType { + function load(ptr : word) returns (sum) { let tag : word = sload(ptr); - match tag { - | 0 => + match (tag ) { + case 0 { let v : f = StorageType.load(ptr + 1); return inl(v); - | _ => + } default { let v : g = StorageType.load(ptr + 1); return inr(v); - } + } } } - function store(ptr : word, value : sum(f, g)) -> () { - match value { - | inl(v) => + function store(ptr : word, value : sum) returns (()) { + match (value ) { + case inl(v) { sstore(ptr, 0); StorageType.store(ptr + 1, v); - | inr(v) => + } case inr(v) { sstore(ptr, 1); StorageType.store(ptr + 1, v); - } + } } } } @@ -134,51 +131,49 @@ instance sum(f, g):StorageType { // handle. // The unit type occupies no slots. -instance storage(()) : CanStore(()) { - function store(r : storage(()), v : ()) -> () { - return (); +impl CanStore<() storage, ()> { + function store(r : () storage, v : ()) returns (()) { + return; } - function load(r : storage(())) -> () { - return (); + function load(r : () storage) returns (()) { + return; } } // Product: store `a` at the base slot, `b` size(a) slots later. -forall a b . storage(a):CanStore(a), a:StorageSize, storage(b):CanStore(b) => -instance storage((a, b)) : CanStore((a, b)) { - function store(r : storage((a, b)), v : (a, b)) -> () { - match v { - | (x, y) => +impl CanStore<(a, b) storage, (a, b)> where a storage: CanStore, a: StorageSize, b storage: CanStore { + function store(r : (a, b) storage, v : (a, b)) returns (()) { + match (v ) { + case (x, y) { let base : word = Typedef.rep(r); - let a_sz : word = StorageSize.size(Proxy : Proxy(a)); - CanStore.store(storage(base) : storage(a), x); - CanStore.store(storage(base + a_sz) : storage(b), y); - } + let a_sz : word = StorageSize.size(Proxy as Proxy); + CanStore.store(storage(base) as a storage, x); + CanStore.store(storage(base + a_sz) as b storage, y); + } } } - function load(r : storage((a, b))) -> (a, b) { + function load(r : (a, b) storage) returns ((a, b)) { let base : word = Typedef.rep(r); - let a_sz : word = StorageSize.size(Proxy : Proxy(a)); - let x : a = CanStore.load(storage(base) : storage(a)); - let y : b = CanStore.load(storage(base + a_sz) : storage(b)); + let a_sz : word = StorageSize.size(Proxy as Proxy); + let x : a = CanStore.load(storage(base) as a storage); + let y : b = CanStore.load(storage(base + a_sz) as b storage); return (x, y); } } // Tagged union: slot 0 holds the tag, the branch payload follows. -forall f g . storage(f):CanStore(f), storage(g):CanStore(g) => -instance storage(sum(f, g)) : CanStore(sum(f, g)) { - function store(r : storage(sum(f, g)), v : sum(f, g)) -> () { +impl CanStore storage, sum> where f storage: CanStore, g storage: CanStore { + function store(r : sum storage, v : sum) returns (()) { let base : word = Typedef.rep(r); - match v { - | inl(x) => + match (v ) { + case inl(x) { sstore(base, 0); - CanStore.store(storage(base + 1) : storage(f), x); - | inr(y) => + CanStore.store(storage(base + 1) as f storage, x); + } case inr(y) { sstore(base, 1); - CanStore.store(storage(base + 1) : storage(g), y); - } + CanStore.store(storage(base + 1) as g storage, y); + } } } - function load(r : storage(sum(f, g))) -> sum(f, g) { + function load(r : sum storage) returns (sum) { let base : word = Typedef.rep(r); let tag : word = sload(base); // NOTE: the loaded payload is inlined directly into inl(...) / inr(...) @@ -188,12 +183,12 @@ instance storage(sum(f, g)) : CanStore(sum(f, g)) { // of the full sum(f, g), so it emits e.g. `inr(y)` and Yul codegen // rejects it (sum nesting off by one). Inlining matches the working // ABIGeneric.decode pattern, so inl/inr pick up the full sum(f, g). - match tag { - | 0 => - return inl(CanStore.load(storage(base + 1) : storage(f))); - | _ => - return inr(CanStore.load(storage(base + 1) : storage(g))); - } + match (tag ) { + case 0 { + return inl(CanStore.load(storage(base + 1) as f storage)); + } default { + return inr(CanStore.load(storage(base + 1) as g storage)); + } } } } @@ -201,21 +196,21 @@ instance storage(sum(f, g)) : CanStore(sum(f, g)) { // / storage(string) instances (data lives at keccak(slot)); these mirror them at // the storage(memory(bytes)) / storage(memory(string)) handle the structural // decomposition asks for, so a memory(bytes) field inside an ADT is storable. -instance storage(memory(bytes)) : CanStore(memory(bytes)) { - function store(r : storage(memory(bytes)), v : memory(bytes)) -> () { - CanStore.store(storage(Typedef.rep(r)) : storage(bytes), v); +impl CanStore { + function store(r : bytes memory storage, v : bytes memory) returns (()) { + CanStore.store(storage(Typedef.rep(r)) as bytes storage, v); } - function load(r : storage(memory(bytes))) -> memory(bytes) { - return CanStore.load(storage(Typedef.rep(r)) : storage(bytes)); + function load(r : bytes memory storage) returns (bytes memory) { + return CanStore.load(storage(Typedef.rep(r)) as bytes storage); } } -instance storage(memory(string)) : CanStore(memory(string)) { - function store(r : storage(memory(string)), v : memory(string)) -> () { - CanStore.store(storage(Typedef.rep(r)) : storage(string), v); +impl CanStore { + function store(r : string memory storage, v : string memory) returns (()) { + CanStore.store(storage(Typedef.rep(r)) as string storage, v); } - function load(r : storage(memory(string))) -> memory(string) { - return CanStore.load(storage(Typedef.rep(r)) : storage(string)); + function load(r : string memory storage) returns (string memory) { + return CanStore.load(storage(Typedef.rep(r)) as string storage); } } @@ -238,13 +233,11 @@ instance storage(memory(string)) : CanStore(memory(string)) { // Convenience wrappers mirroring std.ABIGeneric's encode / decode: persist or // read back any 'a' that has a Generic(rep) instance at a raw storage slot. -forall a rep . a:Generic(rep), rep:StorageType => -function storeGeneric(slot : word, value : a) -> () { +function storeGeneric(slot : word, value : a) returns (()) where a: Generic, rep: StorageType { StorageType.store(slot, Generic.from(value)); } -forall a rep . a:Generic(rep), rep:StorageType => -function loadGeneric(slot : word) -> a { +function loadGeneric(slot : word) returns (a) where a: Generic, rep: StorageType { let r : rep = StorageType.load(slot); return Generic.to(r); } diff --git a/std/dispatch.solc b/std/dispatch.solc index 8cc7be653..cae2d9f14 100644 --- a/std/dispatch.solc +++ b/std/dispatch.solc @@ -1,6 +1,6 @@ -import std.{*}; -import std.opcodes.{callvalue, calldatasize, calldataload, shr}; -import std.Generic.{*}; +import {*} from std; +import {callvalue, calldatasize, calldataload, shr} from std.opcodes; +import {*} from std.Generic; export { ABIString, @@ -21,44 +21,42 @@ export { sigStr }; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; // --- Core Data Types --- // A contract contains a tuple of methods and a single fallback // TODO: implement receive() -data Contract(methods, fb) = Contract(methods,fb); +enum Contract { Contract(methods, fb) } // A method contains an implementation (fn) as well as it's name and type signature -data Method(name, payability, args, rets, fn) = Method(Proxy(name), Proxy(payability), Proxy(args), Proxy(rets), fn); +enum Method { Method(Proxy, Proxy, Proxy, Proxy, fn) } // Contains the implementation for the fallback (fn) as well as it's type signature -data Fallback(payability, args, rets, fn) = Fallback(Proxy(payability), Proxy(args), Proxy(rets), fn); +enum Fallback { Fallback(Proxy, Proxy, Proxy, fn) } // --- Method Selectors --- -forall ty . class ty:ABIString { // deprecated - function append(head : word, tail : word, prx : Proxy(ty)) -> word; +trait ABIString { // deprecated + function append(head : word, tail : word, prx : Proxy) returns (word); } -forall t.class t:SigString { function sigStr(x:Proxy(t)) -> string; } +trait SigString { function sigStr(x:Proxy) returns (string); } -forall t. t: SigString => -function sigStr(p:Proxy(t)) -> string { SigString.sigStr(p) } +function sigStr(p:Proxy) returns (string) where t: SigString { return SigString.sigStr(p); } -instance uint256 : SigString { function sigStr(x:Proxy(uint256)) -> string { "uint256" }} -instance bytes32 : SigString { function sigStr(x:Proxy(bytes32)) -> string { "bytes32" }} -instance address : SigString { function sigStr(x:Proxy(address)) -> string { "address" }} -instance memory(string) : SigString { function sigStr(x:Proxy(memory(string))) -> string { "string" }} -instance memory(bytes) : SigString { function sigStr(x:Proxy(memory(bytes))) -> string { "bytes" }} -instance ():SigString { function sigStr(x:Proxy(())) -> string { "" } } +impl SigString { function sigStr(x:Proxy) returns (string) { return "uint256"; }} +impl SigString { function sigStr(x:Proxy) returns (string) { return "bytes32"; }} +impl SigString
{ function sigStr(x:Proxy
) returns (string) { return "address"; }} +impl SigString { function sigStr(x:Proxy) returns (string) { return "string"; }} +impl SigString { function sigStr(x:Proxy) returns (string) { return "bytes"; }} +impl SigString<()> { function sigStr(x:Proxy<()>) returns (string) { return ""; } } -forall a b. a:SigString, b: SigString => -instance (a,b):SigString { - function sigStr(x:Proxy((a,b))) -> string { - SigString.sigStr( Proxy:Proxy(a) ) + "," + SigString.sigStr( Proxy:Proxy(b) ) +impl SigString<(a, b)> where a: SigString, b: SigString { + function sigStr(x:Proxy<(a, b)>) returns (string) { + return SigString.sigStr( Proxy as Proxy ) + "," + SigString.sigStr( Proxy as Proxy ); } } @@ -69,10 +67,9 @@ instance (a,b):SigString { // `sum(uint256,uint256)` and `(uint256,uint256)` hash to distinct selectors. It // makes ADT-typed parameters produce a deterministic selector; refine here if a // specific on-the-wire sum convention is needed. -forall f g. f:SigString, g: SigString => -instance sum(f,g):SigString { - function sigStr(x:Proxy(sum(f,g))) -> string { - "sum(" + SigString.sigStr( Proxy:Proxy(f) ) + "," + SigString.sigStr( Proxy:Proxy(g) ) + ")" +impl SigString> where f: SigString, g: SigString { + function sigStr(x:Proxy>) returns (string) { + return "sum(" + SigString.sigStr( Proxy as Proxy ) + "," + SigString.sigStr( Proxy as Proxy ) + ")"; } } @@ -80,36 +77,30 @@ instance sum(f,g):SigString { // same way ABIAttribs / ABIEncode bridge through Generic in std.ABIGeneric. This // lets the dispatch take ADT-typed parameters (e.g. a Signature) without a // hand-written SigString instance per type. -forall a rep. a:Generic(rep), rep:SigString => -default instance a:SigString { - function sigStr(x:Proxy(a)) -> string { - SigString.sigStr( Proxy:Proxy(rep) ) +default impl SigString where a: Generic, rep: SigString { + function sigStr(x:Proxy) returns (string) { + return SigString.sigStr( Proxy as Proxy ); } } -forall name f args rets payability. - f: invokable(args,rets), name:SigString, args:SigString, rets:SigString => -instance Method(name,payability,args,rets,f):SigString { - function sigStr(x:Proxy(Method(name,payability,args,rets,f))) -> string { - sigStr(Proxy:Proxy(name)) + "(" + sigStr(Proxy:Proxy(args)) + ")" +impl SigString> where f: invokable, name: SigString, args: SigString, rets: SigString { + function sigStr(x:Proxy>) returns (string) { + return sigStr(Proxy as Proxy) + "(" + sigStr(Proxy as Proxy) + ")"; } } -forall ty . class ty:Selector { - function compute(prx : Proxy(ty)) -> bytes4; +trait Selector { + function compute(prx : Proxy) returns (bytes4); } // Computes the selector hash for a given method // this is a class with a single instance since it made some of the downstream definitions a bit cleaner to define // NOTE: for efficiency purposes this leaves dirty data past the end of the free memory pointer -forall name payability args rets fn - . name:SigString - , args:SigString -=> instance Method(name,payability,args,rets,fn):Selector { - function compute(prx : Proxy(Method(name,payability,args,rets,fn))) -> bytes4 { +impl Selector> where name: SigString, args: SigString { + function compute(prx : Proxy>) returns (bytes4) { // let hash : word = keccakLit(sigStr(prx)); - let hash = keccakLit(sigStr(Proxy:Proxy(name)) + "(" + sigStr(Proxy:Proxy(args)) + ")"); + let hash = keccakLit(sigStr(Proxy as Proxy) + "(" + sigStr(Proxy as Proxy) + ")"); return bytes4(shr(224, hash)); } } @@ -117,81 +108,60 @@ forall name payability args rets fn // --- Method Execution --- // Describes how to execute a given method / fallback -forall ty . class ty:ExecMethod { - function exec(x: ty) -> (); +trait ExecMethod { + function exec(x: ty) returns (()); } // If fn matches the provided args/ret types, then we can execute any non-payable method -forall name args rets fn - . fn:invokable(args,rets) - , args:ABIAttribs - , rets:ABIAttribs - , ABIDecoder(args,CalldataWordReader):ABIDecode(args) - , rets:ABIEncode -=> instance Method(name,NonPayable,args,rets,fn):ExecMethod { - function exec(m : Method(name,NonPayable,args,rets,fn)) -> () { - match m { - | Method(pnm,ppayability,pargs,prets,fn) => +impl ExecMethod> where fn: invokable, args: ABIAttribs, rets: ABIAttribs, ABIDecoder: ABIDecode, rets: ABIEncode { + function exec(m : Method) returns (()) { + match (m ) { + case Method(pnm,ppayability,pargs,prets,fn) { // non-payable methods must reject any callvalue before running - MethodLevelCallvalueCheck.checkCallvalue(Proxy : Proxy(NonPayable)); + MethodLevelCallvalueCheck.checkCallvalue(Proxy as Proxy); do_exec(pargs, prets, fn); - } + } } } } // If fn matches the provided args/ret types, then we can execute any payable method // payable methods skip the callvalue check entirely -forall name args rets fn - . fn:invokable(args,rets) - , args:ABIAttribs - , rets:ABIAttribs - , ABIDecoder(args,CalldataWordReader):ABIDecode(args) - , rets:ABIEncode -=> instance Method(name,Payable,args,rets,fn):ExecMethod { - function exec(m : Method(name,Payable,args,rets,fn)) -> () { - match m { - | Method(pnm,ppayability,pargs,prets,fn) => +impl ExecMethod> where fn: invokable, args: ABIAttribs, rets: ABIAttribs, ABIDecoder: ABIDecode, rets: ABIEncode { + function exec(m : Method) returns (()) { + match (m ) { + case Method(pnm,ppayability,pargs,prets,fn) { do_exec(pargs, prets, fn); - } + } } } } // Fallbacks have no ABI-decoded inputs or outputs, so the instance is // specialised to args = rets = () and bypasses the calldata length check // and ABI decode/encode entirely. -forall payability fn - . fn:invokable((),()) - , payability:MethodLevelCallvalueCheck -=> instance Fallback(payability,(),(),fn):ExecMethod { - function exec(fb : Fallback(payability,(),(),fn)) -> () { - match fb { - | Fallback(ppayability, pargs, prets, fn) => - MethodLevelCallvalueCheck.checkCallvalue(Proxy : Proxy(payability)); +impl ExecMethod> where fn: invokable<(), ()>, payability: MethodLevelCallvalueCheck { + function exec(fb : Fallback) returns (()) { + match (fb ) { + case Fallback(ppayability, pargs, prets, fn) { + MethodLevelCallvalueCheck.checkCallvalue(Proxy as Proxy); fn(()); assembly { stop() } - } + } } } } -forall args rets fn - . fn:invokable(args,rets) - , args:ABIAttribs - , rets:ABIAttribs - , ABIDecoder(args,CalldataWordReader):ABIDecode(args) - , rets:ABIEncode -=> function do_exec(pargs : Proxy(args), prets : Proxy(rets), fn : fn) -> () { +function do_exec(pargs : Proxy, prets : Proxy, fn : fn) returns (()) where fn: invokable, args: ABIAttribs, rets: ABIAttribs, ABIDecoder: ABIDecode, rets: ABIEncode { // check we have enough calldata for the head of args require(calldatasize() >= (ABIAttribs.headSize(pargs) + 4), Error(0x08638556)); // ABIInputTruncated() // TODO: calldatasize checks for dynamic types // abi decode args from calldata - let ptr : calldata(bytes) = calldata(4); + let ptr : bytes calldata = calldata(4); // TODO: this needs entirely too many type annotations - let args : args = abi_decode(ptr, pargs, Proxy : Proxy(CalldataWordReader)); + let args : args = abi_decode(ptr, pargs, Proxy as Proxy); // call fn with args // TODO: why are type annotations needed here? @@ -214,44 +184,41 @@ forall args rets fn // --- Method Dispatch --- // For a given tuple of methods this executes the method specified by the first four bytes of calldata -forall ty . class ty:RunDispatch { - function go(methods : ty) -> (); +trait RunDispatch { + function go(methods : ty) returns (()); } // We can dispatch to a single executable method with a known selector -forall name payability args rets fn - . Method(name,payability,args,rets,fn):ExecMethod - , Method(name,payability,args,rets,fn):Selector -=> instance Method(name,payability,args,rets,fn):RunDispatch { - function go(method : Method(name,payability,args,rets,fn)) -> () { - match selector_matches(Proxy : Proxy(Method(name,payability,args,rets,fn))) { - | true => ExecMethod.exec(method); - | false => return (); - } +impl RunDispatch> where Method: ExecMethod, Method: Selector { + function go(method : Method) returns (()) { + match (selector_matches(Proxy as Proxy>) ) { + case true { ExecMethod.exec(method); + } case false { return; + } } } } // Base case: a contract with no methods has nothing to dispatch to -instance ():RunDispatch { - function go(methods : ()) -> () { } +impl RunDispatch<()> { + function go(methods : ()) returns (()) { } } // Recursive instance -forall n m . n:ExecMethod, n:Selector, m:RunDispatch => instance (n,m):RunDispatch { - function go(methods : (n,m)) -> () { - match methods { - | (method_n, rest) => - match selector_matches(Proxy : Proxy(n)) { - | true => ExecMethod.exec(method_n); - | false => RunDispatch.go(rest); - } - } +impl RunDispatch<(n, m)> where n: ExecMethod, n: Selector, m: RunDispatch { + function go(methods : (n, m)) returns (()) { + match (methods ) { + case (method_n, rest) { + match (selector_matches(Proxy as Proxy) ) { + case true { ExecMethod.exec(method_n); + } case false { RunDispatch.go(rest); + } } + } } } } // TODO: we only wanna do the calldataload once // Given evidence of a type with a known selector, we can check if it matches the selector in the first four bytes of calldata -forall ty . ty:Selector => function selector_matches(prx : Proxy(ty)) -> bool { +function selector_matches(prx : Proxy) returns (bool) where ty: Selector { let candidate = Typedef.rep(Selector.compute(prx)); let selector = shr(224, calldataload(0)); return selector == candidate; @@ -259,20 +226,20 @@ forall ty . ty:Selector => function selector_matches(prx : Proxy(ty)) -> bool { // --- Callvalue Checks --- -data Payable; -data NonPayable; +enum Payable {} +enum NonPayable {} -forall ty . class ty:MethodLevelCallvalueCheck { - function checkCallvalue(pty : Proxy(ty)) -> (); +trait MethodLevelCallvalueCheck { + function checkCallvalue(pty : Proxy) returns (()); } // no callvalue check for Payable methods -instance Payable:MethodLevelCallvalueCheck { - function checkCallvalue(prx : Proxy(Payable)) -> () { } +impl MethodLevelCallvalueCheck { + function checkCallvalue(prx : Proxy) returns (()) { } } // NonPayable methods revert if passed value -instance NonPayable:MethodLevelCallvalueCheck { - function checkCallvalue(prx : Proxy(NonPayable)) -> () { +impl MethodLevelCallvalueCheck { + function checkCallvalue(prx : Proxy) returns (()) { let NonPayableReceivedValue = Error(0xb5988ea3); require(callvalue() == 0, NonPayableReceivedValue); } @@ -281,15 +248,15 @@ instance NonPayable:MethodLevelCallvalueCheck { // --- Contract Execution --- // Describes how to execute a given contract -forall c . class c:RunContract { - function exec(v : c) -> (); +trait RunContract { + function exec(v : c) returns (()); } // If we have a dispatch for the contracts methods, and we know how to execute it's fallback, then we can define an entrypoint -forall methods fb . methods:RunDispatch, fb:ExecMethod => instance Contract(methods, fb):RunContract { - function exec(c : Contract(methods, fb)) -> () { - match c { - | Contract(ms, fb) => +impl RunContract> where methods: RunDispatch, fb: ExecMethod { + function exec(c : Contract) returns (()) { + match (c ) { + case Contract(ms, fb) { // TODO: if all methods are non payable then we should life the callvalue check here @@ -307,12 +274,12 @@ forall methods fb . methods:RunDispatch, fb:ExecMethod => instance Contract(meth // fallthrough to fallback -- this will be reached upon short input // or no matching selector ExecMethod.exec(fb); - } + } } } } // This is the default fallback used if none is defined. -function fallback_default_implementation() -> () { +function fallback_default_implementation() returns (()) { let NoSelectorMatchedWithoutFallback = Error(0x4924aef0); revertWithError(NoSelectorMatchedWithoutFallback); } diff --git a/std/opcodes.solc b/std/opcodes.solc index 991d18eb1..90723e98d 100644 --- a/std/opcodes.solc +++ b/std/opcodes.solc @@ -84,13 +84,13 @@ export { selfdestruct }; -function stop() -> () { +function stop() returns (()) { assembly { stop() } } -function add(a: word, b: word) -> word { +function add(a: word, b: word) returns (word) { let res; assembly { res := add(a, b) @@ -98,7 +98,7 @@ function add(a: word, b: word) -> word { return res; } -function mul(a: word, b: word) -> word { +function mul(a: word, b: word) returns (word) { let res; assembly { res := mul(a, b) @@ -106,7 +106,7 @@ function mul(a: word, b: word) -> word { return res; } -function sub(a: word, b: word) -> word { +function sub(a: word, b: word) returns (word) { let res; assembly { res := sub(a, b) @@ -114,7 +114,7 @@ function sub(a: word, b: word) -> word { return res; } -function div(a: word, b: word) -> word { +function div(a: word, b: word) returns (word) { let res; assembly { res := div(a, b) @@ -122,7 +122,7 @@ function div(a: word, b: word) -> word { return res; } -function sdiv(a: word, b: word) -> word { +function sdiv(a: word, b: word) returns (word) { let res; assembly { res := sdiv(a, b) @@ -130,7 +130,7 @@ function sdiv(a: word, b: word) -> word { return res; } -function mod(a: word, b: word) -> word { +function mod(a: word, b: word) returns (word) { let res; assembly { res := mod(a, b) @@ -138,7 +138,7 @@ function mod(a: word, b: word) -> word { return res; } -function smod(a: word, b: word) -> word { +function smod(a: word, b: word) returns (word) { let res; assembly { res := smod(a, b) @@ -146,7 +146,7 @@ function smod(a: word, b: word) -> word { return res; } -function addmod(a: word, b: word, c: word) -> word { +function addmod(a: word, b: word, c: word) returns (word) { let res; assembly { res := addmod(a, b, c) @@ -154,7 +154,7 @@ function addmod(a: word, b: word, c: word) -> word { return res; } -function mulmod(a: word, b: word, c: word) -> word { +function mulmod(a: word, b: word, c: word) returns (word) { let res; assembly { res := mulmod(a, b, c) @@ -162,7 +162,7 @@ function mulmod(a: word, b: word, c: word) -> word { return res; } -function exp(a: word, b: word) -> word { +function exp(a: word, b: word) returns (word) { let res; assembly { res := exp(a, b) @@ -170,7 +170,7 @@ function exp(a: word, b: word) -> word { return res; } -function signextend(a: word, b: word) -> word { +function signextend(a: word, b: word) returns (word) { let res; assembly { res := signextend(a, b) @@ -178,7 +178,7 @@ function signextend(a: word, b: word) -> word { return res; } -function lt(a: word, b: word) -> word { +function lt(a: word, b: word) returns (word) { let res; assembly { res := lt(a, b) @@ -186,7 +186,7 @@ function lt(a: word, b: word) -> word { return res; } -function gt(a: word, b: word) -> word { +function gt(a: word, b: word) returns (word) { let res; assembly { res := gt(a, b) @@ -194,7 +194,7 @@ function gt(a: word, b: word) -> word { return res; } -function slt(a: word, b: word) -> word { +function slt(a: word, b: word) returns (word) { let res; assembly { res := slt(a, b) @@ -202,7 +202,7 @@ function slt(a: word, b: word) -> word { return res; } -function sgt(a: word, b: word) -> word { +function sgt(a: word, b: word) returns (word) { let res; assembly { res := sgt(a, b) @@ -210,7 +210,7 @@ function sgt(a: word, b: word) -> word { return res; } -function eq(a: word, b: word) -> word { +function eq(a: word, b: word) returns (word) { let res; assembly { res := eq(a, b) @@ -218,7 +218,7 @@ function eq(a: word, b: word) -> word { return res; } -function iszero(a: word) -> word { +function iszero(a: word) returns (word) { let res; assembly { res := iszero(a) @@ -226,7 +226,7 @@ function iszero(a: word) -> word { return res; } -function and(a: word, b: word) -> word { +function and(a: word, b: word) returns (word) { let res; assembly { res := and(a, b) @@ -234,7 +234,7 @@ function and(a: word, b: word) -> word { return res; } -function or(a: word, b: word) -> word { +function or(a: word, b: word) returns (word) { let res; assembly { res := or(a, b) @@ -242,7 +242,7 @@ function or(a: word, b: word) -> word { return res; } -function xor(a: word, b: word) -> word { +function xor(a: word, b: word) returns (word) { let res; assembly { res := xor(a, b) @@ -250,7 +250,7 @@ function xor(a: word, b: word) -> word { return res; } -function not(a: word) -> word { +function not(a: word) returns (word) { let res; assembly { res := not(a) @@ -258,7 +258,7 @@ function not(a: word) -> word { return res; } -function byte(a: word, b: word) -> word { +function byte(a: word, b: word) returns (word) { let res; assembly { res := byte(a, b) @@ -266,7 +266,7 @@ function byte(a: word, b: word) -> word { return res; } -function shl(a: word, b: word) -> word { +function shl(a: word, b: word) returns (word) { let res; assembly { res := shl(a, b) @@ -274,7 +274,7 @@ function shl(a: word, b: word) -> word { return res; } -function shr(a: word, b: word) -> word { +function shr(a: word, b: word) returns (word) { let res; assembly { res := shr(a, b) @@ -282,7 +282,7 @@ function shr(a: word, b: word) -> word { return res; } -function sar(a: word, b: word) -> word { +function sar(a: word, b: word) returns (word) { let res; assembly { res := sar(a, b) @@ -290,7 +290,7 @@ function sar(a: word, b: word) -> word { return res; } -function clz(a: word) -> word { +function clz(a: word) returns (word) { let res; assembly { res := clz(a) @@ -298,7 +298,7 @@ function clz(a: word) -> word { return res; } -function keccak256(a: word, b: word) -> word { +function keccak256(a: word, b: word) returns (word) { let res; assembly { res := keccak256(a, b) @@ -306,7 +306,7 @@ function keccak256(a: word, b: word) -> word { return res; } -function address() -> word { +function address() returns (word) { let res; assembly { res := address() @@ -314,7 +314,7 @@ function address() -> word { return res; } -function balance(a: word) -> word { +function balance(a: word) returns (word) { let res; assembly { res := balance(a) @@ -322,7 +322,7 @@ function balance(a: word) -> word { return res; } -function origin() -> word { +function origin() returns (word) { let res; assembly { res := origin() @@ -330,7 +330,7 @@ function origin() -> word { return res; } -function caller() -> word { +function caller() returns (word) { let res; assembly { res := caller() @@ -338,7 +338,7 @@ function caller() -> word { return res; } -function callvalue() -> word { +function callvalue() returns (word) { let res; assembly { res := callvalue() @@ -346,7 +346,7 @@ function callvalue() -> word { return res; } -function calldataload(a: word) -> word { +function calldataload(a: word) returns (word) { let res; assembly { res := calldataload(a) @@ -354,7 +354,7 @@ function calldataload(a: word) -> word { return res; } -function calldatasize() -> word { +function calldatasize() returns (word) { let res; assembly { res := calldatasize() @@ -362,13 +362,13 @@ function calldatasize() -> word { return res; } -function calldatacopy(a: word, b: word, c: word) -> () { +function calldatacopy(a: word, b: word, c: word) returns (()) { assembly { calldatacopy(a, b, c) } } -function codesize() -> word { +function codesize() returns (word) { let res; assembly { res := codesize() @@ -376,13 +376,13 @@ function codesize() -> word { return res; } -function codecopy(a: word, b: word, c: word) -> () { +function codecopy(a: word, b: word, c: word) returns (()) { assembly { codecopy(a, b, c) } } -function gasprice() -> word { +function gasprice() returns (word) { let res; assembly { res := gasprice() @@ -390,7 +390,7 @@ function gasprice() -> word { return res; } -function extcodesize(a: word) -> word { +function extcodesize(a: word) returns (word) { let res; assembly { res := extcodesize(a) @@ -398,13 +398,13 @@ function extcodesize(a: word) -> word { return res; } -function extcodecopy(a: word, b: word, c: word, d: word) -> () { +function extcodecopy(a: word, b: word, c: word, d: word) returns (()) { assembly { extcodecopy(a, b, c, d) } } -function returndatasize() -> word { +function returndatasize() returns (word) { let res; assembly { res := returndatasize() @@ -412,13 +412,13 @@ function returndatasize() -> word { return res; } -function returndatacopy(a: word, b: word, c: word) -> () { +function returndatacopy(a: word, b: word, c: word) returns (()) { assembly { returndatacopy(a, b, c) } } -function extcodehash(a: word) -> word { +function extcodehash(a: word) returns (word) { let res; assembly { res := extcodehash(a) @@ -426,7 +426,7 @@ function extcodehash(a: word) -> word { return res; } -function blockhash(a: word) -> word { +function blockhash(a: word) returns (word) { let res; assembly { res := blockhash(a) @@ -434,7 +434,7 @@ function blockhash(a: word) -> word { return res; } -function coinbase() -> word { +function coinbase() returns (word) { let res; assembly { res := coinbase() @@ -442,7 +442,7 @@ function coinbase() -> word { return res; } -function timestamp() -> word { +function timestamp() returns (word) { let res; assembly { res := timestamp() @@ -450,7 +450,7 @@ function timestamp() -> word { return res; } -function number() -> word { +function number() returns (word) { let res; assembly { res := number() @@ -458,7 +458,7 @@ function number() -> word { return res; } -function prevrandao() -> word { +function prevrandao() returns (word) { let res; assembly { res := prevrandao() @@ -466,7 +466,7 @@ function prevrandao() -> word { return res; } -function gaslimit() -> word { +function gaslimit() returns (word) { let res; assembly { res := gaslimit() @@ -474,7 +474,7 @@ function gaslimit() -> word { return res; } -function chainid() -> word { +function chainid() returns (word) { let res; assembly { res := chainid() @@ -482,7 +482,7 @@ function chainid() -> word { return res; } -function selfbalance() -> word { +function selfbalance() returns (word) { let res; assembly { res := selfbalance() @@ -490,7 +490,7 @@ function selfbalance() -> word { return res; } -function basefee() -> word { +function basefee() returns (word) { let res; assembly { res := basefee() @@ -498,7 +498,7 @@ function basefee() -> word { return res; } -function blobhash(a: word) -> word { +function blobhash(a: word) returns (word) { let res; assembly { res := blobhash(a) @@ -506,7 +506,7 @@ function blobhash(a: word) -> word { return res; } -function blobbasefee() -> word { +function blobbasefee() returns (word) { let res; assembly { res := blobbasefee() @@ -514,13 +514,13 @@ function blobbasefee() -> word { return res; } -function pop(a: word) -> () { +function pop(a: word) returns (()) { assembly { pop(a) } } -function mload(a: word) -> word { +function mload(a: word) returns (word) { let res; assembly { res := mload(a) @@ -528,19 +528,19 @@ function mload(a: word) -> word { return res; } -function mstore(a: word, b: word) -> () { +function mstore(a: word, b: word) returns (()) { assembly { mstore(a, b) } } -function mstore8(a: word, b: word) -> () { +function mstore8(a: word, b: word) returns (()) { assembly { mstore8(a, b) } } -function sload(a: word) -> word { +function sload(a: word) returns (word) { let res; assembly { res := sload(a) @@ -548,13 +548,13 @@ function sload(a: word) -> word { return res; } -function sstore(a: word, b: word) -> () { +function sstore(a: word, b: word) returns (()) { assembly { sstore(a, b) } } -function msize() -> word { +function msize() returns (word) { let res; assembly { res := msize() @@ -562,7 +562,7 @@ function msize() -> word { return res; } -function gas() -> word { +function gas() returns (word) { let res; assembly { res := gas() @@ -570,7 +570,7 @@ function gas() -> word { return res; } -function tload(a: word) -> word { +function tload(a: word) returns (word) { let res; assembly { res := tload(a) @@ -578,49 +578,49 @@ function tload(a: word) -> word { return res; } -function tstore(a: word, b: word) -> () { +function tstore(a: word, b: word) returns (()) { assembly { tstore(a, b) } } -function mcopy(a: word, b: word, c: word) -> () { +function mcopy(a: word, b: word, c: word) returns (()) { assembly { mcopy(a, b, c) } } -function log0(a: word, b: word) -> () { +function log0(a: word, b: word) returns (()) { assembly { log0(a, b) } } -function log1(a: word, b: word, c: word) -> () { +function log1(a: word, b: word, c: word) returns (()) { assembly { log1(a, b, c) } } -function log2(a: word, b: word, c: word, d: word) -> () { +function log2(a: word, b: word, c: word, d: word) returns (()) { assembly { log2(a, b, c, d) } } -function log3(a: word, b: word, c: word, d: word, e: word) -> () { +function log3(a: word, b: word, c: word, d: word, e: word) returns (()) { assembly { log3(a, b, c, d, e) } } -function log4(a: word, b: word, c: word, d: word, e: word, f: word) -> () { +function log4(a: word, b: word, c: word, d: word, e: word, f: word) returns (()) { assembly { log4(a, b, c, d, e, f) } } -function create(a: word, b: word, c: word) -> word { +function create(a: word, b: word, c: word) returns (word) { let res; assembly { res := create(a, b, c) @@ -628,7 +628,7 @@ function create(a: word, b: word, c: word) -> word { return res; } -function call(a: word, b: word, c: word, d: word, e: word, f: word, g: word) -> word { +function call(a: word, b: word, c: word, d: word, e: word, f: word, g: word) returns (word) { let res; assembly { res := call(a, b, c, d, e, f, g) @@ -636,7 +636,7 @@ function call(a: word, b: word, c: word, d: word, e: word, f: word, g: word) -> return res; } -function callcode(a: word, b: word, c: word, d: word, e: word, f: word, g: word) -> word { +function callcode(a: word, b: word, c: word, d: word, e: word, f: word, g: word) returns (word) { let res; assembly { res := callcode(a, b, c, d, e, f, g) @@ -644,13 +644,13 @@ function callcode(a: word, b: word, c: word, d: word, e: word, f: word, g: word) return res; } -function return_(a: word, b: word) -> () { +function return_(a: word, b: word) returns (()) { assembly { return(a, b) } } -function delegatecall(a: word, b: word, c: word, d: word, e: word, f: word) -> word { +function delegatecall(a: word, b: word, c: word, d: word, e: word, f: word) returns (word) { let res; assembly { res := delegatecall(a, b, c, d, e, f) @@ -658,7 +658,7 @@ function delegatecall(a: word, b: word, c: word, d: word, e: word, f: word) -> w return res; } -function create2(a: word, b: word, c: word, d: word) -> word { +function create2(a: word, b: word, c: word, d: word) returns (word) { let res; assembly { res := create2(a, b, c, d) @@ -666,7 +666,7 @@ function create2(a: word, b: word, c: word, d: word) -> word { return res; } -function staticcall(a: word, b: word, c: word, d: word, e: word, f: word) -> word { +function staticcall(a: word, b: word, c: word, d: word, e: word, f: word) returns (word) { let res; assembly { res := staticcall(a, b, c, d, e, f) @@ -674,19 +674,19 @@ function staticcall(a: word, b: word, c: word, d: word, e: word, f: word) -> wor return res; } -function revert(a: word, b: word) -> () { +function revert(a: word, b: word) returns (()) { assembly { revert(a, b) } } -function invalid() -> () { +function invalid() returns (()) { assembly { invalid() } } -function selfdestruct(a: word) -> () { +function selfdestruct(a: word) returns (()) { assembly { selfdestruct(a) } diff --git a/std/std.solc b/std/std.solc index 213dc5109..41207b846 100644 --- a/std/std.solc +++ b/std/std.solc @@ -1,7 +1,7 @@ -import std.opcodes.{add, sub, mul, div, mod, addmod as addmod_, mulmod as mulmod_, and as and_, or as or_, xor as xor_, shl, shr, eq, not as not_, gt as gt_, iszero, keccak256, mstore, mload, mcopy, sstore, sload, gas, calldataload, calldatacopy, returndatasize, returndatacopy, log1 as log1_, call, staticcall, revert as revert_, invalid}; +import {add, sub, mul, div, mod, addmod as addmod_, mulmod as mulmod_, and as and_, or as or_, xor as xor_, shl, shr, eq, not as not_, gt as gt_, iszero, keccak256, mstore, mload, mcopy, sstore, sload, gas, calldataload, calldatacopy, returndatasize, returndatacopy, log1 as log1_, call, staticcall, revert as revert_, invalid} from std.opcodes; -pragma no-patterson-condition ABIEncode, Num, Array, ArrayPush; -pragma no-coverage-condition ABIDecode, MemoryType, Array, ArrayPush, RValueIdxAccess; +pragma solcore noPattersonCondition ABIEncode, Num, Array, ArrayPush; +pragma solcore noCoverageCondition ABIDecode, MemoryType, Array, ArrayPush, RValueIdxAccess; export { ABIAttribs, @@ -166,19 +166,18 @@ export { */ -forall t.t:Typedef(word) => -function log1(v:t, topic:word) -> () { +function log1(v:t, topic:word) returns (()) where t: Typedef { let w : word = Typedef.rep(v); mstore(0, w); log1_(0, 32, topic); } -function unimplemented() -> () { +function unimplemented() returns (()) { let Unimplemented = Error(0x6e128399); revertWithError(Unimplemented); } -function out_of_bounds() -> () { +function out_of_bounds() returns (()) { let OutOfBounds = Error(0xb4120f14); revertWithError(OutOfBounds); } @@ -188,42 +187,42 @@ function out_of_bounds() -> () { // ------------------------------------------------------------------ // EmitHull has special handling for `revertLit("...")` after MastEval has // constant-folded the argument to a string literal. -function revertLit(s:string) -> () { +function revertLit(s:string) returns (()) { unimplemented(); // Sanity check if folding ignores it. - return (); + return; } // Empty revert. -function revertEmpty() -> () { +function revertEmpty() returns (()) { revert_(0, 0); } // TODO: use bytes4 // TODO: add literal version Msg(string) -data Error = Error(word) | Empty | Msg(memory(string)); +enum Error { Error(word), Empty, Msg(string memory) } // Revert with Error selector. -function revertWithError(e:Error) -> () { - match e { - | .Error(selector) => +function revertWithError(e:Error) returns (()) { + match (e ) { + case .Error(selector) { mstore(0, selector); // We only care about the BE MSB. revert_(28, 4); - | .Empty => + } case .Empty { revert_(0, 0); - | .Msg(msg) => + } case .Msg(msg) { let msg_ = Typedef.rep(msg); revert_(msg_ + 32, mload(msg_)); - } + } } } -function assert(cond: bool) -> () { +function assert(cond: bool) returns (()) { if (!cond) { invalid(); } } -function require(cond: bool, e: Error) -> () { +function require(cond: bool, e: Error) returns (()) { if (!cond) { revertWithError(e); } @@ -232,179 +231,168 @@ function require(cond: bool, e: Error) -> () { // --- booleans --- // TODO: this should short circuit. probably needs some compiler magic to do so. -function and(x: bool, y: bool) -> bool { - match x, y { - | true, y => return y; - | false, _ => return false; - } +function and(x: bool, y: bool) returns (bool) { + match (x, y ) { + case (true, y ) { return y; + } case (false, _ ) { return false; + } } } // TODO: this should short circuit. probably needs some compiler magic to do so. -function or(x: bool, y: bool) -> bool { - match x, y { - | true, _ => return true; - | false, y => return y; - } +function or(x: bool, y: bool) returns (bool) { + match (x, y ) { + case (true, _ ) { return true; + } case (false, y ) { return y; + } } } -function not(b:bool) -> bool { - match b { - | false => return true; - | true => return false; - } +function not(b:bool) returns (bool) { + match (b ) { + case false { return true; + } case true { return false; + } } } -function frombool(b : bool) -> word { - match b { - | false => return 0; - | true => return 1; - } +function frombool(b : bool) returns (word) { + match (b ) { + case false { return 0; + } case true { return 1; + } } } -function tobool(x: word) -> bool { - match x { - | 0 => return false; - | _ => return true; - } +function tobool(x: word) returns (bool) { + match (x ) { + case 0 { return false; + } default { return true; + } } } // --- Tuple projections --- -forall a b . function fst(p: (a, b)) -> a { - match p { - | (a, _) => return a; - } +function fst(p: (a, b)) returns (a) { + match (p ) { + case (a, _) { return a; + } } } -forall a b . function snd(p: (a, b)) -> b { - match p { - | (_, b) => return b; - } +function snd(p: (a, b)) returns (b) { + match (p ) { + case (_, b) { return b; + } } } // --- Proxy --- // Proxy is a unit type that can be used to pass Types as paramaters at runtime -data Proxy(t) = Proxy; +enum Proxy { Proxy } // --- Type Abstraction --- -forall abs rep . class abs:Typedef(rep) { - function abs(x:rep) -> abs; - function rep(x:abs) -> rep; +trait Typedef { + function abs(x:rep) returns (abs); + function rep(x:abs) returns (rep); } -forall t. -default instance t:Typedef(t) { - function abs(x:t) -> t { return x; } - function rep(x:t) -> t { return x; } +default impl Typedef { + function abs(x:t) returns (t) { return x; } + function rep(x:t) returns (t) { return x; } } // --- Equality --- // Note: All these are used by the compiler by name. -forall a. -class a:Eq { - function eq(x:a, y:a) -> bool; +trait Eq { + function eq(x:a, y:a) returns (bool); } -forall a. a:Eq => -function ne(x:a, y:a) -> bool { +function ne(x:a, y:a) returns (bool) where a: Eq { return not(Eq.eq(x,y)); } // --- Ordering --- // Note: All these are used by the compiler by name. -forall a. a:Eq => -class a:Ord { - function gt(x:a, y:a) -> bool; +trait Ord where a: Eq { + function gt(x:a, y:a) returns (bool); } -forall a. a:Ord => -function gt(x:a, y:a) -> bool { +function gt(x:a, y:a) returns (bool) where a: Ord { return Ord.gt(x,y); } -forall a. a:Ord => -function le(x:a, y:a) -> bool { +function le(x:a, y:a) returns (bool) where a: Ord { return not(Ord.gt(x,y)); } -forall a. a:Ord => -function ge(x:a, y:a) -> bool { +function ge(x:a, y:a) returns (bool) where a: Ord { return le(y,x); } -forall a. a:Ord => -function lt(x:a, y:a) -> bool { +function lt(x:a, y:a) returns (bool) where a: Ord { return Ord.gt(y,x); } // --- Arithmetic --- // Note: All these are used by the compiler by name. -forall t . class t:Add { - function add(l: t, r: t) -> t; +trait Add { + function add(l: t, r: t) returns (t); } -forall t . class t:Sub { - function sub(l: t, r: t) -> t; +trait Sub { + function sub(l: t, r: t) returns (t); } -forall t . class t:Mul { - function mul(l: t, r: t) -> t; +trait Mul { + function mul(l: t, r: t) returns (t); } -forall t . class t:Div { - function div(l: t, r: t) -> t; +trait Div { + function div(l: t, r: t) returns (t); } -forall t . class t:Mod { - function mod(l: t, r: t) -> t; +trait Mod { + function mod(l: t, r: t) returns (t); } -forall t . class t:BitAnd { - function band(l: t, r: t) -> t; +trait BitAnd { + function band(l: t, r: t) returns (t); } -forall t . class t:BitOr { - function bor(l: t, r: t) -> t; +trait BitOr { + function bor(l: t, r: t) returns (t); } -forall t . class t:BitXor { - function bxor(l: t, r: t) -> t; +trait BitXor { + function bxor(l: t, r: t) returns (t); } -forall t . class t:Bounded { - function minVal() -> t; - function maxVal() -> t; +trait Bounded { + function minVal() returns (t); + function maxVal() returns (t); } -forall t . t:Bounded => -function maxVal() -> t { return Bounded.maxVal(); } +function maxVal() returns (t) where t: Bounded { return Bounded.maxVal(); } // umbrella class -forall a. a:Add, a:Sub, a:Bounded, a:Eq, a:Ord, a:Typedef(word) => -class a:Num { - function maxVal() -> a; - function toWord(x:a) -> word; - function fromWord(x:word) -> a; - function fromInteger(comptime x:integer) -> comptime a; - function add(x:a, y:a) -> a; - function sub(x:a, y:a) -> a; - function gt(x:a, y:a) -> bool; -} - -forall a. a:Add, a:Sub, a:Bounded, a:Eq, a:Ord, a:Typedef(word) => -default instance a:Num { - function maxVal() -> a { return Bounded.maxVal(); } - function toWord(x:a) -> word { return Typedef.rep(x); } - function fromWord(x:word) -> a { return Typedef.abs(x); } - function fromInteger(comptime x:integer) -> comptime a { return Typedef.abs(wordFromInteger(x)); } - function add(x:a, y:a) -> a { return Add.add(x,y); } - function sub(x:a, y:a) -> a { return Sub.sub(x,y); } - function gt(x: a, y: a) -> bool { return Ord.gt(x, y); } +trait Num where a: Add, a: Sub, a: Bounded, a: Eq, a: Ord, a: Typedef { + function maxVal() returns (a); + function toWord(x:a) returns (word); + function fromWord(x:word) returns (a); + function fromInteger(comptime x:integer) returns (comptime a); + function add(x:a, y:a) returns (a); + function sub(x:a, y:a) returns (a); + function gt(x:a, y:a) returns (bool); +} + +default impl Num where a: Add, a: Sub, a: Bounded, a: Eq, a: Ord, a: Typedef { + function maxVal() returns (a) { return Bounded.maxVal(); } + function toWord(x:a) returns (word) { return Typedef.rep(x); } + function fromWord(x:word) returns (a) { return Typedef.abs(x); } + function fromInteger(comptime x:integer) returns (comptime a) { return Typedef.abs(wordFromInteger(x)); } + function add(x:a, y:a) returns (a) { return Add.add(x,y); } + function sub(x:a, y:a) returns (a) { return Sub.sub(x,y); } + function gt(x: a, y: a) returns (bool) { return Ord.gt(x, y); } } // --- Word Arithmetic & Logic --- @@ -412,161 +400,161 @@ default instance a:Num { // These are intended to be folded by MastEval when their arguments are // statically known word values. -function eqWord(x:word, y:word) -> bool { +function eqWord(x:word, y:word) returns (bool) { return tobool(eq(x, y)); } -function gtWord(x:word, y:word) -> bool { +function gtWord(x:word, y:word) returns (bool) { return tobool(gt_(x, y)); } -function addWord(l: word, r: word) -> word { +function addWord(l: word, r: word) returns (word) { return add(l, r); } -function subWord(l: word, r: word) -> word { +function subWord(l: word, r: word) returns (word) { return sub(l, r); } // Bitwise AND -function bandWord(x: word, y: word) -> word { +function bandWord(x: word, y: word) returns (word) { return and_(x, y); } // Bitwise OR -function borWord(x: word, y: word) -> word { +function borWord(x: word, y: word) returns (word) { return or_(x, y); } // Bitwise XOR -function bxorWord(x: word, y: word) -> word { +function bxorWord(x: word, y: word) returns (word) { return xor_(x, y); } // Bitwise NOT -function bnotWord(x: word) -> word { +function bnotWord(x: word) returns (word) { return not_(x); } // Bitwise SHL -function bshlWord(x: word, y: word) -> word { +function bshlWord(x: word, y: word) returns (word) { return shl(x, y); } // Bitwise SHR -function bshrWord(x: word, y: word) -> word { +function bshrWord(x: word, y: word) returns (word) { return shr(x, y); } -instance word:Eq { - function eq(x:word, y:word) -> bool { +impl Eq { + function eq(x:word, y:word) returns (bool) { return eqWord(x, y); } } -instance word:Ord { - function gt(x:word, y:word) -> bool { +impl Ord { + function gt(x:word, y:word) returns (bool) { return gtWord(x, y); } } -instance word:Add { - function add(l: word, r: word) -> word { +impl Add { + function add(l: word, r: word) returns (word) { return addWord(l, r); } } -instance word:Sub { - function sub(l: word, r: word) -> word { +impl Sub { + function sub(l: word, r: word) returns (word) { return subWord(l, r); } } -function mulWord(l: word, r: word) -> word { +function mulWord(l: word, r: word) returns (word) { return mul(l, r); } -instance word:Mul { - function mul(l: word, r: word) -> word { +impl Mul { + function mul(l: word, r: word) returns (word) { return mulWord(l, r); } } -instance word:Div { - function div(l: word, r: word) -> word { +impl Div { + function div(l: word, r: word) returns (word) { return div(l, r); } } -instance word:Mod { - function mod (l : word, r : word) -> word { +impl Mod { + function mod (l : word, r : word) returns (word) { return mod(l, r); } } -instance word:BitAnd { - function band(l: word, r: word) -> word { +impl BitAnd { + function band(l: word, r: word) returns (word) { return bandWord(l, r); } } -instance word:BitOr { - function bor(l: word, r: word) -> word { +impl BitOr { + function bor(l: word, r: word) returns (word) { return borWord(l, r); } } -instance word:BitXor { - function bxor(l: word, r: word) -> word { +impl BitXor { + function bxor(l: word, r: word) returns (word) { return bxorWord(l, r); } } -instance integer : Eq { - function eq(x : integer, y : integer) -> bool { +impl Eq { + function eq(x : integer, y : integer) returns (bool) { return integerEq(x, y); } } -instance integer : Ord { - function gt(x : integer, y : integer) -> bool { +impl Ord { + function gt(x : integer, y : integer) returns (bool) { return integerLt(y, x); } } -instance integer : Add { - function add(l : integer, r : integer) -> integer { +impl Add { + function add(l : integer, r : integer) returns (integer) { return integerAdd(l, r); } } -instance integer : Sub { - function sub(l : integer, r : integer) -> integer { +impl Sub { + function sub(l : integer, r : integer) returns (integer) { return integerSub(l, r); } } -instance integer : Mul { - function mul(l : integer, r : integer) -> integer { +impl Mul { + function mul(l : integer, r : integer) returns (integer) { return integerMul(l, r); } } -instance word:Bounded { - function maxVal() -> word { +impl Bounded { + function maxVal() returns (word) { return 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; } - function minVal () -> word { + function minVal () returns (word) { return 0; } } -function hash1(x: word) -> word { +function hash1(x: word) returns (word) { mstore(0, x); return keccak256(0, 32); } -function hash2(x: word, y: word) -> word { +function hash2(x: word, y: word) returns (word) { mstore(0, x); mstore(32, y); return keccak256(0, 64); @@ -575,7 +563,7 @@ function hash2(x: word, y: word) -> word { // Zeroes the storage slots in [start, endSlot). Mirrors solc's // clear_storage_range, used when a dynamic array shrinks so that regrowing it // cannot resurrect the old elements. -function clearStorageRange(start: word, endSlot: word) -> () { +function clearStorageRange(start: word, endSlot: word) returns (()) { let i : word = start; for (; i < endSlot; i += 1) { sstore(i, 0); @@ -584,243 +572,242 @@ function clearStorageRange(start: word, endSlot: word) -> () { // --- Value Types --- -forall t. t:Typedef(word) => -function toWord(x:t) -> word { return Typedef.rep(x); } +function toWord(x:t) returns (word) where t: Typedef { return Typedef.rep(x); } -data uint256 = uint256(word); -instance uint256:Typedef(word) { - function abs(w: word) -> uint256 { +enum uint256 { uint256(word) } +impl Typedef { + function abs(w: word) returns (uint256) { return uint256(w); } - function rep(x: uint256) -> word { - match x { - | uint256(w) => return w; - } + function rep(x: uint256) returns (word) { + match (x ) { + case uint256(w) { return w; + } } } } -instance uint256:Add { - function add(x : uint256, y : uint256) -> uint256 { +impl Add { + function add(x : uint256, y : uint256) returns (uint256) { return Typedef.abs(Add.add(Typedef.rep(x), Typedef.rep(y))); } } -instance uint256:Sub { - function sub(x : uint256, y : uint256) -> uint256 { +impl Sub { + function sub(x : uint256, y : uint256) returns (uint256) { return Typedef.abs(Sub.sub(Typedef.rep(x), Typedef.rep(y))); } } -instance uint256:Mul { - function mul(x : uint256, y : uint256) -> uint256 { +impl Mul { + function mul(x : uint256, y : uint256) returns (uint256) { return Typedef.abs(Mul.mul(Typedef.rep(x), Typedef.rep(y))); } } -instance uint256:Div { - function div(x : uint256, y : uint256) -> uint256 { +impl Div { + function div(x : uint256, y : uint256) returns (uint256) { return Typedef.abs(Div.div(Typedef.rep(x), Typedef.rep(y))); } } -instance uint256:Mod { - function mod(x : uint256, y : uint256) -> uint256 { +impl Mod { + function mod(x : uint256, y : uint256) returns (uint256) { return Typedef.abs(Mod.mod(Typedef.rep(x), Typedef.rep(y))); } } -instance uint256:BitAnd { - function band(x : uint256, y : uint256) -> uint256 { +impl BitAnd { + function band(x : uint256, y : uint256) returns (uint256) { return Typedef.abs(BitAnd.band(Typedef.rep(x), Typedef.rep(y))); } } -instance uint256:BitOr { - function bor(x : uint256, y : uint256) -> uint256 { +impl BitOr { + function bor(x : uint256, y : uint256) returns (uint256) { return Typedef.abs(BitOr.bor(Typedef.rep(x), Typedef.rep(y))); } } -instance uint256:BitXor { - function bxor(x : uint256, y : uint256) -> uint256 { +impl BitXor { + function bxor(x : uint256, y : uint256) returns (uint256) { return Typedef.abs(BitXor.bxor(Typedef.rep(x), Typedef.rep(y))); } } -instance uint256:Eq { - function eq(x : uint256, y : uint256) -> bool { +impl Eq { + function eq(x : uint256, y : uint256) returns (bool) { return Eq.eq(Typedef.rep(x), Typedef.rep(y)); } } -instance uint256:Ord { - function gt(x : uint256, y : uint256) -> bool { +impl Ord { + function gt(x : uint256, y : uint256) returns (bool) { return Ord.gt(Typedef.rep(x), Typedef.rep(y)); } } -instance uint256:Bounded { - function maxVal() -> uint256 { +impl Bounded { + function maxVal() returns (uint256) { return uint256(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); } - function minVal () -> uint256 { + function minVal () returns (uint256) { return uint256(0); } } -instance uint256:Int { - function fromInteger(x:integer) -> uint256 { +impl Int { + function fromInteger(x:integer) returns (uint256) { return uint256(wordFromInteger(x)); } } -function addmod(x: uint256, y: uint256, k: uint256) -> uint256 { +function addmod(x: uint256, y: uint256, k: uint256) returns (uint256) { require(k != uint256(0), Error(0x7125cbb9)); // AddModWithZero() return Typedef.abs(addmod_(Typedef.rep(x), Typedef.rep(y), Typedef.rep(k))); } -function mulmod(x: uint256, y: uint256, k: uint256) -> uint256 { +function mulmod(x: uint256, y: uint256, k: uint256) returns (uint256) { require(k != uint256(0), Error(0xdaea23b9)); // MulModWithZero() return Typedef.abs(mulmod_(Typedef.rep(x), Typedef.rep(y), Typedef.rep(k))); } -data byte = byte(word); -instance byte:Typedef(word) { - function abs(w: word) -> byte { +enum byte { byte(word) } +impl Typedef { + function abs(w: word) returns (byte) { return byte(w); } - function rep(x: byte) -> word { - match x { - | byte(w) => return w; - } + function rep(x: byte) returns (word) { + match (x ) { + case byte(w) { return w; + } } } } // --- Address --- -data address = address(word); +enum address { address(word) } -instance address:Typedef(word) { - function rep(x:address) -> word { - match x { - | address(y) => return y; - } +impl Typedef { + function rep(x:address) returns (word) { + match (x ) { + case address(y) { return y; + } } } - function abs(x:word) -> address { + function abs(x:word) returns (address) { return address(x); } } -instance address:Eq { - function eq(x : address , y : address) -> bool { +impl Eq
{ + function eq(x : address , y : address) returns (bool) { return Eq.eq(Typedef.rep(x), Typedef.rep(y)); } } // --- Bytes4 --- -data bytes4 = bytes4(word); +enum bytes4 { bytes4(word) } -instance bytes4:Typedef(word) { - function rep(b : bytes4) -> word { - match b { - | bytes4(w) => return w; - } +impl Typedef { + function rep(b : bytes4) returns (word) { + match (b ) { + case bytes4(w) { return w; + } } } - function abs(w : word) -> bytes4 { + function abs(w : word) returns (bytes4) { return bytes4(w); } } // --- Bytes32 --- -data bytes32 = bytes32(word); +enum bytes32 { bytes32(word) } -instance bytes32:Typedef(word) { - function rep(b : bytes32) -> word { - match b { - | bytes32(w) => return w; - } +impl Typedef { + function rep(b : bytes32) returns (word) { + match (b ) { + case bytes32(w) { return w; + } } } - function abs(w : word) -> bytes32 { + function abs(w : word) returns (bytes32) { return bytes32(w); } } -instance bytes32:Eq { - function eq(x : bytes32, y : bytes32) -> bool { +impl Eq { + function eq(x : bytes32, y : bytes32) returns (bool) { return Eq.eq(Typedef.rep(x), Typedef.rep(y)); } } -instance bytes32:Ord { - function gt(x : bytes32, y : bytes32) -> bool { +impl Ord { + function gt(x : bytes32, y : bytes32) returns (bool) { return Ord.gt(Typedef.rep(x), Typedef.rep(y)); } } // --- Pointers --- -data memory(t) = memory(word); -forall t . instance memory(t) : Typedef(word) { - function abs(x: word) -> memory(t) { +enum memory { memory(word) } +impl Typedef { + function abs(x: word) returns (t memory) { return memory(x); } - function rep(x: memory(t)) -> word { - match x { - | memory(w) => return w; - } + function rep(x: t memory) returns (word) { + match (x ) { + case memory(w) { return w; + } } } } -data storage(t) = storage(word); -forall t . instance storage(t) : Typedef(word) { - function abs(x: word) -> storage(t) { +enum storage { storage(word) } +impl Typedef { + function abs(x: word) returns (t storage) { return storage(x); } - function rep(x: storage(t)) -> word { - match x { - | storage(w) => return w; - } + function rep(x: t storage) returns (word) { + match (x ) { + case storage(w) { return w; + } } } } -data calldata(t) = calldata(word); -forall t . instance calldata(t) : Typedef(word) { - function abs(x: word) -> calldata(t) { +enum calldata { calldata(word) } +impl Typedef { + function abs(x: word) returns (t calldata) { return calldata(x); } - function rep(x: calldata(t)) -> word { - match x { - | calldata(w) => return w; - } + function rep(x: t calldata) returns (word) { + match (x ) { + case calldata(w) { return w; + } } } } -data returndata(t) = returndata(word); -forall t . instance returndata(t) : Typedef(word) { - function abs(x: word) -> returndata(t) { +enum returndata { returndata(word) } +impl Typedef, word> { + function abs(x: word) returns (returndata) { return returndata(x); } - function rep(x: returndata(t)) -> word { - match x { - | returndata(w) => return w; - } + function rep(x: returndata) returns (word) { + match (x ) { + case returndata(w) { return w; + } } } } -data mapping(member, index) = mapping(word) ; +enum mapping { mapping(word) } -data array(member) = array(word) ; +enum array { array(word) } // --- Low-level memory ops -function strlen(s:memory(string)) -> word { - match s { | memory(a) => return mload(a); } +function strlen(s:string memory) returns (word) { + match (s ) { case memory(a) { return mload(a); } } } // --- Memory Utilities --- @@ -829,35 +816,35 @@ function strlen(s:memory(string)) -> word { // The word stored in memory at index 0x40 is used to store the start of the currently unused memory region // returns the value stored in memory(0x40) -function get_free_memory() -> word { +function get_free_memory() returns (word) { return mload(0x40); } // set the value stored in memory(0x40) -function set_free_memory(loc : word) -> () { +function set_free_memory(loc : word) returns (()) { mstore(0x40, loc); } // Allocate memory and update the memory pointer. -function allocate_memory(size : word) -> word { +function allocate_memory(size : word) returns (word) { let ptr = get_free_memory(); set_free_memory(ptr + size); return ptr; } -function allocate_zeroed_memory(size: word) -> word { +function allocate_zeroed_memory(size: word) returns (word) { let ptr = allocate_memory(size); zeroize_memory(ptr, size); return ptr; } // Clears a memory area. -function zeroize_memory(ptr: word, len: word) -> () { +function zeroize_memory(ptr: word, len: word) returns (()) { let end_ptr = ptr + len; // Zero out 32-byte words. for (let i = 0; i < len / 32; i += 1) { - mstore(ptr, 0) + mstore(ptr, 0); ptr += 32; } @@ -869,9 +856,9 @@ function zeroize_memory(ptr: word, len: word) -> () { // types that can be written to and read from at a uint256 index // TODO: this needs to be split into LValue / RValue variants for `=` desugaring -forall t val . class t:IndexAccess(val) { - function get(c: t, i: uint256) -> val; - function set(c: t, i: uint256, v: val) -> (); +trait IndexAccess { + function get(c: t, i: uint256) returns (val); + function set(c: t, i: uint256, v: val) returns (()); } // --- DynArray --- @@ -879,30 +866,30 @@ forall t val . class t:IndexAccess(val) { // Word arrays with a size known only at runtime // types with a size smaller than `word` will not be packed, so a `DynArray(byte)` will waste a lot of space // TODO: storage representation -data DynArray(t); +enum DynArray {} -forall t . t:Typedef(word) => instance memory(DynArray(t)):IndexAccess(t) { - function get(ptr : memory(DynArray(t)), i : uint256) -> t { +impl IndexAccess memory, t> where t: Typedef { + function get(ptr : DynArray memory, i : uint256) returns (t) { let i_: word = Typedef.rep(i); let loc = Typedef.rep(ptr); let res: word; match (i_ > mload(loc)) { - | false => res = mload((i_ * 32) + loc); - | true => out_of_bounds(); - } + case false { res = mload((i_ * 32) + loc); + } case true { out_of_bounds(); + } } return Typedef.abs(res); } - function set(arr : memory(DynArray(t)), i : uint256, val : t) -> () { + function set(arr : DynArray memory, i : uint256, val : t) returns (()) { let i_ : word = Typedef.rep(i); let loc : word = Typedef.rep(arr); - match i_ > mload(loc) { - | false => mstore((i_ * 32) + loc, Typedef.rep(val)); - | true => out_of_bounds(); - } + match (i_ > mload(loc) ) { + case false { mstore((i_ * 32) + loc, Typedef.rep(val)); + } case true { out_of_bounds(); + } } } } -forall t . function allocateDynamicArray(prx : Proxy(t), length : word) -> memory(DynArray(t)) { +function allocateDynamicArray(prx : Proxy, length : word) returns (DynArray memory) { // size of allocation in bytes let sz : word = (length + 1) * 32; @@ -912,7 +899,7 @@ forall t . function allocateDynamicArray(prx : Proxy(t), length : word) -> memor // write array length and return mstore(free, length); - let res : memory(DynArray(t)) = Typedef.abs(free); + let res : DynArray memory = Typedef.abs(free); return res; } @@ -924,15 +911,15 @@ forall t . function allocateDynamicArray(prx : Proxy(t), length : word) -> memor // TODO: IndexAccess for memory(bytes) // TODO: IndexAccess for calldata(bytes) // TODO: IndexAccess for storage(bytes) -data bytes; +enum bytes {} // --- strings --- // TODO: should this be a typedef over `bytes`? -data string; +enum string {} -instance string:Add { - function add(l: string, r: string) -> string { +impl Add { + function add(l: string, r: string) returns (string) { return concatLit(l, r); } } @@ -943,17 +930,17 @@ instance string:Add { // These are intended to be folded by MastEval when their arguments are // statically known string literals. -function concatLit(a:string, b:string) -> string { +function concatLit(a:string, b:string) returns (string) { unimplemented(); // Sanity check if folding ignores it. return ""; } -function strlenLit(a:string) -> word { +function strlenLit(a:string) returns (word) { unimplemented(); // Sanity check if folding ignores it. return 0; } -function keccakLit(a:string) -> word { +function keccakLit(a:string) returns (word) { unimplemented(); // Sanity check if folding ignores it. return 0; } @@ -962,68 +949,68 @@ function keccakLit(a:string) -> word { // A slice is a wrapper around an existing pointer type that extends the // underlying type with information about the size of the data pointed to by `t` -data slice(ptr) = slice(ptr, word); +enum slice { slice(ptr, word) } // --- Word Reader --- // A WordReader is an abstraction over byte indexed structure that can be read in word sized chunks (e.g. calldata / memory) // These let us use the same abi decoding routines for calldata / memory -forall ty . class ty:WordReader { +trait WordReader { // returns the word currently pointed to by the WordReader - function read(reader:ty) -> word; + function read(reader:ty) returns (word); // returns a new WordReader that points to a location `offset` bytes further into the array - function advance(reader:ty, offset:word) -> ty; + function advance(reader:ty, offset:word) returns (ty); // copies a block from the underlying source to memory - function copyToMem(reader:ty, dst: word, cnt: word) -> (); + function copyToMem(reader:ty, dst: word, cnt: word) returns (()); } // WordReader for memory -data MemoryWordReader = MemoryWordReader(word); -instance MemoryWordReader:WordReader { - function read(reader:MemoryWordReader) -> word { - match reader { - | MemoryWordReader(ptr) => return mload(ptr); - } +enum MemoryWordReader { MemoryWordReader(word) } +impl WordReader { + function read(reader:MemoryWordReader) returns (word) { + match (reader ) { + case MemoryWordReader(ptr) { return mload(ptr); + } } } - function advance(reader:MemoryWordReader, offset:word) -> MemoryWordReader { - match reader { - | MemoryWordReader(ptr) => return MemoryWordReader(ptr + offset); - } + function advance(reader:MemoryWordReader, offset:word) returns (MemoryWordReader) { + match (reader ) { + case MemoryWordReader(ptr) { return MemoryWordReader(ptr + offset); + } } } - function copyToMem(reader:MemoryWordReader, dst:word, cnt: word) -> () { - match reader { - | MemoryWordReader(ptr) => mcopy(dst, ptr, cnt); - } + function copyToMem(reader:MemoryWordReader, dst:word, cnt: word) returns (()) { + match (reader ) { + case MemoryWordReader(ptr) { mcopy(dst, ptr, cnt); + } } } } // WordReader for calldata -data CalldataWordReader = CalldataWordReader(word); - -instance CalldataWordReader : Typedef(word) { - function abs(a:word) -> CalldataWordReader { return CalldataWordReader(a); } - function rep(r:CalldataWordReader) -> word { - match r { - | CalldataWordReader(a) => return a; - } +enum CalldataWordReader { CalldataWordReader(word) } + +impl Typedef { + function abs(a:word) returns (CalldataWordReader) { return CalldataWordReader(a); } + function rep(r:CalldataWordReader) returns (word) { + match (r ) { + case CalldataWordReader(a) { return a; + } } } } -instance CalldataWordReader:WordReader { - function read(reader:CalldataWordReader) -> word { - match reader { - | CalldataWordReader(ptr) => return calldataload(ptr); - } +impl WordReader { + function read(reader:CalldataWordReader) returns (word) { + match (reader ) { + case CalldataWordReader(ptr) { return calldataload(ptr); + } } } - function advance(reader:CalldataWordReader, offset:word) -> CalldataWordReader { - match reader { - | CalldataWordReader(ptr) => return CalldataWordReader(ptr + offset); - } + function advance(reader:CalldataWordReader, offset:word) returns (CalldataWordReader) { + match (reader ) { + case CalldataWordReader(ptr) { return CalldataWordReader(ptr + offset); + } } } - function copyToMem(reader:CalldataWordReader, dst:word, cnt: word) -> () { - match reader { - | CalldataWordReader(ptr) => calldatacopy(dst, ptr, cnt); - } + function copyToMem(reader:CalldataWordReader, dst:word, cnt: word) returns (()) { + match (reader ) { + case CalldataWordReader(ptr) { calldatacopy(dst, ptr, cnt); + } } } } @@ -1031,18 +1018,18 @@ instance CalldataWordReader:WordReader { // The HasWordReader class defines the types for which a WordReader can be produced // We define instances for memory(bytes) and calldata(bytes) -forall self reader . class self:HasWordReader(reader) { - function getWordReader(x:self) -> reader; +trait HasWordReader { + function getWordReader(x:self) returns (reader); } -instance memory(bytes):HasWordReader(MemoryWordReader) { - function getWordReader(x:memory(bytes)) -> MemoryWordReader { +impl HasWordReader { + function getWordReader(x:bytes memory) returns (MemoryWordReader) { return MemoryWordReader(Typedef.rep(x)); } } -instance calldata(bytes):HasWordReader(CalldataWordReader) { - function getWordReader(x:calldata(bytes)) -> CalldataWordReader { +impl HasWordReader { + function getWordReader(x:bytes calldata) returns (CalldataWordReader) { return CalldataWordReader(Typedef.rep(x)); } } @@ -1051,15 +1038,15 @@ instance calldata(bytes):HasWordReader(CalldataWordReader) { // A MemoryType instance abstracts over type specific logic related to memory // layout, allowing us to write code that is generic over which type is held in memory -forall self loadedType. class self:MemoryType(loadedType) { +trait MemoryType { // Proxy needed becaused class methods must mention strong type params // loads an instance of `loadedType` from an instance of `self` located at `loc` in memory - function loadFromMemory(p:Proxy(self), loc:word) -> loadedType; + function loadFromMemory(p:Proxy, loc:word) returns (loadedType); } // A uint256 can be loaded from memory and pushed straight onto the stack -instance uint256:MemoryType(uint256) { - function loadFromMemory(p:Proxy(uint256), loc:word) -> uint256 { +impl MemoryType { + function loadFromMemory(p:Proxy, loc:word) returns (uint256) { return uint256(mload(loc)); } } @@ -1095,107 +1082,106 @@ forall ty deref . ty:MemoryType(deref), deref:ABIEncode => instance memory(ty):A // The ABITuple type lets us reiintroduce this distinction: // `ABITuple((1,(1,1))` should be treated as `(1,1,1)` for the purposes of ABI // encoding / decoding. -data ABITuple(tuple) = ABITuple(tuple); +enum ABITuple { ABITuple(tuple) } -forall t . instance ABITuple(t):Typedef(t) { - function abs(t: t) -> ABITuple(t) { +impl Typedef, t> { + function abs(t: t) returns (ABITuple) { return ABITuple(t); } - function rep(x: ABITuple(t)) -> t { - match x { - | ABITuple(v) => return v; - } + function rep(x: ABITuple) returns (t) { + match (x ) { + case ABITuple(v) { return v; + } } } } // --- ABI Metadata --- // Statically knowable ABI related metadata about `self` -forall self . class self:ABIAttribs { +trait ABIAttribs { // how many bytes should be used for the head portion of the abi encoding of `self` - function headSize(ty:Proxy(self)) -> word; + function headSize(ty:Proxy) returns (word); // whether or not `self` is a fully static type - function isStatic(ty:Proxy(self)) -> bool; + function isStatic(ty:Proxy) returns (bool); } -forall t. -default instance t:ABIAttribs { - function headSize(ty : Proxy(t)) -> word { return 32; } - function isStatic(ty : Proxy(t)) -> bool { return true; } +default impl ABIAttribs { + function headSize(ty : Proxy) returns (word) { return 32; } + function isStatic(ty : Proxy) returns (bool) { return true; } } -instance ():ABIAttribs { - function headSize(ty : Proxy(())) -> word { return 0; } - function isStatic(ty : Proxy(())) -> bool { return true; } +impl ABIAttribs<()> { + function headSize(ty : Proxy<()>) returns (word) { return 0; } + function isStatic(ty : Proxy<()>) returns (bool) { return true; } } -instance uint256:ABIAttribs { - function headSize(ty : Proxy(uint256)) -> word { return 32; } - function isStatic(ty : Proxy(uint256)) -> bool { return true; } +impl ABIAttribs { + function headSize(ty : Proxy) returns (word) { return 32; } + function isStatic(ty : Proxy) returns (bool) { return true; } } -instance address:ABIAttribs { - function headSize(ty : Proxy(address)) -> word { return 32; } - function isStatic(ty : Proxy(address)) -> bool { return true; } +impl ABIAttribs
{ + function headSize(ty : Proxy
) returns (word) { return 32; } + function isStatic(ty : Proxy
) returns (bool) { return true; } } -forall t . instance DynArray(t):ABIAttribs { - function headSize(ty : Proxy(DynArray(t))) -> word { return 32; } - function isStatic(ty : Proxy(DynArray(t))) -> bool { return false; } +impl ABIAttribs> { + function headSize(ty : Proxy>) returns (word) { return 32; } + function isStatic(ty : Proxy>) returns (bool) { return false; } } -instance string:ABIAttribs { - function headSize(ty: Proxy(string)) -> word { return 32; } - function isStatic(ty : Proxy(string)) -> bool { return false; } +impl ABIAttribs { + function headSize(ty: Proxy) returns (word) { return 32; } + function isStatic(ty : Proxy) returns (bool) { return false; } } // computes the attribs for a pair of two types that implement attribs -forall a b . a:ABIAttribs, b:ABIAttribs => instance (a,b):ABIAttribs { - function headSize(ty : Proxy((a,b))) -> word { - let pa : Proxy(a); - let pb : Proxy(b); +impl ABIAttribs<(a, b)> where a: ABIAttribs, b: ABIAttribs { + function headSize(ty : Proxy<(a, b)>) returns (word) { + let pa : Proxy; + let pb : Proxy; let sza = ABIAttribs.headSize(pa); let szb = ABIAttribs.headSize(pb); return sza + szb; } - function isStatic(ty : Proxy((a,b))) -> bool { - let pa : Proxy(a); - let pb : Proxy(b); + function isStatic(ty : Proxy<(a, b)>) returns (bool) { + let pa : Proxy; + let pb : Proxy; return and(ABIAttribs.isStatic(pa), ABIAttribs.isStatic(pb)); } } // if an abi tuple contains dynamic elems we store it in the tail, otherwise we // treat it the same as a series of nested pairs -forall tuple . tuple:ABIAttribs => instance ABITuple(tuple):ABIAttribs { - function headSize(ty : Proxy(ABITuple(tuple))) -> word { - let px : Proxy(tuple); - match ABIAttribs.isStatic(px) { - | true => return ABIAttribs.headSize(px); - | false => return 32; - } - } - function isStatic(ty : Proxy(ABITuple(tuple))) -> bool { - let px : Proxy(tuple); +impl ABIAttribs> where tuple: ABIAttribs { + function headSize(ty : Proxy>) returns (word) { + let px : Proxy; + match (ABIAttribs.isStatic(px) ) { + case true { return ABIAttribs.headSize(px); + } case false { return 32; + } } + } + function isStatic(ty : Proxy>) returns (bool) { + let px : Proxy; return ABIAttribs.isStatic(px); } } // for pointer types we fetch the attribs of the pointed to type, not the pointer itself -forall ty . ty:ABIAttribs => instance memory(ty):ABIAttribs { - function headSize(p : Proxy(memory(ty))) -> word { - let px : Proxy(ty); +impl ABIAttribs where ty: ABIAttribs { + function headSize(p : Proxy) returns (word) { + let px : Proxy; return ABIAttribs.headSize(px); } - function isStatic(p : Proxy(memory(ty))) -> bool { - let px : Proxy(ty); + function isStatic(p : Proxy) returns (bool) { + let px : Proxy; return ABIAttribs.isStatic(px); } } -forall ty . ty:ABIAttribs => instance calldata(ty):ABIAttribs { - function headSize(p : Proxy(calldata(ty))) -> word { - let px : Proxy(ty); +impl ABIAttribs where ty: ABIAttribs { + function headSize(p : Proxy) returns (word) { + let px : Proxy; return ABIAttribs.headSize(px); } - function isStatic(ty : Proxy(calldata(ty))) -> bool { - let px : Proxy(ty); + function isStatic(ty : Proxy) returns (bool) { + let px : Proxy; return ABIAttribs.isStatic(px); } } @@ -1205,61 +1191,61 @@ forall ty . ty:ABIAttribs => instance calldata(ty):ABIAttribs { // top level encoding function. // abi encodes an instance of `ty` and returns a pointer to the result -forall ty . ty:ABIAttribs, ty:ABIEncode => function abi_encode(val : ty) -> memory(bytes) { +function abi_encode(val : ty) returns (bytes memory) where ty: ABIAttribs, ty: ABIEncode { let free = get_free_memory(); - let tail = ABIEncode.encodeInto(val, free, 0, free + ABIAttribs.headSize(Proxy : Proxy(ty))); + let tail = ABIEncode.encodeInto(val, free, 0, free + ABIAttribs.headSize(Proxy as Proxy)); set_free_memory(tail); return memory(free); } // types that can be abi encoded -forall self . class self:ABIEncode { +trait ABIEncode { // abi encodes an instance of self into a memory region starting at basePtr // offset gives the offset in memory from basePtr to the first empty byte of the head // tail gives the index in memory of the first empty byte of the tail - function encodeInto(x:self, basePtr:word, offset:word, tail:word) -> word /* newTail */; + function encodeInto(x:self, basePtr:word, offset:word, tail:word) returns (word) /* newTail */; } -instance uint256:ABIEncode { +impl ABIEncode { // a unit256 is written directly into the head - function encodeInto(x:uint256, basePtr:word, offset:word, tail:word) -> word { + function encodeInto(x:uint256, basePtr:word, offset:word, tail:word) returns (word) { let repx : word = Typedef.rep(x); mstore(basePtr + offset, repx); return tail; } } -instance address:ABIEncode { +impl ABIEncode
{ // an address is written directly into the head (into a full 32-byte slot) - function encodeInto(x:address, basePtr:word, offset:word, tail:word) -> word { + function encodeInto(x:address, basePtr:word, offset:word, tail:word) returns (word) { let repx : word = Typedef.rep(x); mstore(basePtr + offset, repx); return tail; } } -instance bytes32:ABIEncode { +impl ABIEncode { // a bytes32 is written directly into the head - function encodeInto(x:bytes32, basePtr:word, offset:word, tail:word) -> word { + function encodeInto(x:bytes32, basePtr:word, offset:word, tail:word) returns (word) { let repx : word = Typedef.rep(x); mstore(basePtr + offset, repx); return tail; } } -instance bool:ABIEncode { - function encodeInto(x:bool, basePtr:word, offset:word, tail:word) -> word { +impl ABIEncode { + function encodeInto(x:bool, basePtr:word, offset:word, tail:word) returns (word) { let repx : word = frombool(x); mstore(basePtr + offset, repx); return tail; } } -function round_up_to_mul_of_32(value:word) -> word { +function round_up_to_mul_of_32(value:word) returns (word) { return and_(value + 31, not_(31)); } -function encodeIntoFromBytesLike(srcPtr:word, basePtr:word, offset:word, tail:word) -> word { +function encodeIntoFromBytesLike(srcPtr:word, basePtr:word, offset:word, tail:word) returns (word) { let length = mload(srcPtr); let total = length + 32; mstore(basePtr + offset, tail - basePtr); @@ -1269,14 +1255,14 @@ function encodeIntoFromBytesLike(srcPtr:word, basePtr:word, offset:word, tail:wo return tail + rounded; } -instance memory(string):ABIEncode { - function encodeInto(x:memory(string), basePtr:word, offset:word, tail:word) -> word { +impl ABIEncode { + function encodeInto(x:string memory, basePtr:word, offset:word, tail:word) returns (word) { return encodeIntoFromBytesLike(Typedef.rep(x), basePtr, offset, tail); } } -instance memory(bytes):ABIEncode { - function encodeInto(x:memory(bytes), basePtr:word, offset:word, tail:word) -> word { +impl ABIEncode { + function encodeInto(x:bytes memory, basePtr:word, offset:word, tail:word) returns (word) { return encodeIntoFromBytesLike(Typedef.rep(x), basePtr, offset, tail); } } @@ -1286,9 +1272,8 @@ instance memory(bytes):ABIEncode { // on-the-wire tail of `t[]` so the body can be `mcopy`d verbatim. // `memory(DynArray(t)):ABIAttribs` is already derivable from the generic // `memory(ty):ABIAttribs` + `DynArray(t):ABIAttribs` instances above. -forall t . t:Typedef(word) => -instance memory(DynArray(t)):ABIEncode { - function encodeInto(x:memory(DynArray(t)), basePtr:word, offset:word, tail:word) -> word { +impl ABIEncode memory> where t: Typedef { + function encodeInto(x:DynArray memory, basePtr:word, offset:word, tail:word) returns (word) { let srcPtr : word = Typedef.rep(x); let len : word = mload(srcPtr); let totalBytes : word = (len + 1) * 32; @@ -1308,47 +1293,47 @@ instance memory(DynArray(t)):ABIEncode { } } -instance ():ABIEncode { +impl ABIEncode<()> { // a unit256 is written directly into the head - function encodeInto(x:(), basePtr:word, offset:word, tail:word) -> word { + function encodeInto(x:(), basePtr:word, offset:word, tail:word) returns (word) { return tail; } } // abi encoding for a pair of two encodable types -forall a b . a:ABIAttribs, a:ABIEncode, b:ABIEncode => instance (a,b):ABIEncode { - function encodeInto(x: (a,b), basePtr: word, offset: word, tail: word) -> word { - match x { - | (l,r) => +impl ABIEncode<(a, b)> where a: ABIAttribs, a: ABIEncode, b: ABIEncode { + function encodeInto(x: (a, b), basePtr: word, offset: word, tail: word) returns (word) { + match (x ) { + case (l,r) { let newTail = ABIEncode.encodeInto(l, basePtr, offset, tail); - let pa : Proxy(a); + let pa : Proxy; let a_sz = ABIAttribs.headSize(pa); return ABIEncode.encodeInto(r, basePtr, offset + a_sz, newTail); - } + } } } } // abi encoding for an ABITuple of encodable types // TODO: is this correct? -forall tuple . tuple:ABIEncode, tuple:ABIAttribs => instance ABITuple(tuple):ABIEncode { - function encodeInto(x:ABITuple(tuple), basePtr:word, offset:word, tail:word) -> word { - let prx : Proxy(tuple); - match ABIAttribs.isStatic(prx) { +impl ABIEncode> where tuple: ABIEncode, tuple: ABIAttribs { + function encodeInto(x:ABITuple, basePtr:word, offset:word, tail:word) returns (word) { + let prx : Proxy; + match (ABIAttribs.isStatic(prx) ) { // if the tuple contains only static elements then we encode it in the head - | true => return ABIEncode.encodeInto(Typedef.rep(x), basePtr, offset, tail); + case true { return ABIEncode.encodeInto(Typedef.rep(x), basePtr, offset, tail); // if the tuple contains dynamically sized elements then we store a // pointer in the head, and encode the tuple into the tail - | false => + } case false { // store the length of the head in basePtr mstore(basePtr, tail - basePtr); // encode the underlying tuple into the tail - let headSize = ABIAttribs.headSize(Proxy : Proxy(tuple)); + let headSize = ABIAttribs.headSize(Proxy as Proxy); basePtr = tail; tail = tail + headSize; return ABIEncode.encodeInto(Typedef.rep(x), basePtr, 0, tail); - } + } } } } @@ -1356,72 +1341,70 @@ forall tuple . tuple:ABIEncode, tuple:ABIAttribs => instance ABITuple(tuple):ABI // Top level decoding function. // abi decodes an instance of `decodable` into a `ty` -forall decodable reader ty decoded . decodable:HasWordReader(reader), ABIDecoder(ty, reader):ABIDecode(decoded) => -function abi_decode(decodable:decodable, pty:Proxy(ty), prdr:Proxy(reader)) -> decoded { - let decoder : ABIDecoder(ty, reader) = ABIDecoder(HasWordReader.getWordReader(decodable)); +function abi_decode(decodable:decodable, pty:Proxy, prdr:Proxy) returns (decoded) where decodable: HasWordReader, ABIDecoder: ABIDecode { + let decoder : ABIDecoder = ABIDecoder(HasWordReader.getWordReader(decodable)); return ABIDecode.decode(decoder, 0); } -forall decoder decoded . class decoder:ABIDecode(decoded) { - function decode(ptr:decoder, currentHeadOffset:word) -> decoded; +trait ABIDecode { + function decode(ptr:decoder, currentHeadOffset:word) returns (decoded); } // An ABI Decoder for `ty` from `reader` // This lets us abstract over memory and calldata when decoding -data ABIDecoder(ty, reader) = ABIDecoder(reader); +enum ABIDecoder { ABIDecoder(reader) } // If `reader` is a `WordReader` then so is our `ABIDecoder` -forall ty reader . reader:WordReader => instance ABIDecoder(ty, reader):WordReader { - function read(decoder:ABIDecoder(ty, reader)) -> word { - match decoder { - | ABIDecoder(ptr) => return WordReader.read(ptr); - } +impl WordReader> where reader: WordReader { + function read(decoder:ABIDecoder) returns (word) { + match (decoder ) { + case ABIDecoder(ptr) { return WordReader.read(ptr); + } } } - function advance(decoder:ABIDecoder(ty, reader), offset:word) -> ABIDecoder(ty, reader) { - match decoder { - | ABIDecoder(ptr) => return ABIDecoder(WordReader.advance(ptr, offset)); - } + function advance(decoder:ABIDecoder, offset:word) returns (ABIDecoder) { + match (decoder ) { + case ABIDecoder(ptr) { return ABIDecoder(WordReader.advance(ptr, offset)); + } } } - function copyToMem(decoder:ABIDecoder(ty, reader), dst:word, cnt: word) -> () { - match decoder { - | ABIDecoder(ptr) => WordReader.copyToMem(ptr, dst, cnt); - } + function copyToMem(decoder:ABIDecoder, dst:word, cnt: word) returns (()) { + match (decoder ) { + case ABIDecoder(ptr) { WordReader.copyToMem(ptr, dst, cnt); + } } } } // ABI Decoding for uint256 -forall reader . reader:WordReader => instance ABIDecoder(uint256, reader):ABIDecode(uint256) { - function decode(ptr:ABIDecoder(uint256, reader), currentHeadOffset:word) -> uint256 { - return Typedef.abs(WordReader.read(WordReader.advance(ptr, currentHeadOffset))) : uint256; +impl ABIDecode, uint256> where reader: WordReader { + function decode(ptr:ABIDecoder, currentHeadOffset:word) returns (uint256) { + return Typedef.abs(WordReader.read(WordReader.advance(ptr, currentHeadOffset))) as uint256; } } // ABI Decoding for bytes32 -forall reader . reader:WordReader => instance ABIDecoder(bytes32, reader):ABIDecode(bytes32) { - function decode(ptr:ABIDecoder(bytes32, reader), currentHeadOffset:word) -> bytes32 { - return Typedef.abs(WordReader.read(WordReader.advance(ptr, currentHeadOffset))) : bytes32; +impl ABIDecode, bytes32> where reader: WordReader { + function decode(ptr:ABIDecoder, currentHeadOffset:word) returns (bytes32) { + return Typedef.abs(WordReader.read(WordReader.advance(ptr, currentHeadOffset))) as bytes32; } } // ABI Decoding for address -forall reader . reader:WordReader => instance ABIDecoder(address, reader):ABIDecode(address) { - function decode(ptr:ABIDecoder(address, reader), currentHeadOffset:word) -> address { +impl ABIDecode, address> where reader: WordReader { + function decode(ptr:ABIDecoder, currentHeadOffset:word) returns (address) { let raw = WordReader.read(WordReader.advance(ptr, currentHeadOffset)); require(shr(160, raw) == 0, Error(0x7cc04fa7)); // DirtyHigherBitsForAddress() - return Typedef.abs(raw) : address; + return Typedef.abs(raw) as address; } } -forall reader . reader:WordReader => instance ABIDecoder((), reader):ABIDecode(()) { - function decode(ptr:ABIDecoder((), reader), currentHeadOffset:word) -> () { - return (); +impl ABIDecode, ()> where reader: WordReader { + function decode(ptr:ABIDecoder<(), reader>, currentHeadOffset:word) returns (()) { + return; } } // ABI decoding for bytes/strings (only in memory) -forall a ptrtype reader. reader:WordReader => -function decodeBytesLike(ptr:ABIDecoder(memory(a), reader), currentHeadOffset:word) -> memory(a) { +function decodeBytesLike(ptr:ABIDecoder, currentHeadOffset:word) returns (a memory) where reader: WordReader { let tmp:word; let headRdr = WordReader.advance(ptr, currentHeadOffset); let tailPtr : word = WordReader.read(headRdr); @@ -1437,82 +1420,71 @@ function decodeBytesLike(ptr:ABIDecoder(memory(a), reader), currentHeadOffset:wo } // ABI decoding for strings (only in memory) -forall reader. reader : WordReader => -instance ABIDecoder(memory(string), reader):ABIDecode(memory(string)) -{ - function decode(ptr:ABIDecoder(memory(string), reader), currentHeadOffset:word) -> memory(string) { +impl ABIDecode, string memory> where reader: WordReader { + function decode(ptr:ABIDecoder, currentHeadOffset:word) returns (string memory) { return decodeBytesLike(ptr, currentHeadOffset); } } // ABI decoding for bytes (only in memory) -forall reader. reader : WordReader => -instance ABIDecoder(memory(bytes), reader):ABIDecode(memory(bytes)) -{ - function decode(ptr:ABIDecoder(memory(bytes), reader), currentHeadOffset:word) -> memory(bytes) { +impl ABIDecode, bytes memory> where reader: WordReader { + function decode(ptr:ABIDecoder, currentHeadOffset:word) returns (bytes memory) { return decodeBytesLike(ptr, currentHeadOffset); } } // ABI decoding for a pair of decodable values // FAIL: Coverage -forall a b a_decoded b_decoded reader . reader:WordReader, ABIDecoder(b,reader):ABIDecode(b_decoded), ABIDecoder(a,reader):ABIDecode(a_decoded), a:ABIAttribs => instance ABIDecoder((a,b), reader):ABIDecode((a_decoded,b_decoded)) -{ - function decode(ptr:ABIDecoder((a,b), reader), currentHeadOffset:word) -> (a_decoded, b_decoded) { - match ptr { - | ABIDecoder(rdr) => - let prx : Proxy(a); - let decoder_a : ABIDecoder(a, reader) = ABIDecoder(rdr); - let decoder_b : ABIDecoder(b, reader) = ABIDecoder(rdr); +impl ABIDecode, (a_decoded, b_decoded)> where reader: WordReader, ABIDecoder: ABIDecode, ABIDecoder: ABIDecode, a: ABIAttribs { + function decode(ptr:ABIDecoder<(a, b), reader>, currentHeadOffset:word) returns ((a_decoded, b_decoded)) { + match (ptr ) { + case ABIDecoder(rdr) { + let prx : Proxy; + let decoder_a : ABIDecoder = ABIDecoder(rdr); + let decoder_b : ABIDecoder = ABIDecoder(rdr); let a_val : a_decoded = ABIDecode.decode(decoder_a, currentHeadOffset); let b_val : b_decoded = ABIDecode.decode(decoder_b, currentHeadOffset + ABIAttribs.headSize(prx)); return (a_val, b_val); - } + } } } } -forall reader tuple tuple_decoded . reader:WordReader, tuple:ABIDecode(tuple_decoded), tuple:ABIAttribs => - instance ABIDecoder(ABITuple(tuple), reader):ABIDecode(tuple_decoded) -{ - function decode(ptr:ABIDecoder(ABITuple(tuple), reader), currentHeadOffset:word) -> tuple_decoded { - let prx : Proxy(tuple); - match ABIAttribs.isStatic(prx) { - | true => return ABIDecode.decode(WordReader.advance(ptr, currentHeadOffset), 0); - | false => +impl ABIDecode, reader>, tuple_decoded> where reader: WordReader, tuple: ABIDecode, tuple: ABIAttribs { + function decode(ptr:ABIDecoder, reader>, currentHeadOffset:word) returns (tuple_decoded) { + let prx : Proxy; + match (ABIAttribs.isStatic(prx) ) { + case true { return ABIDecode.decode(WordReader.advance(ptr, currentHeadOffset), 0); + } case false { let tailPtr = WordReader.read(ptr); return ABIDecode.decode(WordReader.advance(ptr, tailPtr), 0); - } + } } } } -forall reader tuple tuple_decoded . reader:WordReader, tuple:ABIDecode(tuple_decoded), tuple:ABIAttribs => - instance ABIDecoder(memory(ABITuple(tuple)), reader):ABIDecode(memory(tuple_decoded)) -{ - function decode(ptr:ABIDecoder(memory(ABITuple(tuple)), reader), currentHeadOffset:word) -> memory(tuple_decoded) { - let prx : Proxy(tuple); - match ABIAttribs.isStatic(prx) { - | true => return ABIDecode.decode(WordReader.advance(ptr, currentHeadOffset), 0); - | false => +impl ABIDecode memory, reader>, tuple_decoded memory> where reader: WordReader, tuple: ABIDecode, tuple: ABIAttribs { + function decode(ptr:ABIDecoder memory, reader>, currentHeadOffset:word) returns (tuple_decoded memory) { + let prx : Proxy; + match (ABIAttribs.isStatic(prx) ) { + case true { return ABIDecode.decode(WordReader.advance(ptr, currentHeadOffset), 0); + } case false { let tailPtr = WordReader.read(ptr); return ABIDecode.decode(WordReader.advance(ptr, tailPtr), 0); - } + } } } } -forall reader baseType baseType_decoded .baseType : ABIAttribs, reader:WordReader, ABIDecoder(baseType, reader):ABIDecode(baseType_decoded) => - instance ABIDecoder(memory(DynArray(baseType)), reader):ABIDecode(memory(DynArray(baseType_decoded))) -{ - function decode(ptr:ABIDecoder(memory(DynArray(baseType)), reader), currentHeadOffset:word) -> memory(DynArray(baseType_decoded)) { +impl ABIDecode memory, reader>, DynArray memory> where baseType: ABIAttribs, reader: WordReader, ABIDecoder: ABIDecode { + function decode(ptr:ABIDecoder memory, reader>, currentHeadOffset:word) returns (DynArray memory) { let arrayPtr = WordReader.advance(ptr, currentHeadOffset); let length = WordReader.read(arrayPtr); // this trigger a missing typedef constraint // let elementPtr:ABIDecoder(baseType, reader) = Typedef.abs(WordReader.advance(arrayPtr, 32)); arrayPtr = WordReader.advance(arrayPtr, 32); - let prx : Proxy(baseType_decoded); - let result : memory(DynArray(baseType_decoded)) = allocateDynamicArray(prx, length); + let prx : Proxy; + let result : DynArray memory = allocateDynamicArray(prx, length); let offset : word = 0; - let prx : Proxy(baseType); + let prx : Proxy; let elementHeadSize : word = ABIAttribs.headSize(prx); // TODO: surface level loops @@ -1526,18 +1498,14 @@ forall reader baseType baseType_decoded .baseType : ABIAttribs, reader:WordReade } } -forall ty reader. -function getReader(d:ABIDecoder(ty, reader)) -> reader { - match d { - | ABIDecoder(rdr) => return rdr; - } +function getReader(d:ABIDecoder) returns (reader) { + match (d ) { + case ABIDecoder(rdr) { return rdr; + } } } -forall baseType baseType_decoded . ABIDecoder(baseType, CalldataWordReader):ABIDecode(baseType_decoded), - baseType : WordReader => - instance ABIDecoder(calldata(DynArray(baseType)), CalldataWordReader):ABIDecode(calldata(DynArray(baseType_decoded))) - { - function decode(ptr:ABIDecoder(calldata(DynArray(baseType)), CalldataWordReader), currentHeadOffset:word) -> calldata(DynArray(baseType_decoded)) { +impl ABIDecode calldata, CalldataWordReader>, DynArray calldata> where ABIDecoder: ABIDecode, baseType: WordReader { + function decode(ptr:ABIDecoder calldata, CalldataWordReader>, currentHeadOffset:word) returns (DynArray calldata) { let newptr = WordReader.advance(ptr, currentHeadOffset); let reader: CalldataWordReader = getReader(newptr); let addr: word = Typedef.rep(reader); @@ -1556,32 +1524,30 @@ forall baseType baseType_decoded . ABIDecoder(baseType, CalldataWordReader):ABID */ -pragma no-patterson-condition RVA, Assign; -pragma no-coverage-condition MemberAccessProxy, LVA, RVA, CStructField, Assign; -pragma no-bounded-variable-condition LVA, RVA; +pragma solcore noPattersonCondition RVA, Assign; +pragma solcore noCoverageCondition MemberAccessProxy, LVA, RVA, CStructField, Assign; +pragma solcore noBoundVariableCondition LVA, RVA; // -- storage -forall self. -class self:StorageSize { - function size(x:Proxy(self)) -> word; +trait StorageSize { + function size(x:Proxy) returns (word); } -forall self. -default instance self:StorageSize { - function size(x:Proxy(self)) -> word { +default impl StorageSize { + function size(x:Proxy) returns (word) { return 1; } } -instance ():StorageSize { - function size(x:Proxy(())) -> word { +impl StorageSize<()> { + function size(x:Proxy<()>) returns (word) { return 0; } } -instance word:StorageSize { - function size(x:Proxy(word)) -> word { +impl StorageSize { + function size(x:Proxy) returns (word) { return 1; } } @@ -1592,60 +1558,59 @@ instance uint:StorageSize { } } */ -instance uint256:StorageSize { - function size(x:Proxy(uint256)) -> word { +impl StorageSize { + function size(x:Proxy) returns (word) { return 1; } } -instance bytes32:StorageSize { - function size(x:Proxy(bytes32)) -> word { +impl StorageSize { + function size(x:Proxy) returns (word) { return 1; } } -instance address:StorageSize { - function size(x:Proxy(address)) -> word { +impl StorageSize
{ + function size(x:Proxy
) returns (word) { return 1; } } -instance string:StorageSize { - function size(x:Proxy(string)) -> word { +impl StorageSize { + function size(x:Proxy) returns (word) { return 1; } } -instance memory(string):StorageSize { - function size(x:Proxy(memory(string))) -> word { +impl StorageSize { + function size(x:Proxy) returns (word) { return 1; } } -instance bytes:StorageSize { - function size(x:Proxy(bytes)) -> word { +impl StorageSize { + function size(x:Proxy) returns (word) { return 1; } } -instance memory(bytes):StorageSize { - function size(x:Proxy(memory(bytes))) -> word { +impl StorageSize { + function size(x:Proxy) returns (word) { return 1; } } -forall a b. a:StorageSize, b:StorageSize => instance (a,b):StorageSize { - function size(x:Proxy((a,b))) -> word { - let a_sz:word = StorageSize.size(Proxy:Proxy(a)); - let b_sz:word = StorageSize.size(Proxy:Proxy(b)); +impl StorageSize<(a, b)> where a: StorageSize, b: StorageSize { + function size(x:Proxy<(a, b)>) returns (word) { + let a_sz:word = StorageSize.size(Proxy as Proxy); + let b_sz:word = StorageSize.size(Proxy as Proxy); return a_sz + b_sz; } } -forall self. -class self:StorageType { - function load(ptr:word) -> self; - function store(ptr:word, value:self) -> (); +trait StorageType { + function load(ptr:word) returns (self); + function store(ptr:word, value:self) returns (()); } // How to copy one element of type self from one storage slot to another. @@ -1655,49 +1620,46 @@ class self:StorageType { // storage(array(self)) without also gating CanStore.load, which must stay // unconstrained, a field read has to yield the array's storage reference. // Instances live below, next to the CanStore instances the dynamic ones rely on. -forall self. -class self:StorageCopy { - function copySlot(dst:storage(self), src:storage(self)) -> (); +trait StorageCopy { + function copySlot(dst:self storage, src:self storage) returns (()); } -instance word:StorageType { - function load(ptr:word) -> word { +impl StorageType { + function load(ptr:word) returns (word) { return sload(ptr); } - function store(ptr:word, value:word) -> () { + function store(ptr:word, value:word) returns (()) { sstore(ptr, value); } } -instance uint256:StorageType { - function load(ptr:word) -> uint256 { return uint256(StorageType.load(ptr):word); } - function store(ptr:word, value:uint256) -> () { StorageType.store(ptr, Typedef.rep(value):word); } +impl StorageType { + function load(ptr:word) returns (uint256) { return uint256(StorageType.load(ptr) as word); } + function store(ptr:word, value:uint256) returns (()) { StorageType.store(ptr, Typedef.rep(value) as word); } } -instance bytes32:StorageType { - function load(ptr:word) -> bytes32 { return bytes32(StorageType.load(ptr):word); } - function store(ptr:word, value:bytes32) -> () { StorageType.store(ptr, Typedef.rep(value):word); } +impl StorageType { + function load(ptr:word) returns (bytes32) { return bytes32(StorageType.load(ptr) as word); } + function store(ptr:word, value:bytes32) returns (()) { StorageType.store(ptr, Typedef.rep(value) as word); } } -instance address:StorageType { - function load(ptr:word) -> address { return address(StorageType.load(ptr):word); } - function store(ptr:word, value:address) -> () { StorageType.store(ptr, Typedef.rep(value):word); } +impl StorageType
{ + function load(ptr:word) returns (address) { return address(StorageType.load(ptr) as word); } + function store(ptr:word, value:address) returns (()) { StorageType.store(ptr, Typedef.rep(value) as word); } } // -- structure fields (including contract fields) -forall self fieldType offsetType. -class self:CStructField(fieldType, offsetType) {} -data StructField(structType, fieldSelector) = StructField(structType); +trait CStructField {} +enum StructField { StructField(structType) } -data MemberAccessProxy(a, field, fieldtype, offset) = MemberAccessProxy(a, field); +enum MemberAccessProxy { MemberAccessProxy(a, field) } -forall a field fieldType storageType offset . -function memberAccessBase(x:MemberAccessProxy(a, field, fieldType, offset)) -> a { - match x { - | MemberAccessProxy(y,z) => return y; - } +function memberAccessBase(x:MemberAccessProxy) returns (a) { + match (x ) { + case MemberAccessProxy(y,z) { return y; + } } } @@ -1705,25 +1667,17 @@ function memberAccessBase(x:MemberAccessProxy(a, field, fieldType, offset)) -> // Contract field access // ------------------------------------------------------------------ -forall cxt fieldSelector loadType offsetType storageType -. StructField(ContractStorage(cxt), fieldSelector) :CStructField(storage(storageType), offsetType) -, offsetType : StorageSize -, storage(storageType): CanStore(loadType) -=> instance MemberAccessProxy(ContractStorage(cxt), fieldSelector, loadType, offsetType) : LVA (storage(storageType)) { - function acc (x : MemberAccessProxy(ContractStorage(cxt), fieldSelector, loadType, offsetType)) -> storage(storageType) { - let offset : word = StorageSize.size(Proxy : Proxy(offsetType)) ; - return storage(offset):storage(storageType); +impl LVA, fieldSelector, loadType, offsetType>, storageType storage> where StructField, fieldSelector>: CStructField, offsetType: StorageSize, storageType storage: CanStore { + function acc (x : MemberAccessProxy, fieldSelector, loadType, offsetType>) returns (storageType storage) { + let offset : word = StorageSize.size(Proxy as Proxy) ; + return storage(offset) as storageType storage; } } -forall cxt fieldSelector loadType offsetType storageType - . StructField(ContractStorage(cxt), fieldSelector):CStructField(storage(storageType), offsetType) - , storage(storageType):CanStore(loadType) - , offsetType:StorageSize - => instance MemberAccessProxy(ContractStorage(cxt), fieldSelector, loadType, offsetType):RVA(loadType) { - function acc(x:MemberAccessProxy(ContractStorage(cxt), fieldSelector, loadType, offsetType)) -> loadType { - let offset:word = StorageSize.size(Proxy:Proxy(offsetType)); - return CanStore.load(storage(offset):storage(storageType)):loadType; +impl RVA, fieldSelector, loadType, offsetType>, loadType> where StructField, fieldSelector>: CStructField, storageType storage: CanStore, offsetType: StorageSize { + function acc(x:MemberAccessProxy, fieldSelector, loadType, offsetType>) returns (loadType) { + let offset:word = StorageSize.size(Proxy as Proxy); + return CanStore.load(storage(offset) as storageType storage) as loadType; } } @@ -1755,74 +1709,71 @@ forall structType fieldSelector fieldType storageType offsetType -data ContractStorage(cxt) = ContractStorage(cxt); +enum ContractStorage { ContractStorage(cxt) } -forall member index . instance mapping(index, member):Typedef(word) { - function rep(x:mapping(index, member)) -> word { - match x { - | mapping(y) => return y; - } +impl Typedef member), word> { + function rep(x:mapping(index => member)) returns (word) { + match (x ) { + case mapping(y) { return y; + } } } - function abs(x:word) -> mapping(index,member) { + function abs(x:word) returns (mapping(index => member)) { return mapping(x); } } // cf https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html#mappings-and-dynamic-arrays -forall index member . -instance mapping(index, member):StorageSize { - function size(x:Proxy(mapping(index, member))) -> word { +impl StorageSize member)> { + function size(x:Proxy member)>) returns (word) { return 1; } } -forall member . instance array(member):Typedef(word) { - function rep(x:array(member)) -> word { - match x { - | array(y) => return y; - } +impl Typedef { + function rep(x:member[]) returns (word) { + match (x ) { + case array(y) { return y; + } } } - function abs(x:word) -> array(member) { + function abs(x:word) returns (member[]) { return array(x); } } // cf https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html#mappings-and-dynamic-arrays // the slot itself stores the array length; elements live at keccak256(slot) + i -forall member . -instance array(member):StorageSize { - function size(x:Proxy(array(member))) -> word { +impl StorageSize { + function size(x:Proxy) returns (word) { return 1; } } // Dynamic storage arrays carry their length at the slot itself (matching the // Solidity convention) while elements live at keccak256(slot) + i. -forall self . class self:Array { - function length(arr:self) -> uint256; - function setLength(arr:self, n:uint256) -> (); - function pop(arr:self) -> (); +trait Array { + function length(arr:self) returns (uint256); + function setLength(arr:self, n:uint256) returns (()); + function pop(arr:self) returns (()); } // push is split into its own MPTC so its element type only shows up where it // actually matters (the value being appended), without forcing `length`/ // `setLength`/`pop` to drag along an unconstrained `elem` parameter. -forall self elem . class self:ArrayPush(elem) { - function push(arr:self, val:elem) -> (); +trait ArrayPush { + function push(arr:self, val:elem) returns (()); } -forall t . -instance storage(array(t)):Array { - function length(arr:storage(array(t))) -> uint256 { +impl Array { + function length(arr:t[] storage) returns (uint256) { return uint256(sload(Typedef.rep(arr))); } // Shrinking clears the abandoned slots, matching solc's resize_array. // For string/bytes elements this zeroes the inline slot, which makes any // keccak-derived tail unreachable (reads are governed by the length word) but // does not reclaim it. - function setLength(arr:storage(array(t)), n:uint256) -> () { + function setLength(arr:t[] storage, n:uint256) returns (()) { let slot : word = Typedef.rep(arr); let oldLen : word = sload(slot); let newLen : word = Typedef.rep(n); @@ -1833,7 +1784,7 @@ instance storage(array(t)):Array { sstore(slot, newLen); } // Zeroes the removed element before decrementing, as solc's array_pop does. - function pop(arr:storage(array(t))) -> () { + function pop(arr:t[] storage) returns (()) { let slot : word = Typedef.rep(arr); let n : word = sload(slot); if (n == 0) { out_of_bounds(); } @@ -1847,51 +1798,44 @@ instance storage(array(t)):Array { // memory(string), via storage(string):CanStore(memory(string)). For word-sized // elements v collapses to the element type and CanStore.store delegates to // StorageType.store, so the generated code is unchanged. -forall t v . storage(t):CanStore(v) => -instance storage(array(t)):ArrayPush(v) { - function push(arr:storage(array(t)), val:v) -> () { +impl ArrayPush where t storage: CanStore { + function push(arr:t[] storage, val:v) returns (()) { let slot : word = Typedef.rep(arr); let n : word = sload(slot); - CanStore.store(storage(hash1(slot) + n):storage(t), val); + CanStore.store(storage(hash1(slot) + n) as t storage, val); sstore(slot, n + 1); } } -forall self memberRefType. -class self:LVA(memberRefType) { - function acc(x:self) -> memberRefType; +trait LVA { + function acc(x:self) returns (memberRefType); } -forall self member. -class self:RVA(member) { - function acc(x:self) -> member; +trait RVA { + function acc(x:self) returns (member); } -forall a b. a:RVA(b) => -function rval(x:a) -> b { +function rval(x:a) returns (b) where a: RVA { return RVA.acc(x); } // TODO: consider merging CanStore and Assign -forall lhs rhs. -class lhs:Assign(rhs) { - function assign(l:lhs, r:rhs) -> (); +trait Assign { + function assign(l:lhs, r:rhs) returns (()); } // a can store b; e.g. storage(string) : memory(string) -forall a b. -class a:CanStore(b) { - function store(r:a, v:b) -> (); - function load(r:a) -> b; +trait CanStore { + function store(r:a, v:b) returns (()); + function load(r:a) returns (b); } -forall a b. a:CanStore(b) => -instance a:Assign(b) { - function assign(l:a, r:b) -> () { +impl Assign where a: CanStore { + function assign(l:a, r:b) returns (()) { CanStore.store(l, r); } } @@ -1908,73 +1852,71 @@ default instance a:CanStore(a) { } */ - instance storage(word):CanStore(word) { - function store(l:storage(word), r:word) -> () { + impl CanStore { + function store(l:word storage, r:word) returns (()) { StorageType.store(Typedef.rep(l), r); } - function load(l:storage(word)) -> word { + function load(l:word storage) returns (word) { return StorageType.load(Typedef.rep(l)); } } - instance storage(uint256):CanStore(uint256) { - function store(l:storage(uint256), r:uint256) -> () { + impl CanStore { + function store(l:uint256 storage, r:uint256) returns (()) { StorageType.store(Typedef.rep(l), r); } - function load(l:storage(uint256)) -> uint256 { + function load(l:uint256 storage) returns (uint256) { return StorageType.load(Typedef.rep(l)); } } - instance storage(bytes32):CanStore(bytes32) { - function store(l:storage(bytes32), r:bytes32) -> () { + impl CanStore { + function store(l:bytes32 storage, r:bytes32) returns (()) { StorageType.store(Typedef.rep(l), r); } - function load(l:storage(bytes32)) -> bytes32 { + function load(l:bytes32 storage) returns (bytes32) { return StorageType.load(Typedef.rep(l)); } } - instance storage(address):CanStore(address) { - function store(l:storage(address), r:address) -> () { + impl CanStore
{ + function store(l:address storage, r:address) returns (()) { StorageType.store(Typedef.rep(l), r); } - function load(l:storage(address)) -> address { + function load(l:address storage) returns (address) { return StorageType.load(Typedef.rep(l)); } } // bool has no StorageType instance (it is a builtin, not a Typedef(word)), but it // round-trips through word via frombool / tobool, so it can still be stored. -instance storage(bool):CanStore(bool) { - function store(l:storage(bool), r:bool) -> () { +impl CanStore { + function store(l:bool storage, r:bool) returns (()) { StorageType.store(Typedef.rep(l), frombool(r)); } - function load(l:storage(bool)) -> bool { + function load(l:bool storage) returns (bool) { return tobool(StorageType.load(Typedef.rep(l))); } } -forall k v. - instance storage(mapping(k,v)):CanStore(storage(mapping(k,v))) { - function store(l:storage(mapping(k,v)), r:storage(mapping(k,v))) -> () { +impl CanStore v) storage, mapping(k => v) storage> { + function store(l:mapping(k => v) storage, r:mapping(k => v) storage) returns (()) { // StorageType.store(Typedef.rep(l), r); unimplemented(); } - function load(l:storage(mapping(k,v))) -> storage(mapping(k,v)) { + function load(l:mapping(k => v) storage) returns (mapping(k => v) storage) { // "Loading" a storage mapping field yields its storage reference (the // slot); indexed access / method calls consume that reference directly. return l; } } -forall v. v:StorageCopy => - instance storage(array(v)):CanStore(storage(array(v))) { +impl CanStore where v: StorageCopy { // Whole-array assignment is a deep copy, as in Solidity: a = b resizes a // to b's length and then copies every // element. Assigning an array to itself is a no-op. A *local* bound to an // array field stays an alias, because a let is not an Assign.assign. - function store(l:storage(array(v)), r:storage(array(v))) -> () { + function store(l:v[] storage, r:v[] storage) returns (()) { let dst : word = Typedef.rep(l); let src : word = Typedef.rep(r); if (dst != src) { @@ -1988,11 +1930,11 @@ forall v. v:StorageCopy => let srcBase : word = hash1(src); let i : word = 0; for (; i < newLen; i += 1) { - StorageCopy.copySlot(storage(dstBase + i):storage(v), storage(srcBase + i):storage(v)); + StorageCopy.copySlot(storage(dstBase + i) as v storage, storage(srcBase + i) as v storage); } } } - function load(l:storage(array(v))) -> storage(array(v)) { + function load(l:v[] storage) returns (v[] storage) { // "Loading" a storage array field yields its storage reference (the // slot). push / pop / length / arr[i] all consume that reference, so a // field read like `ArrayPush.push(members, x)` must return the slot, @@ -2002,14 +1944,14 @@ forall v. v:StorageCopy => } -instance storage(string):CanStore(memory(string)) { - function store(dst:storage(string), src:memory(string)) -> () { +impl CanStore { + function store(dst:string storage, src:string memory) returns (()) { let srcPtr : word = Typedef.rep(src); let slot = Typedef.rep(dst); storeBytesFromMemory(slot, srcPtr); } - function load(src:storage(string)) -> memory(string) { + function load(src:string storage) returns (string memory) { let srcPtr : word = Typedef.rep(src); let dstPtr : word = get_free_memory(); let endPtr = loadBytesFromStorage(srcPtr, dstPtr); @@ -2020,14 +1962,14 @@ instance storage(string):CanStore(memory(string)) { // bytes share the same storage layout as string, so the same // storeBytesFromMemory / loadBytesFromStorage helpers apply. -instance storage(bytes):CanStore(memory(bytes)) { - function store(dst:storage(bytes), src:memory(bytes)) -> () { +impl CanStore { + function store(dst:bytes storage, src:bytes memory) returns (()) { let srcPtr : word = Typedef.rep(src); let slot = Typedef.rep(dst); storeBytesFromMemory(slot, srcPtr); } - function load(src:storage(bytes)) -> memory(bytes) { + function load(src:bytes storage) returns (bytes memory) { let srcPtr : word = Typedef.rep(src); let dstPtr : word = get_free_memory(); let endPtr = loadBytesFromStorage(srcPtr, dstPtr); @@ -2039,23 +1981,23 @@ instance storage(bytes):CanStore(memory(bytes)) { // --- StorageCopy: per-element copy used by whole-array assignment --- // Word-sized elements are self-contained: the slot is the value. -instance word:StorageCopy { - function copySlot(dst:storage(word), src:storage(word)) -> () { +impl StorageCopy { + function copySlot(dst:word storage, src:word storage) returns (()) { sstore(Typedef.rep(dst), sload(Typedef.rep(src))); } } -instance uint256:StorageCopy { - function copySlot(dst:storage(uint256), src:storage(uint256)) -> () { +impl StorageCopy { + function copySlot(dst:uint256 storage, src:uint256 storage) returns (()) { sstore(Typedef.rep(dst), sload(Typedef.rep(src))); } } -instance bytes32:StorageCopy { - function copySlot(dst:storage(bytes32), src:storage(bytes32)) -> () { +impl StorageCopy { + function copySlot(dst:bytes32 storage, src:bytes32 storage) returns (()) { sstore(Typedef.rep(dst), sload(Typedef.rep(src))); } } -instance address:StorageCopy { - function copySlot(dst:storage(address), src:storage(address)) -> () { +impl StorageCopy
{ + function copySlot(dst:address storage, src:address storage) returns (()) { sstore(Typedef.rep(dst), sload(Typedef.rep(src))); } } @@ -2063,29 +2005,28 @@ instance address:StorageCopy { // Dynamic elements keep their payload at keccak256(elementSlot), so copying the // inline slot alone would leave the destination pointing at the *source's* tail. // Round-tripping through memory copies the payload too. -instance string:StorageCopy { - function copySlot(dst:storage(string), src:storage(string)) -> () { - CanStore.store(dst, CanStore.load(src):memory(string)); +impl StorageCopy { + function copySlot(dst:string storage, src:string storage) returns (()) { + CanStore.store(dst, CanStore.load(src) as string memory); } } -instance bytes:StorageCopy { - function copySlot(dst:storage(bytes), src:storage(bytes)) -> () { - CanStore.store(dst, CanStore.load(src):memory(bytes)); +impl StorageCopy { + function copySlot(dst:bytes storage, src:bytes storage) returns (()) { + CanStore.store(dst, CanStore.load(src) as bytes memory); } } // Nested arrays recurse into the array CanStore instance above. The recursion is // on the element type, so it terminates with the type's structure. -forall t . t:StorageCopy => -instance array(t):StorageCopy { - function copySlot(dst:storage(array(t)), src:storage(array(t))) -> () { +impl StorageCopy where t: StorageCopy { + function copySlot(dst:t[] storage, src:t[] storage) returns (()) { CanStore.store(dst, src); } } // Shamelessly stolen from function copy_byte_array_to_storage_from_t_bytes_memory_ptr_to_t_bytes_storage // TODO: consider wrapping behaviour at end of storage -function storeBytesFromMemory(slot: word, src: word) -> () { +function storeBytesFromMemory(slot: word, src: word) returns (()) { assembly { let newLen := mload(src) // TODO: check old len, cleanup etc @@ -2125,7 +2066,7 @@ function storeBytesFromMemory(slot: word, src: word) -> () { // shamelessly stolen from abi_encode_t_string_storage_to_t_string_memory_ptr -function loadBytesFromStorage(slot:word, memPtr:word) -> word { +function loadBytesFromStorage(slot:word, memPtr:word) returns (word) { let pos = memPtr; let slotValue = sload(slot); let length = slotValue / 2; @@ -2135,14 +2076,14 @@ function loadBytesFromStorage(slot:word, memPtr:word) -> word { } mstore(pos, length); pos += 32; - match outOfPlaceEncoding { - | false => + match (outOfPlaceEncoding ) { + case false { // Short byte array mstore(pos, and_(slotValue, not_(0xff))); let empty = iszero(length); let notzero = iszero(empty); return pos + (notzero * 32); - | true => + } case true { // Long byte array let dataPos = hash1(slot); let i = 0; @@ -2151,32 +2092,30 @@ function loadBytesFromStorage(slot:word, memPtr:word) -> word { dataPos += 1; } return pos + i; - } + } } } // -- Tuple-based indexed access: -forall col_idx val . class col_idx:RValueIdxAccess(val) { - function lookup(ci : col_idx) -> val; +trait RValueIdxAccess { + function lookup(ci : col_idx) returns (val); } -forall col_idx ref . class col_idx:LValueIdxAccess(ref) { - function lookup(ci : col_idx) -> ref; +trait LValueIdxAccess { + function lookup(ci : col_idx) returns (ref); } -forall i a . i:Typedef(word) => -instance (storage(mapping(i,a)), i): LValueIdxAccess(storage(a)) { - function lookup(xi : (storage(mapping(i,a)), i)) -> storage(a) { +impl LValueIdxAccess<(mapping(i => a) storage, i), a storage> where i: Typedef { + function lookup(xi : (mapping(i => a) storage, i)) returns (a storage) { match(xi) { - | (x, i) => return storage(hash2(Typedef.rep(x), Typedef.rep(i))); - } + case (x, i) { return storage(hash2(Typedef.rep(x), Typedef.rep(i))); + } } } } -forall i a . storage(a):CanStore(a), i:Typedef(word) => -instance (storage(mapping(i,a)), i): RValueIdxAccess(a) { - function lookup(xi : (storage(mapping(i,a)), i)) -> a { +impl RValueIdxAccess<(mapping(i => a) storage, i), a> where a storage: CanStore, i: Typedef { + function lookup(xi : (mapping(i => a) storage, i)) returns (a) { /* match(xi) { | (x, i) => return StorageType.load(hash2(Typedef.rep(x), Typedef.rep(i))); @@ -2186,18 +2125,17 @@ instance (storage(mapping(i,a)), i): RValueIdxAccess(a) { } } -forall a i . i:Typedef(word) => -instance (storage(array(a)), i): LValueIdxAccess(storage(a)) { - function lookup(xi : (storage(array(a)), i)) -> storage(a) { +impl LValueIdxAccess<(a[] storage, i), a storage> where i: Typedef { + function lookup(xi : (a[] storage, i)) returns (a storage) { match(xi) { - | (x, i) => + case (x, i) { let slot : word = Typedef.rep(x); let idx : word = Typedef.rep(i); // Bounds check: idx must be in [0, length). Length lives at the // slot itself; inlined to avoid an Array(t) dispatch here. if (idx >= sload(slot)) { out_of_bounds(); } return storage(hash1(slot) + idx); - } + } } } } @@ -2205,9 +2143,8 @@ instance (storage(array(a)), i): LValueIdxAccess(storage(a)) { // than the element tag type. For word-sized elements that is the element itself; // for array(string) it is a memory(string); for a nested array(array(t)) it // is the inner array's handle, which push/pop/length then consume. -forall a v i . storage(a):CanStore(v), i:Typedef(word) => -instance (storage(array(a)), i): RValueIdxAccess(v) { - function lookup(xi : (storage(array(a)), i)) -> v { +impl RValueIdxAccess<(a[] storage, i), v> where a storage: CanStore, i: Typedef { + function lookup(xi : (a[] storage, i)) returns (v) { return CanStore.load(LValueIdxAccess.lookup(xi)); } } @@ -2215,8 +2152,7 @@ instance (storage(array(a)), i): RValueIdxAccess(v) { // Mapping reads go through CanStore, matching the write side (Assign -> CanStore.store). // This lets a mapping hold any value with a CanStore instance — including ADTs whose // fields are dynamic (memory(bytes)) — not just the fixed-slot StorageType primitives. -forall a. storage(a):CanStore(a) => -function readStorage(x:storage(a)) -> a { +function readStorage(x:a storage) returns (a) where a storage: CanStore { return CanStore.load(x); } /* @@ -2235,37 +2171,35 @@ function lval(x:r) -> a { // desugaring. They dispatch through LValueIdxAccess / RValueIdxAccess, so any // collection (mapping, array, ...) that provides those instances supports the // `arr[i]` syntax. -forall col idx ref . (col, idx):LValueIdxAccess(ref) => -function lidx(c: col, i: idx) -> ref { +function lidx(c: col, i: idx) returns (ref) where (col, idx): LValueIdxAccess { return LValueIdxAccess.lookup((c, i)); } -forall col idx val . (col, idx):RValueIdxAccess(val) => -function ridx(c: col, i: idx) -> val { +function ridx(c: col, i: idx) returns (val) where (col, idx): RValueIdxAccess { return RValueIdxAccess.lookup((c, i)); } // --- Memory Encoding --- -forall t . class t:MemorySize { +trait MemorySize { // The size needed for the value. - function len(v: t) -> word; + function len(v: t) returns (word); } // NOTE: this is not implemented for value types. -forall t . class t:MemoryPointer { +trait MemoryPointer { // In-memory location of the given value. - function ptr(v: t) -> word; + function ptr(v: t) returns (word); } -forall t . class t:MemoryEncode { +trait MemoryEncode { // Serialize the entire contents at a provided memory area. - function encodeInto(v: t, target: word) -> (); + function encodeInto(v: t, target: word) returns (()); } // TODO: support variadic arguments // Allocates new memory and concatenates the inputs into it. -forall a b . a:MemorySize, a:MemoryEncode, b:MemorySize, b:MemoryEncode => function concat(x: a, y: b) -> memory(bytes) { +function concat(x: a, y: b) returns (bytes memory) where a: MemorySize, a: MemoryEncode, b: MemorySize, b: MemoryEncode { let x_len = MemorySize.len(x); let y_len = MemorySize.len(y); let res: word = allocate_memory(32 + x_len + y_len); @@ -2276,7 +2210,7 @@ forall a b . a:MemorySize, a:MemoryEncode, b:MemorySize, b:MemoryEncode => funct } // This is a specialized 1-input version of concat. -forall a . a:MemorySize, a:MemoryEncode => function to_bytes(x: a) -> memory(bytes) { +function to_bytes(x: a) returns (bytes memory) where a: MemorySize, a: MemoryEncode { let len = MemorySize.len(x); let res = allocate_memory(32 + len); mstore(res, len); @@ -2284,32 +2218,32 @@ forall a . a:MemorySize, a:MemoryEncode => function to_bytes(x: a) -> memory(byt return memory(res); } -instance bytes32:MemorySize { - function len(v: bytes32) -> word { +impl MemorySize { + function len(v: bytes32) returns (word) { return 32; } } -instance bytes32:MemoryEncode { - function encodeInto(v: bytes32, target: word) -> () { +impl MemoryEncode { + function encodeInto(v: bytes32, target: word) returns (()) { mstore(target, Typedef.rep(v)); } } -instance memory(bytes):MemorySize { - function len(v: memory(bytes)) -> word { +impl MemorySize { + function len(v: bytes memory) returns (word) { return mload(Typedef.rep(v)); } } -instance memory(bytes):MemoryPointer { - function ptr(v: memory(bytes)) -> word { +impl MemoryPointer { + function ptr(v: bytes memory) returns (word) { return Typedef.rep(v) + 32; } } -instance memory(bytes):MemoryEncode { - function encodeInto(v: memory(bytes), target: word) -> () { +impl MemoryEncode { + function encodeInto(v: bytes memory, target: word) returns (()) { let v_ = Typedef.rep(v); mcopy(target, v_ + 32, mload(v_)); } @@ -2318,22 +2252,22 @@ instance memory(bytes):MemoryEncode { // Placeholder for an empty memory area. // The value is the size of the area in bytes. The area will be zeroed upon serialization. // NOTE: not implementing Typedef by design. -data empty = empty(word); +enum empty { empty(word) } -instance empty:MemorySize { - function len(v: empty) -> word { - match v { - | empty(size) => return size; - } +impl MemorySize { + function len(v: empty) returns (word) { + match (v ) { + case empty(size) { return size; + } } } } -instance empty:MemoryEncode { - function encodeInto(v: empty, target: word) -> () { +impl MemoryEncode { + function encodeInto(v: empty, target: word) returns (()) { let size; - match v { - | empty(size_) => size = size_; - } + match (v ) { + case empty(size_) { size = size_; + } } zeroize_memory(target, size); } } @@ -2342,34 +2276,33 @@ instance empty:MemoryEncode { // This is a very cheap abstraction over a memory area of [ptr, ptr+len) // No type information is preserved. -data memory_ref = memory_ref(word, word); +enum memory_ref { memory_ref(word, word) } -instance memory_ref:MemorySize { - function len(v: memory_ref) -> word { - match v { - | memory_ref(ptr, len) => return len; - } +impl MemorySize { + function len(v: memory_ref) returns (word) { + match (v ) { + case memory_ref(ptr, len) { return len; + } } } } -instance memory_ref:MemoryPointer { - function ptr(v: memory_ref) -> word { - match v { - | memory_ref(ptr, len) => return ptr; - } +impl MemoryPointer { + function ptr(v: memory_ref) returns (word) { + match (v ) { + case memory_ref(ptr, len) { return ptr; + } } } } -instance memory_ref:MemoryEncode { - function encodeInto(v: memory_ref, target: word) -> () { - match v { - | memory_ref(ptr, len) => mcopy(target, ptr, len); - } +impl MemoryEncode { + function encodeInto(v: memory_ref, target: word) returns (()) { + match (v ) { + case memory_ref(ptr, len) { mcopy(target, ptr, len); + } } } } -forall a . a:MemorySize, a:MemoryPointer => -function slice_(input: a, start: word) -> memory_ref { +function slice_(input: a, start: word) returns (memory_ref) where a: MemorySize, a: MemoryPointer { let len = MemorySize.len(input); // TODO: should this allow (it does now) a zero-length slice? require(len >= start, Error(0xb4120f14)); // OutOfBounds() @@ -2377,8 +2310,7 @@ function slice_(input: a, start: word) -> memory_ref { return memory_ref(ptr_ + start, len - start); } -forall a . a:MemorySize, a:MemoryPointer => -function truncate(input: a, end: word) -> memory_ref { +function truncate(input: a, end: word) returns (memory_ref) where a: MemorySize, a: MemoryPointer { let len = MemorySize.len(input); // TODO: should this allow (it does now) a zero-length slice? require(len >= end, Error(0xb4120f14)); // OutOfBounds() @@ -2388,13 +2320,13 @@ function truncate(input: a, end: word) -> memory_ref { // --- Hashing --- // NOTE: keccak256 name conflicts with assembly namespace -forall a . a:MemorySize, a:MemoryPointer => function keccak256_(input: a) -> bytes32 { +function keccak256_(input: a) returns (bytes32) where a: MemorySize, a: MemoryPointer { let len : word = MemorySize.len(input); let ptr : word = MemoryPointer.ptr(input); return bytes32(keccak256(ptr, len)); } -forall a . a:MemorySize, a:MemoryPointer => function sha256(input: a) -> bytes32 { +function sha256(input: a) returns (bytes32) where a: MemorySize, a: MemoryPointer { let len : word = MemorySize.len(input); let ptr : word = MemoryPointer.ptr(input); // We assume the [0, 32] scratch space is reserved. @@ -2403,7 +2335,7 @@ forall a . a:MemorySize, a:MemoryPointer => function sha256(input: a) -> bytes32 return bytes32(mload(0)); } -forall a . a:MemorySize, a:MemoryPointer => function ripemd160(input: a) -> bytes32 { +function ripemd160(input: a) returns (bytes32) where a: MemorySize, a: MemoryPointer { let len : word = MemorySize.len(input); let ptr : word = MemoryPointer.ptr(input); // We assume the [0, 32] scratch space is reserved. @@ -2419,7 +2351,7 @@ forall a . a:MemorySize, a:MemoryPointer => function ripemd160(input: a) -> byte // were updated to ban this, but the precompile wasn't. If a user relies on that // feature they can call the precompile via assembly. // TODO: use uint8 -function ecrecover(hash: bytes32, v: uint256, r: bytes32, s: bytes32) -> address { +function ecrecover(hash: bytes32, v: uint256, r: bytes32, s: bytes32) returns (address) { // MalleableSignatureRejected() require( Typedef.rep(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, @@ -2445,7 +2377,7 @@ function ecrecover(hash: bytes32, v: uint256, r: bytes32, s: bytes32) -> address // TODO: use string here // TODO: eventually this needs to become comptime -function erc7201(id: memory(bytes)) -> bytes32 { +function erc7201(id: bytes memory) returns (bytes32) { // return keccak256_(to_bytes(keccak256_(id) - 1)) & ~0xff; return Typedef.abs( and_( @@ -2459,7 +2391,7 @@ function erc7201(id: memory(bytes)) -> bytes32 { ); } -forall a . a:MemorySize, a:MemoryPointer => function raw_call(target: address, value: uint256, payload: a) -> (bool, memory(bytes)) { +function raw_call(target: address, value: uint256, payload: a) returns ((bool, bytes memory)) where a: MemorySize, a: MemoryPointer { let ret = call( gas(), Typedef.rep(target), diff --git a/test/DiagnosticCliTests.hs b/test/DiagnosticCliTests.hs index bf122c4db..215860414 100644 --- a/test/DiagnosticCliTests.hs +++ b/test/DiagnosticCliTests.hs @@ -16,24 +16,24 @@ diagnosticCliTests = [ testCase "parser error" $ expectFailure ["--root", "test/diagnostics", "--file", "test/diagnostics/parse-error.solc", "--no-specialise"] - [ "error[SC0001]: parse error: unexpected '-'", - " --> /test/diagnostics/parse-error.solc:1:16", + [ "error[SC0001]: parse error: unexpected '}'", + " --> /test/diagnostics/parse-error.solc:1:26", " |", - "1 | function main( -> word { return 0; }", - " | ^^ unexpected token", - "note: expecting \"comptime\", ')', or identifier" + "1 | enum Broken { Value(word }", + " | ^ unexpected token", + "note: expecting \"calldata\", \"memory\", \"storage\", ')', ',', '.', '<', or '['" ], testCase "undefined name" $ expectFailure ["--root", "test/diagnostics", "--file", "test/diagnostics/undefined-name.solc", "--no-specialise"] [ "error[SC0101]: undefined name: missing", - " --> /test/diagnostics/undefined-name.solc:1:34", + " --> /test/diagnostics/undefined-name.solc:1:41", " |", - "1 | function main() -> word { return missing; }", - " | ^^^^^^^ unknown name", - "note: in: return missing ;", - "note: in: function main () -> word {", - " return missing ;", + "1 | function main() returns (word) { return missing; }", + " | ^^^^^^^ unknown name", + "note: in: return missing;", + "note: in: function main() returns (word) {", + " return missing;", " }", "note: module validation failed for /test/diagnostics/undefined-name.solc" ], @@ -43,9 +43,9 @@ diagnosticCliTests = [ "error[SC0108]: duplicate declarations in term namespace", " --> /test/diagnostics/duplicate-definition.solc:2:10", " |", - "1 | function foo() -> word { return 1; }", + "1 | function foo() returns (word) { return 1; }", " | --- previous definition", - "2 | function foo() -> word { return 2; }", + "2 | function foo() returns (word) { return 2; }", " | ^^^ duplicate definition", "note: context: module", "note: foo", @@ -56,14 +56,14 @@ diagnosticCliTests = expectFailure ["--root", "test/diagnostics", "--file", "test/diagnostics/type-mismatch.solc", "--no-specialise"] [ "error[SC0201]: types do not unify: bool and word", - " --> /test/diagnostics/type-mismatch.solc:1:34", + " --> /test/diagnostics/type-mismatch.solc:1:41", " |", - "1 | function main() -> word { return true; }", - " | ^^^^ expression has mismatched type", + "1 | function main() returns (word) { return true; }", + " | ^^^^ expression has mismatched type", "note: left type: bool", "note: right type: word", "note: in: true", - "note: in: function main () -> word {", + "note: in: function main() returns (word) {", " return true;", " }", "note: module typecheck failed for /test/diagnostics/type-mismatch.solc" @@ -76,23 +76,23 @@ diagnosticCliTests = " |", "1 | function foo() {", " | ^^^ incomplete signature", - "note: signature: function foo ()", + "note: signature: function foo()", "note: module typecheck failed for /test/diagnostics/missing-signature.solc", - "help: annotate every parameter (name : Type) and provide a return type (-> Type)" + "help: annotate every parameter (name: Type) and provide a return type (returns (Type))" ], testCase "polymorphic type error uses signature span" $ expectFailure ["--root", "test/diagnostics", "--file", "test/diagnostics/not-polymorphic-enough.solc", "--no-specialise"] [ "error[SC0209]: type is not polymorphic enough", - " --> /test/diagnostics/not-polymorphic-enough.solc:1:21", + " --> /test/diagnostics/not-polymorphic-enough.solc:1:10", " |", - "1 | forall a . function fromWord(x : word) -> a {", - " | ^^^^^^^^ annotated type is not polymorphic enough", - "note: annotated type: forall a . word -> a", - "note: inferred type: word -> word", - "note: in: forall a . function fromWord (x : word) -> a", - "note: in: forall a . function fromWord (x : word) -> a {", - " let result ;", + "1 | function fromWord(x : word) returns (a) {", + " | ^^^^^^^^ annotated type is not polymorphic enough", + "note: annotated type: forall a . function(word) internal returns (a)", + "note: inferred type: function(word) internal returns (word)", + "note: in: function fromWord(x: word) returns (a)", + "note: in: function fromWord(x: word) returns (a) {", + " let result;", " assembly {", " result := x", " }", @@ -103,18 +103,18 @@ diagnosticCliTests = testCase "missing instance" $ expectFailure ["--root", "test/examples/cases", "--file", "test/examples/cases/missing-instance.solc", "--no-specialise"] - [ "error[SC0223]: cannot entail: word : Typedef (word)", + [ "error[SC0223]: cannot entail: word: Typedef", " --> /test/examples/cases/missing-instance.solc:12:14", " |", - "12 | function load(ptr:word) -> word {", + "12 | function load(ptr:word) returns (word) {", " | ^^^^ unsolved constraint", "note: using defined instances:", - "note: in: function load (ptr : word) -> word {", - " return Typedef.abs(MemoryType.load(ptr) : word);", + "note: in: function load(ptr: word) returns (word) {", + " return Typedef.abs(MemoryType.load(ptr) as word);", " }", - "note: in: instance word : MemoryType {", - " function load (ptr : word) -> word {", - " return Typedef.abs(MemoryType.load(ptr) : word);", + "note: in: impl MemoryType {", + " function load(ptr: word) returns (word) {", + " return Typedef.abs(MemoryType.load(ptr) as word);", " }", " }", "note: module typecheck failed for /test/examples/cases/missing-instance.solc", @@ -130,7 +130,7 @@ diagnosticCliTests = " | ^^^^ shorthand constructor", "note: constructor: .Nope", "note: in: .Nope(Int.fromInteger(1))", - "note: in: function bad () -> Option {", + "note: in: function bad() returns (Option) {", " return .Nope(Int.fromInteger(1));", " }", "note: module typecheck failed for /test/examples/cases/dot-expression-unknown-fail.solc", @@ -140,10 +140,10 @@ diagnosticCliTests = expectFailure ["--root", "test/imports", "--file", "test/imports/select_unknown.solc", "--no-specialise"] [ "error[SC0110]: unknown import item", - " --> /test/imports/select_unknown.solc:1:19", + " --> /test/imports/select_unknown.solc:1:9", " |", - "1 | import selectlib.{missing};", - " | ^^^^^^^ unknown import item", + "1 | import {missing} from selectlib;", + " | ^^^^^^^ unknown import item", "note: unknown selected imports:", "note: selectlib.missing", "help: check the imported module's exported names" @@ -164,10 +164,10 @@ diagnosticCliTests = expectFailure ["--root", "test/imports", "--file", "test/imports/amb_main.solc", "--no-specialise"] [ "error[SC0120]: ambiguous selected imports", - " --> /test/imports/amb_main.solc:2:14", + " --> /test/imports/amb_main.solc:2:9", " |", - "2 | import ambB.{pick};", - " | ^^^^ ambiguous selected import", + "2 | import {pick} from ambB;", + " | ^^^^ ambiguous selected import", "note: pick imported from ambB, ambA", "help: use an explicit module qualifier or narrow the selected imports" ], @@ -179,16 +179,16 @@ diagnosticCliTests = " |", "4 | return Token.Err(0);", " | ^^^ unknown name", - "note: in: return Token.Err(0) ;", - "note: in: function main () -> Token {", - " return Token.Err(0) ;", + "note: in: return Token.Err(0);", + "note: in: function main() returns (Token) {", + " return Token.Err(0);", " }", "note: module validation failed for /test/imports/hidden_ctor_expr_fail.solc" ], testCase "short output" $ expectFailure ["--root", "test/diagnostics", "--file", "test/diagnostics/undefined-name.solc", "--no-specialise", "--diagnostic-format", "short"] - ["/test/diagnostics/undefined-name.solc:1:34: error[SC0101]: undefined name: missing"], + ["/test/diagnostics/undefined-name.solc:1:41: error[SC0101]: undefined name: missing"], testCase "warnings always" $ expectSuccess ["--root", "test/examples/cases", "--file", "test/examples/cases/redundant-match.solc", "--no-specialise", "--warnings", "always"] @@ -253,23 +253,25 @@ maybeToList (Just value) = [value] redundantWarningsSnapshot :: [String] redundantWarningsSnapshot = [ "warning[SC0301]: redundant pattern clause", - " --> /test/examples/cases/redundant-match.solc:6:7", + " --> /test/examples/cases/redundant-match.solc:6:12", " |", - "6 | | Bool.True => return Bool.True;", - " | ^^^^^^^^^^^ redundant clause", - "note: clause: | Bool.True =>", + "6 | } case Bool.True { return Bool.True;", + " | ^^^^^^^^^^^ redundant clause", + "note: clause: case Bool.True {", " return Bool.True;", + " }", "note: in: match (x)", "note: in: function f", "help: remove this clause or make an earlier pattern more specific", "", "warning[SC0301]: redundant pattern clause", - " --> /test/examples/cases/redundant-match.solc:7:7", + " --> /test/examples/cases/redundant-match.solc:7:12", " |", - "7 | | Bool.False => return Bool.False;", - " | ^^^^^^^^^^^ redundant clause", - "note: clause: | Bool.False =>", + "7 | } case Bool.False { return Bool.False;", + " | ^^^^^^^^^^^ redundant clause", + "note: clause: case Bool.False {", " return Bool.False;", + " }", "note: in: match (x)", "note: in: function f", "help: remove this clause or make an earlier pattern more specific" diff --git a/test/DiagnosticTests.hs b/test/DiagnosticTests.hs index e7dbb1327..c523d70b5 100644 --- a/test/DiagnosticTests.hs +++ b/test/DiagnosticTests.hs @@ -90,9 +90,9 @@ test_nearbyLabelsShareOneSnippet = [ "error[SC0108]: duplicate declarations", " --> dup.solc:2:10", " |", - "1 | function foo() -> word { return 1; }", + "1 | function foo() returns (word) { return 1; }", " | --- previous definition", - "2 | function foo() -> word { return 2; }", + "2 | function foo() returns (word) { return 2; }", " | ^^^ duplicate definition" ] @@ -119,7 +119,7 @@ sourceMap = sourceFile :: SourceFile sourceFile = - makeSourceFile "main.solc" (unlines ["function main() -> word {", " return missing;", "}"]) + makeSourceFile "main.solc" (unlines ["function main() returns (word) {", " return missing;", "}"]) duplicateSourceMap :: SourceMap duplicateSourceMap = @@ -127,7 +127,7 @@ duplicateSourceMap = duplicateSourceFile :: SourceFile duplicateSourceFile = - makeSourceFile "dup.solc" (unlines ["function foo() -> word { return 1; }", "function foo() -> word { return 2; }"]) + makeSourceFile "dup.solc" (unlines ["function foo() returns (word) { return 1; }", "function foo() returns (word) { return 2; }"]) undefinedNameDiagnostic :: Diagnostic undefinedNameDiagnostic = @@ -140,8 +140,8 @@ undefinedNameDiagnostic = { labelSpan = SourceSpan { spanFile = "main.solc", - spanStartByte = 34, - spanEndByte = 41, + spanStartByte = 41, + spanEndByte = 48, spanStartLine = 2, spanStartColumn = 10, spanEndLine = 2, @@ -180,8 +180,8 @@ duplicateDiagnostic = { labelSpan = SourceSpan { spanFile = "dup.solc", - spanStartByte = 47, - spanEndByte = 50, + spanStartByte = 54, + spanEndByte = 57, spanStartLine = 2, spanStartColumn = 10, spanEndLine = 2, diff --git a/test/diagnostics/duplicate-definition.solc b/test/diagnostics/duplicate-definition.solc index 11e1185e3..ea0d4cc1e 100644 --- a/test/diagnostics/duplicate-definition.solc +++ b/test/diagnostics/duplicate-definition.solc @@ -1,3 +1,3 @@ -function foo() -> word { return 1; } -function foo() -> word { return 2; } -function main() -> word { return foo(); } +function foo() returns (word) { return 1; } +function foo() returns (word) { return 2; } +function main() returns (word) { return foo(); } diff --git a/test/diagnostics/not-polymorphic-enough.solc b/test/diagnostics/not-polymorphic-enough.solc index 7400c26ce..05bb9764d 100644 --- a/test/diagnostics/not-polymorphic-enough.solc +++ b/test/diagnostics/not-polymorphic-enough.solc @@ -1,4 +1,4 @@ -forall a . function fromWord(x : word) -> a { +function fromWord(x : word) returns (a) { let result; assembly { result := x } return result; diff --git a/test/diagnostics/parse-error.solc b/test/diagnostics/parse-error.solc index 88b553a77..1d83359ce 100644 --- a/test/diagnostics/parse-error.solc +++ b/test/diagnostics/parse-error.solc @@ -1 +1 @@ -function main( -> word { return 0; } +enum Broken { Value(word } diff --git a/test/diagnostics/type-mismatch.solc b/test/diagnostics/type-mismatch.solc index 64d7ed2c4..2ca138c65 100644 --- a/test/diagnostics/type-mismatch.solc +++ b/test/diagnostics/type-mismatch.solc @@ -1 +1 @@ -function main() -> word { return true; } +function main() returns (word) { return true; } diff --git a/test/diagnostics/undefined-name.solc b/test/diagnostics/undefined-name.solc index 6aae2ad1e..db2d49cdc 100644 --- a/test/diagnostics/undefined-name.solc +++ b/test/diagnostics/undefined-name.solc @@ -1 +1 @@ -function main() -> word { return missing; } +function main() returns (word) { return missing; } diff --git a/test/examples/Convertible.solc b/test/examples/Convertible.solc index cccfa06c7..9ebaf5ff4 100644 --- a/test/examples/Convertible.solc +++ b/test/examples/Convertible.solc @@ -1,70 +1,69 @@ -data Pair(a,b) = Pair(a,b); -data Proxy(a) = Proxy; -data Unit = Unit; +enum Pair { Pair(a, b) } +enum Proxy { Proxy } +enum Unit { Unit } -class a:Typedef(r) { - function abs(x:r) -> a; - function rep(x:a) -> r; +trait Typedef { + function abs(x:r) returns (a); + function rep(x:a) returns (r); } -data uint16 = uint16(word); +enum uint16 { uint16(word) } -instance uint16:Typedef(word) { +impl Typedef { function abs(r:word) { return uint16(r);} - function rep(x: uint16) -> word { - match x { - | uint16(val) => return val; - }; + function rep(x: uint16) returns (word) { + match (x ) { + case uint16(val) { return val; + } } } } -data uint8 = uint8(word); +enum uint8 { uint8(word) } -instance uint8:Typedef(word) { +impl Typedef { function abs(r:word) { return uint8(r);} - function rep(x: uint8) -> word { - match x { - | uint8(val) => return val; - }; + function rep(x: uint8) returns (word) { + match (x ) { + case uint8(val) { return val; + } } } } -data uint256 = uint256(word); +enum uint256 { uint256(word) } -instance uint256:Typedef(word) { +impl Typedef { function abs(r:word) { return uint256(r);} - function rep(x: uint256) -> word { - match x { - | uint256(val) => return val; - }; + function rep(x: uint256) returns (word) { + match (x ) { + case uint256(val) { return val; + } } } } -function foo(x:word) -> uint16 { +function foo(x:word) returns (uint16) { let result : uint16 = Typedef.abs(x); return result; } -class self:Convertible(r) -{ - function convert(x:self) -> r; +trait Convertible { + function convert(x:self) returns (r); } -instance Pair(uint8,Proxy(uint16)):Convertible(uint16) { - function convert(p:Pair(uint8,Proxy(uint16))) -> uint16 { - match p { - | Pair(x, _) => return Typedef.abs(Typedef.rep(x)); - }; +impl Convertible>, uint16> { + function convert(p:Pair>) returns (uint16) { + match (p ) { + case Pair(x, _) { return Typedef.abs(Typedef.rep(x)); + } } } } -function uint8to16(x : uint8) -> uint16 { - let proxy : Proxy(uint16) = Proxy; +function uint8to16(x : uint8) returns (uint16) { + let proxy : Proxy = Proxy; let result : uint16 = Convertible.convert(Pair(x,proxy)); return result; } @@ -77,31 +76,31 @@ forall Pair(a,Proxy(b)):Convertible(b). function convert(x:a) -> b { } */ -forall a, b. function convert(x:a) -> b { - let proxy : Proxy(b) = Proxy; +function convert(x:a) returns (b) { + let proxy : Proxy = Proxy; let result : b = Convertible.convert(Pair(x,proxy)); return result; } -function bar(x:Unit) -> word { +function bar(x:Unit) returns (word) { let result: word = convert(x); return result; } -instance Pair(uint8,Proxy(uint256)):Convertible(uint256) { - function convert(p:Pair(uint8,Proxy(uint256))) -> uint256 { - match p { - | Pair(x, _) => return Typedef.abs(Typedef.rep(x)); - }; +impl Convertible>, uint256> { + function convert(p:Pair>) returns (uint256) { + match (p ) { + case Pair(x, _) { return Typedef.abs(Typedef.rep(x)); + } } } } -instance Pair(uint16,Proxy(uint256)):Convertible(uint256) { - function convert(p:Pair(uint16,Proxy(uint256))) -> uint256 { - match p { - | Pair(x, _) => return Typedef.abs(Typedef.rep(x)); - }; +impl Convertible>, uint256> { + function convert(p:Pair>) returns (uint256) { + match (p ) { + case Pair(x, _) { return Typedef.abs(Typedef.rep(x)); + } } } } @@ -109,7 +108,7 @@ instance Pair(uint16,Proxy(uint256)):Convertible(uint256) { contract Bar { -public function main() -> word { +function main() public returns (word) { let x = Unit; let y : word = convert(x); return y; diff --git a/test/examples/cases/Ackermann.solc b/test/examples/cases/Ackermann.solc index c2181cc01..acb5e6bde 100644 --- a/test/examples/cases/Ackermann.solc +++ b/test/examples/cases/Ackermann.solc @@ -1,10 +1,10 @@ -data Nat = Zero | Succ(Nat) ; +enum Nat { Zero, Succ(Nat) } -function foo (x : Nat, y : Nat) -> word { - match y, x { - | y1, Nat.Zero => return 1 ; - | Nat.Zero, Nat.Succ(x2) => return 2; - | Nat.Succ(y3), Nat.Succ(x3) => return 3; - } +function foo (x : Nat, y : Nat) returns (word) { + match (y, x ) { + case (y1, Nat.Zero ) { return 1 ; + } case (Nat.Zero, Nat.Succ(x2) ) { return 2; + } case (Nat.Succ(y3), Nat.Succ(x3) ) { return 3; + } } } diff --git a/test/examples/cases/Add1.solc b/test/examples/cases/Add1.solc index 8c47763d2..72baa53a8 100644 --- a/test/examples/cases/Add1.solc +++ b/test/examples/cases/Add1.solc @@ -1,5 +1,5 @@ contract Add1 { - public function main() -> word { + function main() public returns (word) { let res: word; assembly { res := add(40, 2) diff --git a/test/examples/cases/BadInstance.solc b/test/examples/cases/BadInstance.solc index 0906c2305..c8dbd916a 100644 --- a/test/examples/cases/BadInstance.solc +++ b/test/examples/cases/BadInstance.solc @@ -1,17 +1,17 @@ -class a:Enum { - function fromEnum(x:a) -> word; +trait Enum { + function fromEnum(x:a) returns (word); } -data Color = R | G | B; +enum Color { R, G, B } -data Bool = False | True; +enum Bool { False, True } -instance Bool : Enum { - function fromEnum(b : Bool) -> word { - match b { - | Color.R => return 0; - | Color.G => return 1; - } +impl Enum { + function fromEnum(b : Bool) returns (word) { + match (b ) { + case Color.R { return 0; + } case Color.G { return 1; + } } } } diff --git a/test/examples/cases/BoolNot.solc b/test/examples/cases/BoolNot.solc index 379698454..086e73c55 100644 --- a/test/examples/cases/BoolNot.solc +++ b/test/examples/cases/BoolNot.solc @@ -1,8 +1,8 @@ -data Bool = False | True; +enum Bool { False, True } -function not (b : Bool) -> Bool { - match b { - | Bool.False => return Bool.True ; - | Bool.True => return Bool.False ; - } +function not (b : Bool) returns (Bool) { + match (b ) { + case Bool.False { return Bool.True ; + } case Bool.True { return Bool.False ; + } } } diff --git a/test/examples/cases/Compose.solc b/test/examples/cases/Compose.solc index 8b25bc25f..3fa04ed7b 100644 --- a/test/examples/cases/Compose.solc +++ b/test/examples/cases/Compose.solc @@ -1,7 +1,7 @@ contract Compose { - public function id(x : word) -> word { return x; } + function id(x : word) public returns (word) { return x; } - public function main() -> word { + function main() public returns (word) { return id(id(42)); } } diff --git a/test/examples/cases/Compose3.solc b/test/examples/cases/Compose3.solc index d04847e87..84605da6f 100644 --- a/test/examples/cases/Compose3.solc +++ b/test/examples/cases/Compose3.solc @@ -1,11 +1,11 @@ contract Compose { - forall a . public function id(x : a) -> a { return x; } + function id(x : a) public returns (a) { return x; } - public function apply1(f : (word) -> word, a : word) -> word { return f(a); } + function apply1(f : function(word) internal returns (word), a : word) public returns (word) { return f(a); } - public function idThenId(x : word) -> word { return id(id(x)); } + function idThenId(x : word) public returns (word) { return id(id(x)); } - public function main() -> word { + function main() public returns (word) { return apply1(idThenId, 42); } } diff --git a/test/examples/cases/CondExp.solc b/test/examples/cases/CondExp.solc index 4c5a236d7..620f347d5 100644 --- a/test/examples/cases/CondExp.solc +++ b/test/examples/cases/CondExp.solc @@ -1,8 +1,8 @@ contract CondExp { - public function main() -> word { + function main() public returns (word) { return - if if true then false else true - then if false then 1 else 2 - else if true then 42 else 56; + ( ( true ? false : true + )? ( false ? 1 : 2 + ): ( true ? 42 : 56)); } } \ No newline at end of file diff --git a/test/examples/cases/DupFun.solc b/test/examples/cases/DupFun.solc index fbfc9dce5..162f3b44d 100644 --- a/test/examples/cases/DupFun.solc +++ b/test/examples/cases/DupFun.solc @@ -1,11 +1,11 @@ -function f(x : word) -> word { +function f(x : word) returns (word) { return x; } -function f(x : word) -> word { +function f(x : word) returns (word) { return 10; } -function g(x : word) -> word { +function g(x : word) returns (word) { return f(x); } diff --git a/test/examples/cases/DuplicateFun.solc b/test/examples/cases/DuplicateFun.solc index 5ef3bb2c7..bc3f99bc9 100644 --- a/test/examples/cases/DuplicateFun.solc +++ b/test/examples/cases/DuplicateFun.solc @@ -1,21 +1,21 @@ -forall self . class self:A { - function foo(p : self) -> word; +trait A { + function foo(p : self) returns (word); } -forall self . class self:B { - function foo(p : self) -> word; +trait B { + function foo(p : self) returns (word); } -instance word:B { - function foo(x : word) -> word { +impl B { + function foo(x : word) returns (word) { return x; } } // error: Constraint for A not found in type of foo -instance word:A { - function foo(x : word) -> word { +impl A { + function foo(x : word) returns (word) { return x; } } diff --git a/test/examples/cases/EitherModule.solc b/test/examples/cases/EitherModule.solc index abf8b3939..8f7683704 100644 --- a/test/examples/cases/EitherModule.solc +++ b/test/examples/cases/EitherModule.solc @@ -1,17 +1,17 @@ contract EitherModule { - data Either(a,b) = Left(a) | Right(b); - data List(a) = Nil | Cons(a,List(a)); + enum Either { Left(a), Right(b) } + enum List { Nil, Cons(a, List) } - public function lefts(xs : List(Either(word,word))) -> List(word) { - match xs { - | List.Nil => return List.Nil ; - | List.Cons(y,ys) => - match y { - | Either.Left(z) => return List.Cons(z,lefts(ys)) ; - | Either.Right(z) => return lefts(ys) ; - } - } + function lefts(xs : List>) public returns (List) { + match (xs ) { + case List.Nil { return List.Nil ; + } case List.Cons(y,ys) { + match (y ) { + case Either.Left(z) { return List.Cons(z,lefts(ys)) ; + } case Either.Right(z) { return lefts(ys) ; + } } + } } } - public function main() -> word { return 0; } + function main() public returns (word) { return 0; } } diff --git a/test/examples/cases/Enum.solc b/test/examples/cases/Enum.solc index 6b977e4ac..2ef40c865 100644 --- a/test/examples/cases/Enum.solc +++ b/test/examples/cases/Enum.solc @@ -1,21 +1,21 @@ -class a: Enum { - function fromEnum(x : a) -> word; +trait Enum { + function fromEnum(x : a) returns (word); } -data Food = Curry | Beans | Other; +enum Food { Curry, Beans, Other } -instance Food : Enum { - function fromEnum(x : Food) -> word { - match x { - | Food.Curry => return 1; - | Food.Beans => return 2; - | Food.Other => return 3; - } +impl Enum { + function fromEnum(x : Food) returns (word) { + match (x ) { + case Food.Curry { return 1; + } case Food.Beans { return 2; + } case Food.Other { return 3; + } } } } contract Food { - public function main() -> word { + function main() public returns (word) { return Enum.fromEnum(Food.Beans); } } diff --git a/test/examples/cases/Eq.solc b/test/examples/cases/Eq.solc index a36b462d6..0510c2314 100644 --- a/test/examples/cases/Eq.solc +++ b/test/examples/cases/Eq.solc @@ -1,20 +1,20 @@ -data Bool = True | False; +enum Bool { True, False } -class a : Eq { - function eq (x : a, y : a) -> Bool; +trait Eq { + function eq (x : a, y : a) returns (Bool); } -forall a . a : Eq => class a : Ord { - function lt (x : a, y : a) -> Bool ; +trait Ord where a: Eq { + function lt (x : a, y : a) returns (Bool) ; } -instance word : Eq { +impl Eq { function eq (x,y) { - match primEqWord(x,y) { - | 0 => + match (primEqWord(x,y) ) { + case 0 { return Bool.False; - | _ => + } default { return Bool.True ; - } + } } } } diff --git a/test/examples/cases/EqQual.solc b/test/examples/cases/EqQual.solc index 874acf93d..2e7301a73 100644 --- a/test/examples/cases/EqQual.solc +++ b/test/examples/cases/EqQual.solc @@ -1,24 +1,24 @@ -data Bool = True | False; +enum Bool { True, False } -forall a . class a : Eq { - function eq (x : a, y : a) -> Bool; +trait Eq { + function eq (x : a, y : a) returns (Bool); } -forall a . a : Eq => class a : Ord { - function lt (x : a, y : a) -> Bool ; +trait Ord where a: Eq { + function lt (x : a, y : a) returns (Bool) ; } -instance word : Eq { - function eq (x : word, y : word) -> Bool { - match primEqWord(x,y) { - | 0 => +impl Eq { + function eq (x : word, y : word) returns (Bool) { + match (primEqWord(x,y) ) { + case 0 { return Bool.False; - | _ => + } default { return Bool.True ; - } + } } } } -function foo (x : word) -> Bool { +function foo (x : word) returns (Bool) { return Eq.eq (x, 0); } diff --git a/test/examples/cases/EvenOdd.solc b/test/examples/cases/EvenOdd.solc index 96da4173e..2e5280e7c 100644 --- a/test/examples/cases/EvenOdd.solc +++ b/test/examples/cases/EvenOdd.solc @@ -1,20 +1,20 @@ contract EvenOdd { - data Nat = Zero | Succ(Nat); - data Bool = False | True; + enum Nat { Zero, Succ(Nat) } + enum Bool { False, True } - public function even (n : Nat) -> Bool { - match n { - | Nat.Zero => return Bool.True; - | Nat.Succ(m) => return odd(m); - } + function even (n : Nat) public returns (Bool) { + match (n ) { + case Nat.Zero { return Bool.True; + } case Nat.Succ(m) { return odd(m); + } } } - public function odd(n : Nat) -> Bool { - match n { - | Nat.Zero => return Bool.False; - | Nat.Succ(m) => return even(m); - } + function odd(n : Nat) public returns (Bool) { + match (n ) { + case Nat.Zero { return Bool.False; + } case Nat.Succ(m) { return even(m); + } } } - public function main() -> word { return 0; } + function main() public returns (word) { return 0; } } diff --git a/test/examples/cases/Filter.solc b/test/examples/cases/Filter.solc index fd0d0d595..3fab130e7 100644 --- a/test/examples/cases/Filter.solc +++ b/test/examples/cases/Filter.solc @@ -1,51 +1,51 @@ -data List(a) = Nil | Cons(a,List(a)); -data Bool = False | True; +enum List { Nil, Cons(a, List) } +enum Bool { False, True } -function and(x : Bool, y : Bool) -> Bool { - match x, y { - | Bool.False, _ => return Bool.False; - | Bool.True, z => return z; - } +function and(x : Bool, y : Bool) returns (Bool) { + match (x, y ) { + case (Bool.False, _ ) { return Bool.False; + } case (Bool.True, z ) { return z; + } } } -class a : Eq { - function eq (x : a, y : a) -> Bool ; +trait Eq { + function eq (x : a, y : a) returns (Bool) ; } -instance Word : Eq { - function eq (x : Word, y : Word) -> Bool { - match primEqWord(x,y) { - | 0 => return Bool.False ; - | _ => return Bool.True ; - } +impl Eq { + function eq (x : Word, y : Word) returns (Bool) { + match (primEqWord(x,y) ) { + case 0 { return Bool.False ; + } default { return Bool.True ; + } } } } -function filter (f : (Word) -> Bool, xs : List(Word)) -> List(Word) { - match xs { - | List.Nil => return List.Nil ; - | List.Cons(y,ys) => - match f(y) { - | Bool.False => return filter(f,ys); - | Bool.True => return List.Cons(y,filter(f,ys)); - } - } +function filter (f : function(Word) internal returns (Bool), xs : List) returns (List) { + match (xs ) { + case List.Nil { return List.Nil ; + } case List.Cons(y,ys) { + match (f(y) ) { + case Bool.False { return filter(f,ys); + } case Bool.True { return List.Cons(y,filter(f,ys)); + } } + } } } -function list1 () -> List(Word) { +function list1 () returns (List) { return List.Cons(1, List.Cons(2, List.Cons(3, List.Nil))); } -function foo0(y : Word) -> List(Word) { +function foo0(y : Word) returns (List) { return filter((lam (x){ return eq(x,y); }), list1()); } -function foo1() -> List(Word) { +function foo1() returns (List) { return filter((lam (x){ return eq(x,1); }), list1()); } -function foo2(p : (Word) -> Bool, q : (Word) -> Bool) -> List(Word) { +function foo2(p : function(Word) internal returns (Bool), q : function(Word) internal returns (Bool)) returns (List) { return filter(lam (x) { return and(p(x), q(x)) ; } , list1()); } diff --git a/test/examples/cases/Foo.solc b/test/examples/cases/Foo.solc index c416cd9f5..756176f18 100644 --- a/test/examples/cases/Foo.solc +++ b/test/examples/cases/Foo.solc @@ -1,8 +1,8 @@ - function one() -> word { + function one() returns (word) { return primAddWord(1, zero()) ; } - function zero () -> word { + function zero () returns (word) { return 0; } diff --git a/test/examples/cases/GetSet.solc b/test/examples/cases/GetSet.solc index b8da15856..c19927505 100644 --- a/test/examples/cases/GetSet.solc +++ b/test/examples/cases/GetSet.solc @@ -1,11 +1,11 @@ contract GetSet { value : Word ; - public function setValue (x) { + function setValue (x) public { value = x ; } - public function getValue () { + function getValue () public { return value ; } } diff --git a/test/examples/cases/GoodInstance.solc b/test/examples/cases/GoodInstance.solc index 14cf8a799..e03475dd0 100644 --- a/test/examples/cases/GoodInstance.solc +++ b/test/examples/cases/GoodInstance.solc @@ -1,31 +1,31 @@ -class a:Enum { - function fromEnum(x:a) -> Word; +trait Enum { + function fromEnum(x:a) returns (Word); } - data Color = R | G | B; + enum Color { R, G, B } -instance Color : Enum { - function fromEnum(c : Color) -> Word { - match c { - | Color.R => return 1; - | Color.G => return 2; - | Color.B => return 3; - } +impl Enum { + function fromEnum(c : Color) returns (Word) { + match (c ) { + case Color.R { return 1; + } case Color.G { return 2; + } case Color.B { return 3; + } } } } -data Bool = False | True; +enum Bool { False, True } -instance Bool : Enum { - function fromEnum(b : Bool) -> Word { - match b { - | Bool.False => return 0; - | Bool.True => return 1; - } +impl Enum { + function fromEnum(b : Bool) returns (Word) { + match (b ) { + case Bool.False { return 0; + } case Bool.True { return 1; + } } } } contract GoodInstance { - public function main() -> Word { return fromEnum(Bool.True);} + function main() public returns (Word) { return fromEnum(Bool.True);} } diff --git a/test/examples/cases/Id.solc b/test/examples/cases/Id.solc index 1594f7cb0..fdfe5acf4 100644 --- a/test/examples/cases/Id.solc +++ b/test/examples/cases/Id.solc @@ -1,9 +1,9 @@ -function id (x : word) -> word { +function id (x : word) returns (word) { return x; } contract Id { - public function main () -> word { + function main () public returns (word) { return id(0); } } diff --git a/test/examples/cases/IncompleteInstDef.solc b/test/examples/cases/IncompleteInstDef.solc index 4d86d53e1..fc6e3d3f6 100644 --- a/test/examples/cases/IncompleteInstDef.solc +++ b/test/examples/cases/IncompleteInstDef.solc @@ -1,14 +1,14 @@ -forall a b . class a : Foo(b) { - function foo (x : a, y : b) -> b ; - function faa (y : a) -> a ; +trait Foo { + function foo (x : a, y : b) returns (b) ; + function faa (y : a) returns (a) ; } -data Bool = False | True; +enum Bool { False, True } -data Maybe(a) = Nothing | Just(a); +enum Maybe { Nothing, Just(a) } // missing the definition of Foo.foo -instance Bool : Foo(Bool) { - function faa(y : Bool) -> Bool { +impl Foo { + function faa(y : Bool) returns (Bool) { return y ; } } diff --git a/test/examples/cases/Invokable.solc b/test/examples/cases/Invokable.solc index 35e527358..39d0daccb 100644 --- a/test/examples/cases/Invokable.solc +++ b/test/examples/cases/Invokable.solc @@ -1,16 +1,16 @@ -class self : invokable(args, ret) { - function invoke (s:self, a:args) -> ret; +trait invokable { + function invoke (s:self, a:args) returns (ret); } - forall a . function id(x : a) -> a { + function id(x : a) returns (a) { return x ; } - data IdToken(a) = IdToken; + enum IdToken { IdToken } -instance IdToken(a) : invokable(a,a) { - function invoke(token: IdToken(a), a) -> a { +impl invokable, a, a> { + function invoke(token: IdToken, a) returns (a) { return id(a); } } diff --git a/test/examples/cases/KindTest.solc b/test/examples/cases/KindTest.solc index 9a4399f53..69772cf5d 100644 --- a/test/examples/cases/KindTest.solc +++ b/test/examples/cases/KindTest.solc @@ -1,5 +1,5 @@ -data M = M; -function foo(x: M(Word)) {} +enum M { M } +function foo(x: M) {} -data P(a) = P; +enum P { P } function foo2(x: P) {} diff --git a/test/examples/cases/ListModule.solc b/test/examples/cases/ListModule.solc index ec5343fa6..4944f94d7 100644 --- a/test/examples/cases/ListModule.solc +++ b/test/examples/cases/ListModule.solc @@ -1,26 +1,26 @@ contract ListModule { - data List(a) = Nil | Cons(a,List(a)); - data Bool = True | False; + enum List { Nil, Cons(a, List) } + enum Bool { True, False } - forall a b c . public function zipWith (f : (a,b) -> c,xs : List(a),ys : List(b)) -> List(c) { - match xs, ys { - | List.Nil, List.Nil => return List.Nil ; - | List.Cons(x1,xs1), List.Cons(y1,ys1) => + function zipWith (f : function((a, b)) internal returns (c),xs : List,ys : List) public returns (List) { + match (xs, ys ) { + case (List.Nil, List.Nil ) { return List.Nil ; + } case (List.Cons(x1,xs1), List.Cons(y1,ys1) ) { return List.Cons(f(x1,y1), zipWith(f,xs1,ys1)) ; - | _, _ => return List.Nil; - } + } default { return List.Nil; + } } } - forall a b . public function foldr(f : (a,b) -> b, v : b, xs : List(a)) -> b { - match xs { - | List.Nil => return v; - | List.Cons(y,ys) => + function foldr(f : function((a, b)) internal returns (b), v : b, xs : List) public returns (b) { + match (xs ) { + case List.Nil { return v; + } case List.Cons(y,ys) { return f(y, foldr(f,v,ys)) ; - } + } } } - public function main () -> word { + function main () public returns (word) { return 0; } } diff --git a/test/examples/cases/Logic.solc b/test/examples/cases/Logic.solc index e5463613a..550da5c42 100644 --- a/test/examples/cases/Logic.solc +++ b/test/examples/cases/Logic.solc @@ -1,35 +1,35 @@ contract Logic { - data Bool = True | False; + enum Bool { True, False } - public function not (x : Bool) -> Bool { - match x { - | Bool.True => return Bool.False ; - | Bool.False => return Bool.True ; - } + function not (x : Bool) public returns (Bool) { + match (x ) { + case Bool.True { return Bool.False ; + } case Bool.False { return Bool.True ; + } } } - public function and(x : Bool, y : Bool) -> Bool { - match x, y { - | Bool.False, _ => return Bool.False ; - | Bool.True , _ => return y ; - } + function and(x : Bool, y : Bool) public returns (Bool) { + match (x, y ) { + case (Bool.False, _ ) { return Bool.False ; + } case (Bool.True , _ ) { return y ; + } } } - public function and1 (x : Bool, y : Bool) -> Bool { - match x, y { - | Bool.False, Bool.False => return Bool.False ; - | Bool.True , Bool.False => return Bool.False; - | Bool.False ,Bool.True => return Bool.False; - | Bool.True, Bool.True => return Bool.True; - } + function and1 (x : Bool, y : Bool) public returns (Bool) { + match (x, y ) { + case (Bool.False, Bool.False ) { return Bool.False ; + } case (Bool.True , Bool.False ) { return Bool.False; + } case (Bool.False ,Bool.True ) { return Bool.False; + } case (Bool.True, Bool.True ) { return Bool.True; + } } } - public function elim (f : word, g : word, x : Bool) -> word { - match x { - | Bool.True => return f; - | Bool.False => return g; - } + function elim (f : word, g : word, x : Bool) public returns (word) { + match (x ) { + case Bool.True { return f; + } case Bool.False { return g; + } } } - public function main() -> word { return 0; } + function main() public returns (word) { return 0; } } diff --git a/test/examples/cases/MatchCall.solc b/test/examples/cases/MatchCall.solc index c4c4be10c..6ef800250 100644 --- a/test/examples/cases/MatchCall.solc +++ b/test/examples/cases/MatchCall.solc @@ -1,14 +1,14 @@ -data Bool = False | True; +enum Bool { False, True } contract MatchCall { - public function f() -> Bool { + function f() public returns (Bool) { return Bool.True; } - public function main() -> word { - match f() { - | Bool.True => return 42; - | Bool.False => return 0; - } + function main() public returns (word) { + match (f() ) { + case Bool.True { return 42; + } case Bool.False { return 0; + } } } } diff --git a/test/examples/cases/Memory1.solc b/test/examples/cases/Memory1.solc index af3a5ecda..ea3747dd3 100644 --- a/test/examples/cases/Memory1.solc +++ b/test/examples/cases/Memory1.solc @@ -1,7 +1,7 @@ -data memory(a) = memory(word); +enum memory { memory(word) } -function g() -> () { - let x : memory(memory(word)); - let y : memory(word) = memory(1); +function g() returns (()) { + let x : word memory memory; + let y : word memory = memory(1); x = memory(0); } diff --git a/test/examples/cases/Memory2.solc b/test/examples/cases/Memory2.solc index 64fb9d950..cd6430d00 100644 --- a/test/examples/cases/Memory2.solc +++ b/test/examples/cases/Memory2.solc @@ -1,5 +1,5 @@ -data Memory(a) = Memory(word); +enum Memory { Memory(word) } -function g() -> () { - let x : Memory(Memory(word)) = Memory(0); +function g() returns (()) { + let x : Memory> = Memory(0); } diff --git a/test/examples/cases/Mutuals.solc b/test/examples/cases/Mutuals.solc index aa2d70e43..3bab19d3f 100644 --- a/test/examples/cases/Mutuals.solc +++ b/test/examples/cases/Mutuals.solc @@ -1,8 +1,8 @@ contract Mutual { - public function main () -> word { + function main () public returns (word) { return f(); } - public function f () -> word { + function f () public returns (word) { return 42; } } diff --git a/test/examples/cases/NegPair.solc b/test/examples/cases/NegPair.solc index d3d9da626..8e3329ff4 100644 --- a/test/examples/cases/NegPair.solc +++ b/test/examples/cases/NegPair.solc @@ -1,53 +1,53 @@ -forall a . class a : Neg { - function neg(x:a) -> a; +trait Neg { + function neg(x:a) returns (a); } -data B = F | T; +enum B { F, T } -instance B : Neg { - function neg (x : B) -> B { - match x { - | B.F => return B.T; - | B.T => return B.F; - } +impl Neg { + function neg (x : B) returns (B) { + match (x ) { + case B.F { return B.T; + } case B.T { return B.F; + } } } } -forall a b . function fst (p : (a,b)) -> a { - match p { - | (x,y) => return x; - } +function fst (p : (a, b)) returns (a) { + match (p ) { + case (x,y) { return x; + } } } -forall a b . function snd(p : (a,b)) -> b { - match p { - | (x,y) => return y; - } +function snd(p : (a, b)) returns (b) { + match (p ) { + case (x,y) { return y; + } } } -forall a b . a : Neg, b : Neg => instance (a,b):Neg { - function neg(p : (a,b)) -> (a,b) { +impl Neg<(a, b)> where a: Neg, b: Neg { + function neg(p : (a, b)) returns ((a, b)) { return (Neg.neg (fst(p)), Neg.neg(snd (p))); } } contract NegPair { - public function bnot(x : B) -> B { - match x { - | B.T => return B.F; - | B.F => return B.T; - } + function bnot(x : B) public returns (B) { + match (x ) { + case B.T { return B.F; + } case B.F { return B.T; + } } } - public function fromB(b : B) -> word { - match b { - | B.F => return 0; - | B.T => return 1; - } + function fromB(b : B) public returns (word) { + match (b ) { + case B.F { return 0; + } case B.T { return 1; + } } } - public function main() -> word { return fromB(fst(Neg.neg((B.F,B.T)))); } + function main() public returns (word) { return fromB(fst(Neg.neg((B.F,B.T)))); } } diff --git a/test/examples/cases/Option.solc b/test/examples/cases/Option.solc index 5176d111f..38a4e23ff 100644 --- a/test/examples/cases/Option.solc +++ b/test/examples/cases/Option.solc @@ -1,13 +1,13 @@ contract Option { - data Option(a) = None | Some(a); + enum Option { None, Some(a) } - public function join(mmx : Option(Option(word))) -> Option(word) { - match mmx { - | Option.None => return Option.None; - | Option.Some(Option.Some(x)) => return Option.Some(x); - | Option.Some(Option.None) => return Option.None; - } + function join(mmx : Option>) public returns (Option) { + match (mmx ) { + case Option.None { return Option.None; + } case Option.Some(Option.Some(x)) { return Option.Some(x); + } case Option.Some(Option.None) { return Option.None; + } } } - public function main() -> word { return 0; } + function main() public returns (word) { return 0; } } diff --git a/test/examples/cases/Pair.solc b/test/examples/cases/Pair.solc index 5e698e453..df753a0c9 100644 --- a/test/examples/cases/Pair.solc +++ b/test/examples/cases/Pair.solc @@ -1,27 +1,27 @@ - forall a b . function fst (x : (a,b)) -> a { - match x { - | (a,_) => return a; - } + function fst (x : (a, b)) returns (a) { + match (x ) { + case (a,_) { return a; + } } } - forall a b . function snd(x : (a,b)) -> b { - match x { - | (_,b) => return b; - } + function snd(x : (a, b)) returns (b) { + match (x ) { + case (_,b) { return b; + } } } - function uncurry(f : (word, word) -> word, x : (word,word)) -> word { - match x { - | (a,b) => return f(a,b); - } + function uncurry(f : function((word, word)) internal returns (word), x : (word, word)) returns (word) { + match (x ) { + case (a,b) { return f(a,b); + } } } - function snds (p1 : (word,word), p2 : (word,word)) -> (word,word) { - match p1, p2 { - | (a,b) , (c,d) => return (b,d); - } + function snds (p1 : (word, word), p2 : (word, word)) returns ((word, word)) { + match (p1, p2 ) { + case ((a,b) , (c,d) ) { return (b,d); + } } } - function curry(f : ((word,word)) -> word, x : word, y : word) -> word { + function curry(f : function((word, word)) internal returns (word), x : word, y : word) returns (word) { return f((x,y)) ; } diff --git a/test/examples/cases/PairMatch1.solc b/test/examples/cases/PairMatch1.solc index 3f2a46364..155603d8f 100644 --- a/test/examples/cases/PairMatch1.solc +++ b/test/examples/cases/PairMatch1.solc @@ -1,6 +1,6 @@ -data Pair(a, b) = Pair(a, b); +enum Pair { Pair(a, b) } -forall a . function foo(p: a) -> word { +function foo(p: a) returns (word) { let x: word = p; return x; } diff --git a/test/examples/cases/PairMatch2.solc b/test/examples/cases/PairMatch2.solc index 98395bb4f..44e88844f 100644 --- a/test/examples/cases/PairMatch2.solc +++ b/test/examples/cases/PairMatch2.solc @@ -1,8 +1,8 @@ -forall a . function snd(p: (a, word)) -> a { - match p { - | (_, w) => return w; - } +function snd(p: (a, word)) returns (a) { + match (p ) { + case (_, w) { return w; + } } } diff --git a/test/examples/cases/Peano.solc b/test/examples/cases/Peano.solc index 4deac8611..1fa1363c9 100644 --- a/test/examples/cases/Peano.solc +++ b/test/examples/cases/Peano.solc @@ -1,12 +1,12 @@ -data Nat = Zero | Succ(Nat); +enum Nat { Zero, Succ(Nat) } -function natInd (step : (Nat, Nat) -> Nat, v : Nat, n : Nat) -> Nat { - match n { - | Nat.Zero => return v ; - | Nat.Succ(m) => return step(m, natInd(step,v,m)); - } +function natInd (step : function((Nat, Nat)) internal returns (Nat), v : Nat, n : Nat) returns (Nat) { + match (n ) { + case Nat.Zero { return v ; + } case Nat.Succ(m) { return step(m, natInd(step,v,m)); + } } } -function add(n : Nat, m : Nat) -> Nat { +function add(n : Nat, m : Nat) returns (Nat) { return natInd (lam (x, acc) {return Nat.Succ(acc) ; }, m, n); } diff --git a/test/examples/cases/PeanoMatch.solc b/test/examples/cases/PeanoMatch.solc index 696c5136d..b1583dbed 100644 --- a/test/examples/cases/PeanoMatch.solc +++ b/test/examples/cases/PeanoMatch.solc @@ -1,9 +1,9 @@ -data Nat = Zero | Succ(Nat); +enum Nat { Zero, Succ(Nat) } -function foo(n : Nat) -> Nat { - match n { - | Nat.Zero => return Nat.Succ(Nat.Zero) ; - | Nat.Succ(Nat.Succ(x)) => return x; - | x => return Nat.Zero; - } +function foo(n : Nat) returns (Nat) { + match (n ) { + case Nat.Zero { return Nat.Succ(Nat.Zero) ; + } case Nat.Succ(Nat.Succ(x)) { return x; + } case x { return Nat.Zero; + } } } diff --git a/test/examples/cases/Ref.solc b/test/examples/cases/Ref.solc index afce6aa53..b1930c9a3 100644 --- a/test/examples/cases/Ref.solc +++ b/test/examples/cases/Ref.solc @@ -1,15 +1,15 @@ -class ref : Ref(deref) { - function load (r : ref) -> deref; - function store (r : ref, d : deref) -> unit; +trait Ref { + function load (r : ref) returns (deref); + function store (r : ref, d : deref) returns (unit); } -data Memory(a) = new(a); +enum Memory { new(a) } -instance Memory(a) : Ref(a) { +impl Ref, a> { function load (r) { - match r { - | Memory.new(x) => return x; - } + match (r ) { + case Memory.new(x) { return x; + } } } } diff --git a/test/examples/cases/RefDeref.solc b/test/examples/cases/RefDeref.solc index f096ffb56..50d5c258e 100644 --- a/test/examples/cases/RefDeref.solc +++ b/test/examples/cases/RefDeref.solc @@ -1,12 +1,10 @@ -forall ref deref . class ref:Loadable (deref) { - function load (r : ref) -> deref; +trait Loadable { + function load (r : ref) returns (deref); } -forall ref deref . class ref:Storable (deref) { - function store (r : ref, d : deref) -> (); +trait Storable { + function store (r : ref, d : deref) returns (()); } // haskell style class constraints -forall ref deref . - ref : Loadable(deref) - , ref : Storable(ref) => class ref:Ref (deref) {} +trait Ref where ref: Loadable, ref: Storable {} diff --git a/test/examples/cases/SillyReturn.solc b/test/examples/cases/SillyReturn.solc index 25dddd329..c2f34b946 100644 --- a/test/examples/cases/SillyReturn.solc +++ b/test/examples/cases/SillyReturn.solc @@ -1,9 +1,9 @@ -data Nat = Zero | Succ(Nat); -data Bool = True | False; +enum Nat { Zero, Succ(Nat) } +enum Bool { True, False } -function even (n) -> Bool { - match n { - | Nat.Zero => return 1; return Bool.True; - | Nat.Succ(m) => return 0; return Bool.False; - } +function even (n) returns (Bool) { + match (n ) { + case Nat.Zero { return 1; return Bool.True; + } case Nat.Succ(m) { return 0; return Bool.False; + } } } diff --git a/test/examples/cases/SimpleInvoke.solc b/test/examples/cases/SimpleInvoke.solc index 09f5ce97b..6ecf1e2d1 100644 --- a/test/examples/cases/SimpleInvoke.solc +++ b/test/examples/cases/SimpleInvoke.solc @@ -1,17 +1,17 @@ function lambdaimpl1 (x) { return x; } -data LambdaTy0(a) = LambdaTy0; -class self : invokable (args, ret) { - function invoke (self : self, args : args) -> ret; +enum LambdaTy0 { LambdaTy0 } +trait invokable { + function invoke (self : self, args : args) returns (ret); } -instance LambdaTy0(a) : invokable (a, a) { - forall a . function invoke (self : LambdaTy0(a), args : a) -> a { +impl invokable, a, a> { + function invoke (self : LambdaTy0, args : a) returns (a) { return lambdaimpl1(args); } } contract SimpleLambda { - public function f () { + function f () public { let n = LambdaTy0 ; return invokable.invoke(n, 0); } diff --git a/test/examples/cases/SimpleLambda.solc b/test/examples/cases/SimpleLambda.solc index 1a68797e8..6d5cf5553 100644 --- a/test/examples/cases/SimpleLambda.solc +++ b/test/examples/cases/SimpleLambda.solc @@ -1,4 +1,4 @@ -function addWord(x : word, y : word) -> word { +function addWord(x : word, y : word) returns (word) { let res: word; assembly { res := add(x, y) @@ -7,7 +7,7 @@ function addWord(x : word, y : word) -> word { } contract SimpleLambda{ - public function f (z : word) -> word { + function f (z : word) public returns (word) { let n = lam (x : word, y : word) { return addWord(x,addWord(y,1)); } ; @@ -16,7 +16,7 @@ contract SimpleLambda{ } ; return m(n(1,0)); } - public function main() -> word { + function main() public returns (word) { return f(40); } } diff --git a/test/examples/cases/SingleFun.solc b/test/examples/cases/SingleFun.solc index 0f93d869c..2dca06980 100644 --- a/test/examples/cases/SingleFun.solc +++ b/test/examples/cases/SingleFun.solc @@ -1,3 +1,3 @@ -function id (x : word) -> word { +function id (x : word) returns (word) { return x ; } diff --git a/test/examples/cases/StructMembers.solc b/test/examples/cases/StructMembers.solc index 89508d448..a2d7a9072 100644 --- a/test/examples/cases/StructMembers.solc +++ b/test/examples/cases/StructMembers.solc @@ -1,24 +1,24 @@ /// Other used stdlib classes and types: -class self:Ref(deref) { - function load(x:self) -> deref; +trait Ref { + function load(x:self) returns (deref); } -data Uint256 = Uint256(Word) -data Bool = True | False -data Bytes32 = Bytes32(Word) -data Unit = Unit +enum Uint256 { Uint256(Word) } +enum Bool { True, False } +enum Bytes32 { Bytes32(Word) } +enum Unit { Unit } -data Proxy(t) = Proxy -data Memory(x) = Memory(Word) +enum Proxy { Proxy } +enum Memory { Memory(Word) } /// Specific new stdlib classes and types: -class self:StructMember(preceding, memberTy) {} -data StructMember(structType, fieldType) = StructMember +trait StructMember {} +enum StructMember { StructMember } // "dead" is only here to compensate for non-relaxed coverage condition and // incorrectly implemented Paterson condition -data MemberAccess(ty, field, dead) = MemberAccess(ty) +enum MemberAccess { MemberAccess(ty) } /// Usage Example / Proof of Concept: @@ -31,16 +31,16 @@ data MemberAccess(ty, field, dead) = MemberAccess(ty) } */ -data S = S(Pair(Uint256, Pair(Bool, Bytes32))) +enum S { S(Pair>) } -data Field_x = FieldX // Selector type for "x" -data Field_y = FieldY // Selector type for "y" -data Field_z = FieldZ // Selector type for "z" +enum Field_x { FieldX } // Selector type for "x" +enum Field_y { FieldY } // Selector type for "y" +enum Field_z { FieldZ } // Selector type for "z" // StructMember instances for field selectors: -instance StructMember(S, Field_x):StructMember(Unit, Uint256) {} -instance StructMember(S, Field_y):StructMember(Uint256, Bool) {} -instance StructMember(S, Field_z):StructMember(Pair(Uint256, Bool), Bytes32) {} +impl StructMember, Unit, Uint256> {} +impl StructMember, Uint256, Bool> {} +impl StructMember, Pair, Bytes32> {} /* Further compiler-internal builtin instances for use on stack (at least the stackref versions cannot be expressed in-language, * but none of these rely on any layout other than the compiler-builtin stack layout, so we can handle these purely internally @@ -57,82 +57,73 @@ instance StructMember(S, Field_z):StructMember(Pair(Uint256, Bool), Bytes32) {} /// Size of a type in memory -class self:MemorySize { - function memorySize(x:Proxy(self)) -> Word; +trait MemorySize { + function memorySize(x:Proxy) returns (Word); } /// Size of the struct member types in memory: -instance Unit:MemorySize { function memorySize(x : Proxy(Unit)) -> Word { return 0; } } -instance Uint256:MemorySize { function memorySize(x : Proxy(Uint256)) -> Word { return 32; } } -instance Bool:MemorySize { function memorySize(x : Proxy(Bool)) -> Word { return 32; } } -instance Bytes32:MemorySize { function memorySize(x : Proxy(Bytes32)) -> Word { return 32; } } +impl MemorySize { function memorySize(x : Proxy) returns (Word) { return 0; } } +impl MemorySize { function memorySize(x : Proxy) returns (Word) { return 32; } } +impl MemorySize { function memorySize(x : Proxy) returns (Word) { return 32; } } +impl MemorySize { function memorySize(x : Proxy) returns (Word) { return 32; } } /// Memory size of pairs -instance Pair(a,b):MemorySize { - function memorySize(x : Proxy((a,b))) -> Word +impl MemorySize> { + function memorySize(x : Proxy<(a, b)>) returns (Word) { - let pa:Proxy(a); - let pb:Proxy(b); + let pa:Proxy; + let pb:Proxy; let sz = memorySize(pa); let szb = memorySize(pb); - assembly { sz := add(sz, szb) }; // TODO: bounds check? + assembly { sz := add(sz, szb) } // TODO: bounds check? return sz; } } /// Fragments of a generic memory implementation: -class self:MemoryType { - function loadFromMemory(p:Proxy(self), off:Word) -> self; +trait MemoryType { + function loadFromMemory(p:Proxy, off:Word) returns (self); } -instance Uint256:MemoryType { - function loadFromMemory(p:Proxy(Uint256), off:Word) -> Uint256 { +impl MemoryType { + function loadFromMemory(p:Proxy, off:Word) returns (Uint256) { let v; - assembly { v := mload(off) }; + assembly { v := mload(off) } return Uint256(v); } } -instance (a:MemoryType) => Memory(a):Ref(a) { - function load(x : Memory(a)) -> a { - let p:Proxy(a); - match x { | Memory(off) => return loadFromMemory(p, off); }; +impl Ref, a> where a: MemoryType { + function load(x : Memory) returns (a) { + let p:Proxy; + match (x ) { case Memory(off) { return loadFromMemory(p, off); } } } } /// Crucial instance: member access to struct fields in memory: -instance ( - StructMember(structType, fieldType):StructMember(precedingTuple, ty), - precedingTuple:MemorySize, - Memory(ty):Ref(ty) -) => MemberAccess(Memory(structType), fieldType, - // Needs ridiculous amounts of constructor applications due to incorrect implementation of the Paterson Condition - // Needs to mention "ty" due to non-relaxed Coverage Condition - Memory(ty) -):Ref(ty) -{ - function load(x : MemberAccess(Memory(structType), fieldType, Memory(ty))) -> ty { +// Needs ridiculous amounts of constructor applications due to incorrect implementation of the Paterson Condition +// Needs to mention "ty" due to non-relaxed Coverage Condition +impl Ref, fieldType, Memory>, ty> where StructMember: StructMember, precedingTuple: MemorySize, Memory: Ref { + function load(x : MemberAccess, fieldType, Memory>) returns (ty) { let ptr:Word; - match x { | MemberAccess(Memory(y)) => ptr = y; }; + match (x ) { case MemberAccess(Memory(y)) { ptr = y; } } - let p:Proxy(precedingTuple); + let p:Proxy; let offset = memorySize(p); - assembly { ptr := add(ptr, offset) }; + assembly { ptr := add(ptr, offset) } - let tyPtr:Memory(ty) = Memory(ptr); + let tyPtr:Memory = Memory(ptr); return load(tyPtr); } } -function test() -> () +function test() returns (()) { - let x:Memory(S); - let memberAccess:MemberAccess(Memory(S), Field_x, - Memory(Uint256) // will become unnecessary - ); + let x:Memory; + let memberAccess:MemberAccess, Field_x, Memory>; memberAccess = MemberAccess(x); let result = load(memberAccess); /* diff --git a/test/examples/cases/Uncurry.solc b/test/examples/cases/Uncurry.solc index bde18537f..c70c63048 100644 --- a/test/examples/cases/Uncurry.solc +++ b/test/examples/cases/Uncurry.solc @@ -1,5 +1,5 @@ -function uncurry (f : word, p : (word, word)) -> word { - match p { - | (x,y) => return f(x,y); - } +function uncurry (f : word, p : (word, word)) returns (word) { + match (p ) { + case (x,y) { return f(x,y); + } } } diff --git a/test/examples/cases/abigeneric.solc b/test/examples/cases/abigeneric.solc index 95d450295..911ebf0de 100644 --- a/test/examples/cases/abigeneric.solc +++ b/test/examples/cases/abigeneric.solc @@ -1,36 +1,35 @@ -pragma no-patterson-condition ABIAttribs, ABIEncode, ABIDecode; -pragma no-bounded-variable-condition ABIAttribs, ABIEncode, ABIDecode; -pragma no-coverage-condition ABIDecode; +pragma solcore noPattersonCondition ABIAttribs, ABIEncode, ABIDecode; +pragma solcore noBoundVariableCondition ABIAttribs, ABIEncode, ABIDecode; +pragma solcore noCoverageCondition ABIDecode; export { encode, decode }; -import std.{*}; -import std.opcodes.{mstore}; -import std.Generic.{*}; +import {*} from std; +import {mstore} from std.opcodes; +import {*} from std.Generic; -function maxWord(a : word, b : word) -> word { - match gtWord(a, b) { - | true => return a; - | false => return b; - } +function maxWord(a : word, b : word) returns (word) { + match (gtWord(a, b) ) { + case true { return a; + } case false { return b; + } } } // ─── ABIAttribs for the primitive sum(f, g) type ───────────────────────── // headSize = 32 (tag word) + max(headSize(f), headSize(g)) -forall f g . f:ABIAttribs, g:ABIAttribs => -instance sum(f, g) : ABIAttribs { - function headSize(ty : Proxy(sum(f, g))) -> word { - let pf : Proxy(f); - let pg : Proxy(g); +impl ABIAttribs> where f: ABIAttribs, g: ABIAttribs { + function headSize(ty : Proxy>) returns (word) { + let pf : Proxy; + let pg : Proxy; return 32 + maxWord(ABIAttribs.headSize(pf), ABIAttribs.headSize(pg)); } - function isStatic(ty : Proxy(sum(f, g))) -> bool { - let pf : Proxy(f); - let pg : Proxy(g); + function isStatic(ty : Proxy>) returns (bool) { + let pf : Proxy; + let pg : Proxy; return and(ABIAttribs.isStatic(pf), ABIAttribs.isStatic(pg)); } } @@ -40,63 +39,55 @@ instance sum(f, g) : ABIAttribs { // [offset + 0 .. offset + 31] : tag word (0 = inl, 1 = inr) // [offset + 32 .. ] : encoded branch payload -forall f g . f:ABIAttribs, f:ABIEncode, g:ABIAttribs, g:ABIEncode => -instance sum(f, g) : ABIEncode { - function encodeInto(x : sum(f, g), basePtr : word, offset : word, tail : word) -> word { - match x { - | inl(v) => +impl ABIEncode> where f: ABIAttribs, f: ABIEncode, g: ABIAttribs, g: ABIEncode { + function encodeInto(x : sum, basePtr : word, offset : word, tail : word) returns (word) { + match (x ) { + case inl(v) { mstore(basePtr + offset, 0); return ABIEncode.encodeInto(v, basePtr, offset + 32, tail); - | inr(v) => + } case inr(v) { mstore(basePtr + offset, 1); return ABIEncode.encodeInto(v, basePtr, offset + 32, tail); - } + } } } } // ─── ABIDecode for sum(f, g) ───────────────────────────────────────────── // Reads the tag word at headOffset; dispatches to f or g decoder at headOffset + 32. -forall f g reader . - reader : WordReader, - f : ABIAttribs, - ABIDecoder(f, reader) : ABIDecode(f), - ABIDecoder(g, reader) : ABIDecode(g) => -instance ABIDecoder(sum(f, g), reader) : ABIDecode(sum(f, g)) { - function decode(ptr : ABIDecoder(sum(f, g), reader), headOffset : word) -> sum(f, g) { - match ptr { - | ABIDecoder(rdr) => +impl ABIDecode, reader>, sum> where reader: WordReader, f: ABIAttribs, ABIDecoder: ABIDecode, ABIDecoder: ABIDecode { + function decode(ptr : ABIDecoder, reader>, headOffset : word) returns (sum) { + match (ptr ) { + case ABIDecoder(rdr) { let tag = WordReader.read(WordReader.advance(rdr, headOffset)); - match tag { - | 0 => - let dec_f : ABIDecoder(f, reader) = ABIDecoder(rdr); + match (tag ) { + case 0 { + let dec_f : ABIDecoder = ABIDecoder(rdr); return inl(ABIDecode.decode(dec_f, headOffset + 32)); - | _ => - let dec_g : ABIDecoder(g, reader) = ABIDecoder(rdr); + } default { + let dec_g : ABIDecoder = ABIDecoder(rdr); return inr(ABIDecode.decode(dec_g, headOffset + 32)); - } - } + } } + } } } } // ─── Default bridges: ABIAttribs and ABIEncode via Generic ─────────────── // Any type 'a' with Generic(rep) inherits its ABI layout from rep. -forall a rep . a:Generic(rep), rep:ABIAttribs => -default instance a : ABIAttribs { - function headSize(ty : Proxy(a)) -> word { - let prx : Proxy(rep); +default impl ABIAttribs where a: Generic, rep: ABIAttribs { + function headSize(ty : Proxy) returns (word) { + let prx : Proxy; return ABIAttribs.headSize(prx); } - function isStatic(ty : Proxy(a)) -> bool { - let prx : Proxy(rep); + function isStatic(ty : Proxy) returns (bool) { + let prx : Proxy; return ABIAttribs.isStatic(prx); } } -forall a rep . a:Generic(rep), rep:ABIAttribs, rep:ABIEncode => -default instance a : ABIEncode { - function encodeInto(x : a, basePtr : word, offset : word, tail : word) -> word { +default impl ABIEncode where a: Generic, rep: ABIAttribs, rep: ABIEncode { + function encodeInto(x : a, basePtr : word, offset : word, tail : word) returns (word) { return ABIEncode.encodeInto(Generic.from(x), basePtr, offset, tail); } } @@ -105,8 +96,7 @@ default instance a : ABIEncode { // Serialises any 'a' that has a Generic(rep) instance. // Only the Generic instance is required — ABIEncode is resolved via the bridge. -forall a rep . a:Generic(rep), rep:ABIAttribs, rep:ABIEncode => -function encode(x : a, basePtr : word, offset : word, tail : word) -> word { +function encode(x : a, basePtr : word, offset : word, tail : word) returns (word) where a: Generic, rep: ABIAttribs, rep: ABIEncode { let xrep : rep = Generic.from(x); return ABIEncode.encodeInto(xrep, basePtr, offset, tail); } @@ -115,14 +105,10 @@ function encode(x : a, basePtr : word, offset : word, tail : word) -> word { // Deserialises any 'a' that has a Generic(rep) instance. // Only the Generic instance is required — ABIDecode is resolved via the bridge. -forall a rep reader . - a : Generic(rep), - reader : WordReader, - ABIDecoder(rep, reader) : ABIDecode(rep) => -function decode(ptr : ABIDecoder(a, reader), headOffset : word) -> a { - match ptr { - | ABIDecoder(rdr) => - let rep_ptr : ABIDecoder(rep, reader) = ABIDecoder(rdr); +function decode(ptr : ABIDecoder, headOffset : word) returns (a) where a: Generic, reader: WordReader, ABIDecoder: ABIDecode { + match (ptr ) { + case ABIDecoder(rdr) { + let rep_ptr : ABIDecoder = ABIDecoder(rdr); return Generic.to(ABIDecode.decode(rep_ptr, headOffset)); - } + } } } diff --git a/test/examples/cases/add-moritz.solc b/test/examples/cases/add-moritz.solc index d3654ec8e..9cb51e8ee 100644 --- a/test/examples/cases/add-moritz.solc +++ b/test/examples/cases/add-moritz.solc @@ -6,65 +6,65 @@ function add(x : word, y : word) { return res; } -class self:Typedef(underlyingType) { - function rep(x:self) -> underlyingType; - function abs(x:underlyingType) -> self; +trait Typedef { + function rep(x:self) returns (underlyingType); + function abs(x:underlyingType) returns (self); } -forall a.class a : Add { - function add(x:a, y:a) -> a; +trait Add { + function add(x:a, y:a) returns (a); } -data B = F | T; +enum B { F, T } -instance B : Typedef(word) { - function rep(x : B) -> word { - match x { - | B.F => return 0; - | B.T => return 1; - } +impl Typedef { + function rep(x : B) returns (word) { + match (x ) { + case B.F { return 0; + } case B.T { return 1; + } } } - function abs(x : word) -> B { - match x { - | 0 => return B.F; - | 1 => return B.T; - } + function abs(x : word) returns (B) { + match (x ) { + case 0 { return B.F; + } case 1 { return B.T; + } } } } -instance B : Add { - function add(x : B, y : B) -> B { - match x { - | B.F => - match y { - | B.F => return B.F; - | B.T => return B.T; - } +impl Add { + function add(x : B, y : B) returns (B) { + match (x ) { + case B.F { + match (y ) { + case B.F { return B.F; + } case B.T { return B.T; + } } - | B.T => - match y { - | B.F => return B.T; - | B.T => return B.F; - } - } + } case B.T { + match (y ) { + case B.F { return B.T; + } case B.T { return B.F; + } } + } } } } -function fun(a : (B, B), b : (B, B)) -> (B, B) { // -> c - match a, b { - | (a1, a2), (b1, b2) => return (Add.add(a1, b1), fun(a2, b2)); - } +function fun(a : (B, B), b : (B, B)) returns ((B, B)) { // -> c + match (a, b ) { + case ((a1, a2), (b1, b2) ) { return (Add.add(a1, b1), fun(a2, b2)); + } } } contract Compose { - public function main() -> word { + function main() public returns (word) { let res = fun ((B.T, B.T, B.F), (B.F, B.F, B.T)); - match res { - | (r1, r2, r3) => return Typedef.rep(r1); - } + match (res ) { + case (r1, r2, r3) { return Typedef.rep(r1); + } } } } diff --git a/test/examples/cases/another-subst.solc b/test/examples/cases/another-subst.solc index 37840ccd0..0b3a105a9 100644 --- a/test/examples/cases/another-subst.solc +++ b/test/examples/cases/another-subst.solc @@ -1,9 +1,9 @@ -forall a . class a: Foo {function foo(x:a) -> (); } +trait Foo {function foo(x:a) returns (()); } -forall a b . a : Foo, b : Foo => instance (a,b) : Foo { - function foo( p : (a,b) ) -> () { - match p { - | (pa, pb) => Foo.foo(pa); Foo.foo(pb); - } +impl Foo<(a, b)> where a: Foo, b: Foo { + function foo( p : (a, b) ) returns (()) { + match (p ) { + case (pa, pb) { Foo.foo(pa); Foo.foo(pb); + } } } } diff --git a/test/examples/cases/app.solc b/test/examples/cases/app.solc index 60f4b573d..7ae33e9c8 100644 --- a/test/examples/cases/app.solc +++ b/test/examples/cases/app.solc @@ -1,21 +1,21 @@ -forall a b c . c : invokable(a, b) => function app (f : c, x : a) -> b { +function app (f : c, x : a) returns (b) where c: invokable { return invokable.invoke(f, x); } -data t_id = t_id; +enum t_id { t_id } -instance t_id : invokable(word, word) { - function invoke(self : t_id, x : word) -> word { +impl invokable { + function invoke(self : t_id, x : word) returns (word) { return x; } } -function foo() -> word { +function foo() returns (word) { return app(t_id, 0); } contract C { - public function main () -> word { + function main () public returns (word) { return foo(); } } diff --git a/test/examples/cases/array-elem-no-storagecopy.solc b/test/examples/cases/array-elem-no-storagecopy.solc index 260b3d401..e88ffbe28 100644 --- a/test/examples/cases/array-elem-no-storagecopy.solc +++ b/test/examples/cases/array-elem-no-storagecopy.solc @@ -2,18 +2,18 @@ // storage array: `CanStore` for `storage(array(t))` -- which every field access // goes through -- requires `t:StorageCopy`. Rejecting this at compile time is // what keeps `a = b` from silently shallow-copying a type it cannot copy. -import std.{*}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +import {*} from std; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; -data Odd = Odd(word); +enum Odd { Odd(word) } contract NoCopy { reserved : word; - xs : array(Odd); + xs : Odd[]; - function main() -> uint256 { + function main() returns (uint256) { return Array.length(xs); } } diff --git a/test/examples/cases/array-push-no-canstore.solc b/test/examples/cases/array-push-no-canstore.solc index 2d89a295d..14d89f8b6 100644 --- a/test/examples/cases/array-push-no-canstore.solc +++ b/test/examples/cases/array-push-no-canstore.solc @@ -2,18 +2,18 @@ // something `storage(t)` can store. A type with no `CanStore` instance is // rejected -- this is the constraint `storage(t):CanStore(v)` on ArrayPush, // distinct from the `t:StorageCopy` one that whole-array assignment needs. -import std.{*}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +import {*} from std; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; -data Odd = Odd(word); +enum Odd { Odd(word) } contract PushNoStore { reserved : word; - function main() -> uint256 { - let arr : storage(array(Odd)) = storage(0x100); + function main() returns (uint256) { + let arr : Odd[] storage = storage(0x100); ArrayPush.push(arr, Odd(1)); return uint256(0); } diff --git a/test/examples/cases/array.solc b/test/examples/cases/array.solc index b587bb4b2..3cabe71ae 100644 --- a/test/examples/cases/array.solc +++ b/test/examples/cases/array.solc @@ -1,14 +1,14 @@ -pragma no-coverage-condition TAdd; +pragma solcore noCoverageCondition TAdd; -data Zero; -data Succ(a); +enum Zero {} +enum Succ {} -forall self res . class self:TAdd(res) {} -forall a . instance (Zero, a):TAdd(a) {} -forall a b c . (b, a):TAdd(c) => instance (Succ(b), a):TAdd(Succ(c)) {} +trait TAdd {} +impl TAdd<(Zero, a), a> {} +impl TAdd<(Succ, a), Succ> where (b, a): TAdd {} -forall lhs rhs . class lhs:Eq(rhs) {} -forall a . instance a:Eq(a) {} +trait Eq {} +impl Eq {} // this should work but doesnt: forall sizel sizer elem sizeout . (sizel, sizer):TAdd(sizeout) // TODO: this panics during specialization @@ -17,27 +17,27 @@ forall sizel sizer elem sizeout pairSizelSizer . pairSizelSizer:Eq((sizel, sizer return memory(0) : memory(array(sizeout, elem)); // :D } */ -data Itself(a) = ItselfRuntimeTag; +enum Itself { ItselfRuntimeTag } -data array(size, elem) = array; -data memory(a) = memory(word); +enum array { array } +enum memory { memory(word) } -forall self indexType elementType . class self:IndexAccessible (indexType, elementType){ - function set(self:self, ix:indexType, val:elementType) -> (); - function at(self:self, ix:indexType) -> elementType; +trait IndexAccessible { + function set(self:self, ix:indexType, val:elementType) returns (()); + function at(self:self, ix:indexType) returns (elementType); } -forall self . class self:ToWord{ - function toWord(self:Itself(self)) -> word; +trait ToWord { + function toWord(self:Itself) returns (word); } -instance Zero : ToWord { - function toWord(zero : Itself(Zero)) -> word { return 0; } +impl ToWord { + function toWord(zero : Itself) returns (word) { return 0; } } -forall prev . prev:ToWord => instance Succ(prev) : ToWord { - function toWord(self: Itself(Succ(prev))) -> word { - let returnVal : word = ToWord.toWord(Itself.ItselfRuntimeTag:Itself(prev)); +impl ToWord> where prev: ToWord { + function toWord(self: Itself>) returns (word) { + let returnVal : word = ToWord.toWord(Itself.ItselfRuntimeTag as Itself); assembly { returnVal := add(1, returnVal) } @@ -45,25 +45,25 @@ forall prev . prev:ToWord => instance Succ(prev) : ToWord { } } -forall self . class self:MemoryType { - function load(ptr:word) -> self; - function store(ptr:word, value:self) -> (); +trait MemoryType { + function load(ptr:word) returns (self); + function store(ptr:word, value:self) returns (()); } -instance word:MemoryType { - function load(ptr:word) -> word { +impl MemoryType { + function load(ptr:word) returns (word) { let val : word; assembly { val := mload(ptr) } return val; } - function store(ptr:word, value:word) -> () { + function store(ptr:word, value:word) returns (()) { assembly { mstore(ptr, value) } } } -forall size elem . size : ToWord, elem:MemoryType => instance memory(array(size, elem)) : IndexAccessible(word, elem) { - function at(self : memory(array(size,elem)), index : word) -> elem { - let sizeValue = ToWord.toWord(Itself.ItselfRuntimeTag:Itself(size)); +impl IndexAccessible where size: ToWord, elem: MemoryType { + function at(self : elem[size] memory, index : word) returns (elem) { + let sizeValue = ToWord.toWord(Itself.ItselfRuntimeTag as Itself); assembly { if iszero(lt(index, sizeValue)) { @@ -71,18 +71,18 @@ forall size elem . size : ToWord, elem:MemoryType => instance memory(array(size, } } - match self { - | memory(offset) => + match (self ) { + case memory(offset) { let x = offset; // can't use this inside the assembly block :-( assembly { index := add(x, mul(32, index)) } return MemoryType.load(index); - } + } } } - function set(self : memory(array(size,elem)), index : word, val : elem) -> () { - let sizeValue = ToWord.toWord(Itself.ItselfRuntimeTag:Itself(size)); + function set(self : elem[size] memory, index : word, val : elem) returns (()) { + let sizeValue = ToWord.toWord(Itself.ItselfRuntimeTag as Itself); assembly { if iszero(lt(index, sizeValue)) { @@ -90,14 +90,14 @@ forall size elem . size : ToWord, elem:MemoryType => instance memory(array(size, } } - match self { - | memory(offset) => + match (self ) { + case memory(offset) { let x = offset; // can't use this inside the assembly block :-( assembly { index := add(x, mul(32, index)) } MemoryType.store(index, val); - } + } } } } @@ -105,8 +105,8 @@ forall size elem . size : ToWord, elem:MemoryType => instance memory(array(size, contract Array { - public function main() -> word { - let arr : memory(array(Succ(Succ(Succ(Succ(Zero)))), word)) = memory(42); // = (1,2,3,4,5,6,7,8,9,10); + function main() public returns (word) { + let arr : word[Succ>>>] memory = memory(42); // = (1,2,3,4,5,6,7,8,9,10); IndexAccessible.set(arr, 3, 33); return IndexAccessible.at(arr, 3); diff --git a/test/examples/cases/asm-assign-no-return.solc b/test/examples/cases/asm-assign-no-return.solc index 2037d58a9..6ac3d2b7e 100644 --- a/test/examples/cases/asm-assign-no-return.solc +++ b/test/examples/cases/asm-assign-no-return.solc @@ -1,6 +1,6 @@ // mstore does not return a value, so it cannot be assigned. contract Test { - public function main() { + function main() public { let x : word; assembly { x := mstore(1, 1) diff --git a/test/examples/cases/asm-assign-non-word.solc b/test/examples/cases/asm-assign-non-word.solc index be96a1bb4..e725165c1 100644 --- a/test/examples/cases/asm-assign-non-word.solc +++ b/test/examples/cases/asm-assign-non-word.solc @@ -3,9 +3,9 @@ // is a tagged inl/inr pair) would corrupt that layout, so the type checker // must reject this program. contract AsmBool { - public function main() -> word { + function main() public returns (word) { let b : bool = false; assembly { b := add(1, 1) } - if b { return 1; } else { return 0; } + if (b ) { return 1; } else { return 0; } } } diff --git a/test/examples/cases/asm-let-bool-lit.solc b/test/examples/cases/asm-let-bool-lit.solc index 4af0a7171..8cb30c46f 100644 --- a/test/examples/cases/asm-let-bool-lit.solc +++ b/test/examples/cases/asm-let-bool-lit.solc @@ -2,7 +2,7 @@ // `true` in an assembly block must type-check as `word`. Before the fix // `tcYLit YulTrue/YulFalse` called `notImplemented`, crashing the compiler. contract Test { - public function main() -> word { + function main() public returns (word) { let r : word = 0; assembly { let x := true diff --git a/test/examples/cases/asm-let-no-return.solc b/test/examples/cases/asm-let-no-return.solc index 9a7b997f1..61b625cc1 100644 --- a/test/examples/cases/asm-let-no-return.solc +++ b/test/examples/cases/asm-let-no-return.solc @@ -1,6 +1,6 @@ // mstore does not return a value, so it cannot initialize a `let`. contract Test { - public function main() { + function main() public { assembly { let x := mstore(1, 1) } diff --git a/test/examples/cases/asm-let-uninit.solc b/test/examples/cases/asm-let-uninit.solc index 0229db02e..8f35b9acd 100644 --- a/test/examples/cases/asm-let-uninit.solc +++ b/test/examples/cases/asm-let-uninit.solc @@ -3,7 +3,7 @@ // Before the fix `tcYulStmt` dropped `YLet ns Nothing`, so `x` never entered // the env and the read `r := x` failed to resolve. contract Test { - public function main() -> word { + function main() public returns (word) { let r : word = 0; assembly { let x diff --git a/test/examples/cases/asm-match-tuple-read.solc b/test/examples/cases/asm-match-tuple-read.solc index 359a02495..46f6f2fff 100644 --- a/test/examples/cases/asm-match-tuple-read.solc +++ b/test/examples/cases/asm-match-tuple-read.solc @@ -1,10 +1,10 @@ contract C { - function main() -> word { + function main() returns (word) { let res : word; - let foo : (word,word) = (1, 42); - match foo { - | (v0, v1) => assembly { res := v1 } - } + let foo : (word, word) = (1, 42); + match (foo ) { + case (v0, v1) { assembly { res := v1 } + } } return res; } } diff --git a/test/examples/cases/asm-match-tuple-write-read.solc b/test/examples/cases/asm-match-tuple-write-read.solc index 1816e0fb3..948c0a3a5 100644 --- a/test/examples/cases/asm-match-tuple-write-read.solc +++ b/test/examples/cases/asm-match-tuple-write-read.solc @@ -3,16 +3,16 @@ // Runtime correctness of the write->read depends on ecSubst being updated after // the assembly block (EmitHull.hs: emitStmt MastAsm, modify ecSubst). contract C { - function main() -> word { + function main() returns (word) { let res : word; - let foo : (word,word) = (0, 0); - match foo { - | (v0, v1) => { + let foo : (word, word) = (0, 0); + match (foo ) { + case (v0, v1) { { assembly { v1 := 42 } let x : word = v1; assembly { res := x } } - } + } } return res; } } diff --git a/test/examples/cases/assembly.solc b/test/examples/cases/assembly.solc index 5850f0ca9..8ee8f87ed 100644 --- a/test/examples/cases/assembly.solc +++ b/test/examples/cases/assembly.solc @@ -1,14 +1,14 @@ -forall a . class a : Mem { - function size(x : a) -> word; +trait Mem { + function size(x : a) returns (word); } -instance word : Mem { - function size(x : word) -> word { +impl Mem { + function size(x : word) returns (word) { return 32; } } -function foo () -> () { +function foo () returns (()) { let ptr : word; let arg : word = 0; let size = Mem.size(arg); diff --git a/test/examples/cases/bal.solc b/test/examples/cases/bal.solc index c2f51f0ce..a343a32b5 100644 --- a/test/examples/cases/bal.solc +++ b/test/examples/cases/bal.solc @@ -1,12 +1,12 @@ -data Proxy (a) = Proxy ; -data dict(member, index) = dict(word, Proxy(member), Proxy(index)) ; -data address = address(word) ; -data storage(a) = storage(word) ; +enum Proxy { Proxy } +enum dict { dict(word, Proxy, Proxy) } +enum address { address(word) } +enum storage { storage(word) } -data IndexAP (m, idx, member) = IndexAP(m, idx, Proxy(member)) ; +enum IndexAP { IndexAP(m, idx, Proxy) } -function wal(ref: storage(dict(address, word)) , src : address, amt: word) -> () { - let ip = IndexAP(ref, src, Proxy : Proxy(word)); +function wal(ref: dict storage , src : address, amt: word) returns (()) { + let ip = IndexAP(ref, src, Proxy as Proxy); Assign.assign(LVA.acc(ip), amt); } @@ -38,23 +38,20 @@ instance IndexAP(storage(dict(index,member)), index, member):LVA(storage(member) b5 +-> e4 should really be b5 ~ e4 */ -forall self memberRefType. -class self:LVA(memberRefType) { - function acc(x:self) -> memberRefType; +trait LVA { + function acc(x:self) returns (memberRefType); } -forall index member. - instance IndexAP(storage(dict(index,member)), index, member):LVA(storage(member)) { - function acc(x:IndexAP(storage(dict(index,member)), index, member)) -> storage(member) { +impl LVA storage, index, member>, member storage> { + function acc(x:IndexAP storage, index, member>) returns (member storage) { return storage(30); } } -forall lhs rhs. -class lhs:Assign(rhs) { - function assign(l:lhs, r:rhs) -> (); +trait Assign { + function assign(l:lhs, r:rhs) returns (()); } -forall a . instance storage(a):Assign(a) { - function assign(l:storage(a), y:a) -> () {} +impl Assign { + function assign(l:a storage, y:a) returns (()) {} } diff --git a/test/examples/cases/bar.solc b/test/examples/cases/bar.solc index 24e215e1c..7ff9a4051 100644 --- a/test/examples/cases/bar.solc +++ b/test/examples/cases/bar.solc @@ -1,20 +1,18 @@ -pragma no-coverage-condition Bar; +pragma solcore noCoverageCondition Bar; -data Wrap(a) = Wrap(a); +enum Wrap { Wrap(a) } -forall self rep . class self : Foo(rep) {} +trait Foo {} -forall self rep . class self : Bar(rep) {} +trait Bar {} -forall a b . a : Foo(b) => instance Wrap(a) : Bar(b) {} +impl Bar, b> where a: Foo {} -forall a rep . Wrap(a) : Bar(rep) => -function need_bar(x : Wrap(a)) -> () { - return (); +function need_bar(x : Wrap) returns (()) where Wrap: Bar { + return; } -forall a . a : Foo(word) => -function use_bar(x : Wrap(a)) -> () { +function use_bar(x : Wrap) returns (()) where a: Foo { need_bar(x); - return (); + return; } diff --git a/test/examples/cases/bitwise.solc b/test/examples/cases/bitwise.solc index d71caeec4..782b6b6ee 100644 --- a/test/examples/cases/bitwise.solc +++ b/test/examples/cases/bitwise.solc @@ -1,18 +1,18 @@ -import std.{*}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +import {*} from std; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; // Exercises the `^` / `&` / `|` operators, the `^=` / `&=` / `|=` // compound assignments, and the bxorWord / bandWord / borWord constant // folding (mirrors gtWord). -function fxor(x: word, y: word) -> word { +function fxor(x: word, y: word) returns (word) { let acc : word = x ^ y; acc ^= x; // acc = (x ^ y) ^ x == y return acc ^ 0; // identity: a ^ 0 == a } -function fbitwise(x: word, y: word) -> word { +function fbitwise(x: word, y: word) returns (word) { let acc : word = x & y; acc |= x; // acc = (x & y) | x == x acc &= y; // acc = x & y @@ -21,5 +21,5 @@ function fbitwise(x: word, y: word) -> word { contract Bitwise { // fxor(5, 3) == 3, fbitwise(6, 3) == 2, 3 ^ 2 == 1 — folded at compile time. - public function main() -> word { return fxor(5, 3) ^ fbitwise(6, 3); } + function main() public returns (word) { return fxor(5, 3) ^ fbitwise(6, 3); } } diff --git a/test/examples/cases/bool-elim.solc b/test/examples/cases/bool-elim.solc index c12366896..3f7037127 100644 --- a/test/examples/cases/bool-elim.solc +++ b/test/examples/cases/bool-elim.solc @@ -1,14 +1,14 @@ -data Bool = False | True; +enum Bool { False, True } - function second(x : Bool, y : word) -> word { - match x, y { - | Bool.True, z => return z; - | Bool.False, z => return z; - } + function second(x : Bool, y : word) returns (word) { + match (x, y ) { + case (Bool.True, z ) { return z; + } case (Bool.False, z ) { return z; + } } } contract Second { - public function main() -> word { - second(Bool.True, 42) + function main() public returns (word) { + return second(Bool.True, 42); } } diff --git a/test/examples/cases/bound-merge-case.solc b/test/examples/cases/bound-merge-case.solc index 661e8588b..9b491034f 100644 --- a/test/examples/cases/bound-merge-case.solc +++ b/test/examples/cases/bound-merge-case.solc @@ -2,4 +2,4 @@ //pragma no-bounded-variable-condition TestClassB1; // === Test Classes === -forall a . class a:TestClassP1 {} +trait TestClassP1 {} diff --git a/test/examples/cases/bound-minimal.solc b/test/examples/cases/bound-minimal.solc index 748b5c806..9f201dee4 100644 --- a/test/examples/cases/bound-minimal.solc +++ b/test/examples/cases/bound-minimal.solc @@ -2,11 +2,11 @@ // This SHOULD FAIL - variable 'bad' in context but not in instance head -forall a . class a:TestBound {} -forall a b . class a:TestHelper(b) {} +trait TestBound {} +trait TestHelper {} -data TestType(x) = TestType; +enum TestType { TestType } // Variable 'bad' appears in context but not in instance head // Should fail bound variable check -forall x . bad:TestHelper(x) => instance TestType(x):TestBound {} +impl TestBound> where bad: TestHelper {} diff --git a/test/examples/cases/bound-only-test.solc b/test/examples/cases/bound-only-test.solc index 96695759a..94cd15056 100644 --- a/test/examples/cases/bound-only-test.solc +++ b/test/examples/cases/bound-only-test.solc @@ -1,10 +1,10 @@ // Test only bound variable check, disable Patterson -forall a . class a:TestBound {} -forall a b . class a:TestHelper(b) {} +trait TestBound {} +trait TestHelper {} -data TestType(x) = TestType; +enum TestType { TestType } // Variable 'bad' appears in context but not in instance head // Should fail bound variable check -forall x . bad:TestHelper(x) => instance TestType(x):TestBound {} +impl TestBound> where bad: TestHelper {} diff --git a/test/examples/cases/bound-with-pragma.solc b/test/examples/cases/bound-with-pragma.solc index 3f6687864..943b74570 100644 --- a/test/examples/cases/bound-with-pragma.solc +++ b/test/examples/cases/bound-with-pragma.solc @@ -1,14 +1,14 @@ // Same test but with pragma to disable bound variable check // This SHOULD PASS -pragma no-bounded-variable-condition TestBound; -pragma no-patterson-condition TestBound; // Also disable Patterson to avoid that error +pragma solcore noBoundVariableCondition TestBound; +pragma solcore noPattersonCondition TestBound; // Also disable Patterson to avoid that error -forall a . class a:TestBound {} -forall a b . class a:TestHelper(b) {} +trait TestBound {} +trait TestHelper {} -data TestType(x) = TestType; +enum TestType { TestType } // Variable 'bad' appears in context but not in instance head // But pragma disables the check, so should pass -forall x bad . bad:TestHelper(x) => instance TestType(x):TestBound {} +impl TestBound> where bad: TestHelper {} diff --git a/test/examples/cases/bug-import-default-inst-shadow.solc b/test/examples/cases/bug-import-default-inst-shadow.solc index 8425be2d9..d7ef8f42a 100644 --- a/test/examples/cases/bug-import-default-inst-shadow.solc +++ b/test/examples/cases/bug-import-default-inst-shadow.solc @@ -1,8 +1,8 @@ -pragma no-patterson-condition ABIAttribs, ABIEncode; -pragma no-bounded-variable-condition ABIAttribs, ABIEncode; +pragma solcore noPattersonCondition ABIAttribs, ABIEncode; +pragma solcore noBoundVariableCondition ABIAttribs, ABIEncode; -import std.{*}; -import std.Generic.{*}; +import {*} from std; +import {*} from std.Generic; // Minimal reproducer for the "imported-default-instance-stub mis-tagged" bug. // @@ -24,9 +24,8 @@ import std.Generic.{*}; // 4. tcTopDeclWithVisibility calls tcTopDecl' on the stub (funs = []). // 5. tcInstance' -> checkCompleteInstDef -> "Incomplete definition for ABIEncode". -forall a rep . a:Generic(rep), rep:ABIAttribs, rep:ABIEncode => -default instance a : ABIEncode { - function encodeInto(x : a, basePtr : word, offset : word, tail : word) -> word { +default impl ABIEncode where a: Generic, rep: ABIAttribs, rep: ABIEncode { + function encodeInto(x : a, basePtr : word, offset : word, tail : word) returns (word) { return ABIEncode.encodeInto(Generic.from(x), basePtr, offset, tail); } } diff --git a/test/examples/cases/bug-rep-name-capture.solc b/test/examples/cases/bug-rep-name-capture.solc index 953fdb337..bd70ff9fe 100644 --- a/test/examples/cases/bug-rep-name-capture.solc +++ b/test/examples/cases/bug-rep-name-capture.solc @@ -7,16 +7,16 @@ // Expected: compiles successfully; `Typedef.rep` resolves to the class method. // Actual (before fix): PANIC: no resolution found for invokable.invoke -import std.{*}; -import std.dispatch.{*}; -pragma no-patterson-condition; -pragma no-coverage-condition; -pragma no-bounded-variable-condition; +import {*} from std; +import {*} from std.dispatch; +pragma solcore noPattersonCondition; +pragma solcore noCoverageCondition; +pragma solcore noBoundVariableCondition; contract Bug { constructor() {} - function f(a : uint256) -> uint256 { + function f(a : uint256) returns (uint256) { let rep : uint256 = a; let w : word = Typedef.rep(a); return Typedef.abs(w); diff --git a/test/examples/cases/bug-spec-generic-let.solc b/test/examples/cases/bug-spec-generic-let.solc index 9288943aa..d6e127e92 100644 --- a/test/examples/cases/bug-spec-generic-let.solc +++ b/test/examples/cases/bug-spec-generic-let.solc @@ -13,37 +13,37 @@ // Expected: compiles successfully. // Actual (before fix): PANIC: Type mismatch expected uint256 actual (uint256,uint256) -import std.{*}; -import std.dispatch.{*}; -import std.Generic.{*}; -pragma no-patterson-condition; -pragma no-coverage-condition; -pragma no-bounded-variable-condition; +import {*} from std; +import {*} from std.dispatch; +import {*} from std.Generic; +pragma solcore noPattersonCondition; +pragma solcore noCoverageCondition; +pragma solcore noBoundVariableCondition; -data Pair = MkPair(uint256, uint256); +enum Pair { MkPair(uint256, uint256) } -instance Pair : Generic((uint256, uint256)) { - function from(x : Pair) -> (uint256, uint256) { - match x { | Pair.MkPair(a, b) => return (a, b); } +impl Generic { + function from(x : Pair) returns ((uint256, uint256)) { + match (x ) { case Pair.MkPair(a, b) { return (a, b); } } } - function to(x : (uint256, uint256)) -> Pair { - match x { | (a, b) => return Pair.MkPair(a, b); } + function to(x : (uint256, uint256)) returns (Pair) { + match (x ) { case (a, b) { return Pair.MkPair(a, b); } } } } contract BugSpecGenericLet { constructor() {} - function roundtrip(a : uint256, b : uint256) -> uint256 { + function roundtrip(a : uint256, b : uint256) returns (uint256) { let p : Pair = Pair.MkPair(a, b); - let encoded : memory(bytes) = abi_encode(p); - let decoded : (uint256, uint256) = abi_decode(encoded, @(uint256, uint256), @MemoryWordReader); - match decoded { - | (x, y) => - match and(Eq.eq(x, a), Eq.eq(y, b)) { - | true => return uint256(1); - | false => return uint256(0); - } - } + let encoded : bytes memory = abi_encode(p); + let decoded : (uint256, uint256) = abi_decode(encoded, Proxy as Proxy<(uint256, uint256)>, Proxy as Proxy); + match (decoded ) { + case (x, y) { + match (and(Eq.eq(x, a), Eq.eq(y, b)) ) { + case true { return uint256(1); + } case false { return uint256(0); + } } + } } } } diff --git a/test/examples/cases/catch-all.solc b/test/examples/cases/catch-all.solc index a3fd9f8b1..f4d3ca052 100644 --- a/test/examples/cases/catch-all.solc +++ b/test/examples/cases/catch-all.solc @@ -1,14 +1,14 @@ -data Bool = False | True; +enum Bool { False, True } contract CatchAll { - public function catchAll(x : Bool, y : Bool) -> Bool{ - match x, y { - | Bool.True, Bool.True => return Bool.True; - | z, w => return z; - } + function catchAll(x : Bool, y : Bool) public returns (Bool){ + match (x, y ) { + case (Bool.True, Bool.True ) { return Bool.True; + } case (z, w ) { return z; + } } } - public function main() -> Bool { - catchAll(Bool.True, Bool.False) + function main() public returns (Bool) { + return catchAll(Bool.True, Bool.False); } } diff --git a/test/examples/cases/catenable-err.solc b/test/examples/cases/catenable-err.solc index 5bbf71e2a..9b05208c4 100644 --- a/test/examples/cases/catenable-err.solc +++ b/test/examples/cases/catenable-err.solc @@ -1,3 +1,3 @@ -forall t.class t:Catenable { - function cat(x:t) -> memory(bytes) +trait Catenable { + function cat(x: t) returns (bytes memory); } diff --git a/test/examples/cases/class-context.solc b/test/examples/cases/class-context.solc index 8a2477c73..20768965a 100644 --- a/test/examples/cases/class-context.solc +++ b/test/examples/cases/class-context.solc @@ -1,4 +1,3 @@ -forall self fieldType offsetType -. class self:CStructField(fieldType, offsetType) { - function offsetSize(s: self) -> word; +trait CStructField { + function offsetSize(s: self) returns (word); } diff --git a/test/examples/cases/class-return-type-miss.solc b/test/examples/cases/class-return-type-miss.solc index 01856331b..e52a1d4d0 100644 --- a/test/examples/cases/class-return-type-miss.solc +++ b/test/examples/cases/class-return-type-miss.solc @@ -1,9 +1,9 @@ -data bytes32 = bytes32(word); +enum bytes32 { bytes32(word) } -forall t . class t:Memory { +trait Memory { function encodeInto(v: t, target: word); } -instance bytes32:Memory { +impl Memory { function encodeInto(v: bytes32, target: word) {} } diff --git a/test/examples/cases/class-type-name-collision.solc b/test/examples/cases/class-type-name-collision.solc index ee30b07bd..3d6ffa074 100644 --- a/test/examples/cases/class-type-name-collision.solc +++ b/test/examples/cases/class-type-name-collision.solc @@ -1,6 +1,5 @@ -data Foo = MkFoo; +enum Foo { MkFoo } -forall a. -class a:Foo { - function foo(x:a) -> word; +trait Foo { + function foo(x:a) returns (word); } diff --git a/test/examples/cases/closure-capture-only.solc b/test/examples/cases/closure-capture-only.solc index 96228cd16..199845cb4 100644 --- a/test/examples/cases/closure-capture-only.solc +++ b/test/examples/cases/closure-capture-only.solc @@ -1,7 +1,7 @@ -function testApplied(x: word) -> word { +function testApplied(x: word) returns (word) { return x; } -function main() -> word { +function main() returns (word) { return testApplied(1); } diff --git a/test/examples/cases/closure-free-bound-test.solc b/test/examples/cases/closure-free-bound-test.solc index 6ffc20ca4..a5ad32eac 100644 --- a/test/examples/cases/closure-free-bound-test.solc +++ b/test/examples/cases/closure-free-bound-test.solc @@ -1,4 +1,4 @@ -function foo (b : bool) -> () { +function foo (b : bool) returns (()) { let y:word; let f = lam(x : word) { if (b) { let z : word = 7; y = z; } else {x = 1;} diff --git a/test/examples/cases/closure-free-var-local.solc b/test/examples/cases/closure-free-var-local.solc index a396740cc..a38b45d51 100644 --- a/test/examples/cases/closure-free-var-local.solc +++ b/test/examples/cases/closure-free-var-local.solc @@ -1,5 +1,5 @@ -function test() -> word { - let f = lam (x: word) -> word { +function test() returns (word) { + let f = lam (x: word) returns (word) { let y : word = 42; return y; }; @@ -7,7 +7,7 @@ function test() -> word { } contract C { - public function main() -> word { + function main() public returns (word) { return test(); } } diff --git a/test/examples/cases/closure-free-var-std.solc b/test/examples/cases/closure-free-var-std.solc index 8ce806b83..52f65eddd 100644 --- a/test/examples/cases/closure-free-var-std.solc +++ b/test/examples/cases/closure-free-var-std.solc @@ -1,14 +1,14 @@ -import std.{*}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +import {*} from std; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; contract Bug { - public function main() -> word { + function main() public returns (word) { return makeClosure(42); } - public function makeClosure(e : word) -> word { + function makeClosure(e : word) public returns (word) { let f = lam (x : word) { return e + x; // Uses Add.add typeclass method }; diff --git a/test/examples/cases/closure-free-var.solc b/test/examples/cases/closure-free-var.solc index dd29195b5..f6d9edf1a 100644 --- a/test/examples/cases/closure-free-var.solc +++ b/test/examples/cases/closure-free-var.solc @@ -1,4 +1,4 @@ -function addW (l: word, r: word) -> word { +function addW (l: word, r: word) returns (word) { let rw : word; assembly { rw := add(l,r) @@ -6,20 +6,20 @@ function addW (l: word, r: word) -> word { return rw; } -forall t . class t:Add { - function add(l: t, r: t) -> t; +trait Add { + function add(l: t, r: t) returns (t); } -instance word:Add { - function add(l: word, r: word) -> word { return addW(l,r); } +impl Add { + function add(l: word, r: word) returns (word) { return addW(l,r); } } contract Bug { - public function main() -> word { + function main() public returns (word) { return makeClosure(42); } - public function makeClosure(e : word) -> word { + function makeClosure(e : word) public returns (word) { let f = lam (x : word) { return Add.add(x,e); // this crashes // return addW(e,x); // this works diff --git a/test/examples/cases/closure.solc b/test/examples/cases/closure.solc index 497d5acc3..1811d9330 100644 --- a/test/examples/cases/closure.solc +++ b/test/examples/cases/closure.solc @@ -1,4 +1,4 @@ - function foo (z : word, k : (), a : word) -> word { + function foo (z : word, k : (), a : word) returns (word) { let f = lam (x : word, y : word) { k; return primAddWord(a,primAddWord(y,z)); diff --git a/test/examples/cases/comparisons.solc b/test/examples/cases/comparisons.solc index 98608204b..e4ca57d56 100644 --- a/test/examples/cases/comparisons.solc +++ b/test/examples/cases/comparisons.solc @@ -1,8 +1,8 @@ -import std.{*}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; -function f(x: word, y:word) -> bool { +import {*} from std; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; +function f(x: word, y:word) returns (bool) { return (!((x == y) && (x != y) && (x >= y) @@ -13,5 +13,5 @@ function f(x: word, y:word) -> bool { } contract Comparisons { - public function main() -> bool { return f(0,1); } + function main() public returns (bool) { return f(0,1); } } diff --git a/test/examples/cases/complexproxy.solc b/test/examples/cases/complexproxy.solc index 54ca326ea..821b3d034 100644 --- a/test/examples/cases/complexproxy.solc +++ b/test/examples/cases/complexproxy.solc @@ -1,35 +1,34 @@ -data Proxy(a) = Proxy; +enum Proxy { Proxy } function add(x:word, y: word) {return x;} -class self:BaseMemoryType { - function memorySize(x:Proxy(self)) -> word; +trait BaseMemoryType { + function memorySize(x:Proxy) returns (word); } -instance word:BaseMemoryType { - function memorySize(x:Proxy(word)) -> word { +impl BaseMemoryType { + function memorySize(x:Proxy) returns (word) { return 32; } } -forall a b . a:BaseMemoryType, b:BaseMemoryType => - instance (a,b):BaseMemoryType { +impl BaseMemoryType<(a, b)> where a: BaseMemoryType, b: BaseMemoryType { - function memorySize(x) -> word { // not correct semantically, just for debugging - return add(BaseMemoryType.memorySize(Proxy:Proxy(a)), + function memorySize(x) returns (word) { // not correct semantically, just for debugging + return add(BaseMemoryType.memorySize(Proxy as Proxy), // BaseMemoryType.memorySize(Proxy:Proxy(b)) - morefun(Proxy:Proxy(b)) + morefun(Proxy as Proxy) ); } } // this should trigger a type error. -forall t. function morefun(p:Proxy(t)) -> word { - return BaseMemoryType.memorySize(Proxy:Proxy(t)); +function morefun(p:Proxy) returns (word) { + return BaseMemoryType.memorySize(Proxy as Proxy); } contract TestMemoryType { - public function main() -> word { - return BaseMemoryType.memorySize(Proxy:Proxy( (word,word) )); + function main() public returns (word) { + return BaseMemoryType.memorySize(Proxy as Proxy<(word, word)>); } } diff --git a/test/examples/cases/compose0.solc b/test/examples/cases/compose0.solc index 10a703854..2b8ab66d0 100644 --- a/test/examples/cases/compose0.solc +++ b/test/examples/cases/compose0.solc @@ -1,4 +1,4 @@ -forall a b c . function compose (f : (b) -> c,g : (a) -> b) -> ((a) -> c) { +function compose (f : function(b) internal returns (c),g : function(a) internal returns (b)) returns (function(a) internal returns (c)) { return lam (x) { return f(g(x)); }; diff --git a/test/examples/cases/compose_desugared.solc b/test/examples/cases/compose_desugared.solc index 303c3b030..9a50f77db 100644 --- a/test/examples/cases/compose_desugared.solc +++ b/test/examples/cases/compose_desugared.solc @@ -1,45 +1,39 @@ -forall a b c d e . d : invokable(b,c) - , e : invokable(a,b) - => function compose(f : d, g : e) -> t_closure1(a,b,c,d,e) { +function compose(f : d, g : e) returns (t_closure1) where d: invokable, e: invokable { return t_closure1(f,g); } -data t_closure1(a,b,c,d,e) = t_closure1(d,e); +enum t_closure1 { t_closure1(d, e) } -forall a b c d e . d : invokable(b,c), e : invokable(a,b) => - function lambda2(c : t_closure1(a,b,c,d,e), x : a) -> c { - match c { - | t_closure1(f, g) => +function lambda2(c : t_closure1, x : a) returns (c) where d: invokable, e: invokable { + match (c ) { + case t_closure1(f, g) { return invokable.invoke(f, invokable.invoke(g,x)); - } + } } } -forall a b c d e . d : invokable(b,c) - , e : invokable(a,b) - => instance t_closure1(a,b,c,d,e) : invokable(a,c) { - function invoke(self : t_closure1(a,b,c,d,e), args : a) -> c { +impl invokable, a, c> where d: invokable, e: invokable { + function invoke(self : t_closure1, args : a) returns (c) { return lambda2(self, args); } } -data t_id3(a) = t_id3 ; +enum t_id3 { t_id3 } -forall a . function id (x : a) -> a { +function id (x : a) returns (a) { return x; } -forall a . instance t_id3(a) : invokable(a,a) { - function invoke(self : t_id3(a), args : a) -> a { - match self { - | t_id3 => return id(args) ; - } +impl invokable, a, a> { + function invoke(self : t_id3, args : a) returns (a) { + match (self ) { + case t_id3 { return id(args) ; + } } } } contract Foo { - public function main() -> word { + function main() public returns (word) { let f = compose(t_id3, t_id3); return invokable.invoke(f, 0); } } - diff --git a/test/examples/cases/const-array.solc b/test/examples/cases/const-array.solc index 17a2fcecf..5ab8953de 100644 --- a/test/examples/cases/const-array.solc +++ b/test/examples/cases/const-array.solc @@ -1,40 +1,40 @@ -data Zero; -data Succ(a); +enum Zero {} +enum Succ {} -forall self res . class self:TAdd(res) {} -forall a . instance (Zero, a):TAdd(a) {} -forall a b c . (b, a):TAdd(c) => instance (Succ(b), a):TAdd(Succ(c)) {} +trait TAdd {} +impl TAdd<(Zero, a), a> {} +impl TAdd<(Succ, a), Succ> where (b, a): TAdd {} -forall lhs rhs . class lhs:Eq(rhs) {} -forall a . instance a:Eq(a) {} +trait Eq {} +impl Eq {} // this should work but doesnt: forall sizel sizer elem sizeout . (sizel, sizer):TAdd(sizeout) -forall sizel sizer elem sizeout pairSizelSizer . pairSizelSizer:Eq((sizel, sizer)), pairSizelSizer:TAdd(sizeout) => function concat(lhs:memory(array(sizel, elem)), rhs:memory(array(sizer, elem))) -> memory(array(sizeout, elem)) { - return memory(0) : memory(array(sizeout, elem)); // :D +function concat(lhs:elem[sizel] memory, rhs:elem[sizer] memory) returns (elem[sizeout] memory) where pairSizelSizer: Eq<(sizel, sizer)>, pairSizelSizer: TAdd { + return memory(0) as elem[sizeout] memory; // :D } -data Itself(a) = ItselfRuntimeTag; +enum Itself { ItselfRuntimeTag } -data array(size, elem) = array; -data memory(a) = memory(word); +enum array { array } +enum memory { memory(word) } -forall self indexType elementType . class self:IndexAccessible (indexType, elementType){ +trait IndexAccessible { function set(self:self, ix:indexType, val:elementType); - function at(self:self, ix:indexType) -> elementType; + function at(self:self, ix:indexType) returns (elementType); } -forall self . class self:ToWord{ - function toWord(self:Itself(self)) -> word; +trait ToWord { + function toWord(self:Itself) returns (word); } -instance Zero : ToWord { +impl ToWord { function toWord(zero) { return 0; } } -forall prev . prev:ToWord => instance Succ(prev) : ToWord { - function toWord(self: Itself(Succ(prev))) { - let returnVal : word = ToWord.toWord(Itself.ItselfRuntimeTag:Itself(prev)); +impl ToWord> where prev: ToWord { + function toWord(self: Itself>) { + let returnVal : word = ToWord.toWord(Itself.ItselfRuntimeTag as Itself); assembly { returnVal := add(1, returnVal) } @@ -42,13 +42,13 @@ forall prev . prev:ToWord => instance Succ(prev) : ToWord { } } -forall self . class self:MemoryType { - function load(ptr:word) -> self; +trait MemoryType { + function load(ptr:word) returns (self); function store(ptr:word, value:self); } -instance word:MemoryType { - function load(ptr:word) -> word { +impl MemoryType { + function load(ptr:word) returns (word) { let val : word; assembly { val := mload(ptr) } return val; @@ -58,9 +58,9 @@ instance word:MemoryType { } } -forall size elem . size : ToWord, elem:MemoryType => instance memory(array(size, elem)) : IndexAccessible(word, elem) { - function at(self, index) -> elem { - let sizeValue = ToWord.toWord(Itself.ItselfRuntimeTag:Itself(size)); +impl IndexAccessible where size: ToWord, elem: MemoryType { + function at(self, index) returns (elem) { + let sizeValue = ToWord.toWord(Itself.ItselfRuntimeTag as Itself); // this should work but doesn't // assembly { // if iszero(lt(index, sizeValue)) { @@ -68,18 +68,18 @@ forall size elem . size : ToWord, elem:MemoryType => instance memory(array(size, // } //} - match self { - | memory(offset) => + match (self ) { + case memory(offset) { let x = offset; // can't use this inside the assembly block :-( assembly { index := add(x, mul(32, index)) } return MemoryType.load(index); - } + } } } function set(self, index, val) { - let sizeValue = ToWord.toWord(Itself.ItselfRuntimeTag:Itself(size)); + let sizeValue = ToWord.toWord(Itself.ItselfRuntimeTag as Itself); //assembly { // if iszero(lt(index, sizeValue)) { @@ -87,14 +87,14 @@ forall size elem . size : ToWord, elem:MemoryType => instance memory(array(size, // } //} - match self { - | memory(offset) => + match (self ) { + case memory(offset) { let x = offset; // can't use this inside the assembly block :-( assembly { index := add(x, mul(32, index)) } MemoryType.store(index, val); - } + } } } } @@ -102,8 +102,8 @@ forall size elem . size : ToWord, elem:MemoryType => instance memory(array(size, contract Array { - public function main() { - let arr : memory(array(Succ(Succ(Succ(Succ(Zero)))), word)) = memory(42); // = (1,2,3,4,5,6,7,8,9,10); + function main() public { + let arr : word[Succ>>>] memory = memory(42); // = (1,2,3,4,5,6,7,8,9,10); IndexAccessible.set(arr, 4, 33); // this (correctly) typechecks but doesn't specialize diff --git a/test/examples/cases/const.solc b/test/examples/cases/const.solc index 0871138b8..e4839e250 100644 --- a/test/examples/cases/const.solc +++ b/test/examples/cases/const.solc @@ -1,9 +1,9 @@ -function constApplied(x : word, y : word) -> word { +function constApplied(x : word, y : word) returns (word) { return y; } contract Foo { - public function main () -> word { + function main () public returns (word) { return constApplied(0,1); } } diff --git a/test/examples/cases/constrained-instance-context.solc b/test/examples/cases/constrained-instance-context.solc index 8a6781cbe..a389c796b 100644 --- a/test/examples/cases/constrained-instance-context.solc +++ b/test/examples/cases/constrained-instance-context.solc @@ -1,25 +1,25 @@ -data memory(t) = memory(word); +enum memory { memory(word) } -forall t . class t:ValueTy { - function rep(x:t) -> word; +trait ValueTy { + function rep(x:t) returns (word); } -forall t . instance memory(t) : ValueTy { - function rep(x: memory(t)) -> word { - match x { - | memory(w) => return w; - } +impl ValueTy { + function rep(x: t memory) returns (word) { + match (x ) { + case memory(w) { return w; + } } } } -forall ref deref . class ref:Ref(deref) { - function store(loc: ref, value: deref) -> (); +trait Ref { + function store(loc: ref, value: deref) returns (()); } -forall t . t : ValueTy => instance memory(t) : Ref(t) { - function store(loc: memory(t), value: t) -> () { +impl Ref where t: ValueTy { + function store(loc: t memory, value: t) returns (()) { // We don't have a `ValueTy` bound on `t` anywhere, so this should raise a type error... let vw = ValueTy.rep(value); } diff --git a/test/examples/cases/constrained-instance.solc b/test/examples/cases/constrained-instance.solc index 9ae0ccb46..b3aa2df0f 100644 --- a/test/examples/cases/constrained-instance.solc +++ b/test/examples/cases/constrained-instance.solc @@ -1,24 +1,24 @@ -data memory(t) = memory(word); +enum memory { memory(word) } -forall t . class t:ValueTy { - function rep(x:t) -> word; +trait ValueTy { + function rep(x:t) returns (word); } -forall t . instance memory(t) : ValueTy { - function rep(x: memory(t)) -> word { - match x { - | memory(w) => return w; - } +impl ValueTy { + function rep(x: t memory) returns (word) { + match (x ) { + case memory(w) { return w; + } } } } -forall ref deref . class ref:Ref(deref) { - function store(loc: ref, value: deref) -> (); +trait Ref { + function store(loc: ref, value: deref) returns (()); } -forall t . t : ValueTy => instance memory(t) : Ref(t) { - function store(loc: memory(t), value: t) -> () { +impl Ref where t: ValueTy { + function store(loc: t memory, value: t) returns (()) { // We don't have a `ValueTy` bound on `t` anywhere, so this should raise a type error... let vw = ValueTy.rep(value); } diff --git a/test/examples/cases/constructor-weak-args.solc b/test/examples/cases/constructor-weak-args.solc index 4f2ee34bd..7ddc57ad5 100644 --- a/test/examples/cases/constructor-weak-args.solc +++ b/test/examples/cases/constructor-weak-args.solc @@ -1,7 +1,7 @@ -forall ref deref . class ref:Loadable (deref) { - function load (r : ref) -> deref; +trait Loadable { + function load (r : ref) returns (deref); } -forall t . t : Loadable(word) => function foo(v : t) -> word { +function foo(v : t) returns (word) where t: Loadable { return Loadable.load(v); } diff --git a/test/examples/cases/copytomem.solc b/test/examples/cases/copytomem.solc index b37fb5b8e..b21508bdc 100644 --- a/test/examples/cases/copytomem.solc +++ b/test/examples/cases/copytomem.solc @@ -1,13 +1,13 @@ -data MemoryWordReader = MemoryWordReader(word); +enum MemoryWordReader { MemoryWordReader(word) } -function copyToMem(reader:MemoryWordReader, dst:word, cnt: word) -> () { - match reader { - | MemoryWordReader(ptr) => assembly { mcopy(dst, ptr, cnt) } - } +function copyToMem(reader:MemoryWordReader, dst:word, cnt: word) returns (()) { + match (reader ) { + case MemoryWordReader(ptr) { assembly { mcopy(dst, ptr, cnt) } + } } } contract Main { - public function main() -> () { + function main() public returns (()) { let r : MemoryWordReader = MemoryWordReader(42); copyToMem(r, 0, 32); } diff --git a/test/examples/cases/cyclical-defs-inferred.solc b/test/examples/cases/cyclical-defs-inferred.solc index 4304a96b5..6a65fa163 100644 --- a/test/examples/cases/cyclical-defs-inferred.solc +++ b/test/examples/cases/cyclical-defs-inferred.solc @@ -1,12 +1,12 @@ -function foo(x : word) -> word { +function foo(x : word) returns (word) { return bar(x); } -function bar(x : word) -> word { +function bar(x : word) returns (word) { return foo(x); } contract C { - public function main() -> word { + function main() public returns (word) { return foo(1); } } diff --git a/test/examples/cases/cyclical-defs.solc b/test/examples/cases/cyclical-defs.solc index 9c31ed615..05854ace3 100644 --- a/test/examples/cases/cyclical-defs.solc +++ b/test/examples/cases/cyclical-defs.solc @@ -1,18 +1,18 @@ -function foo(x : word) -> word { +function foo(x : word) returns (word) { return bar(x); } -function bar(x : word) -> word { +function bar(x : word) returns (word) { return foo(x); } contract C { - public function m(x : word) -> word { + function m(x : word) public returns (word) { return n(x); } - public function n(x : word) -> word { + function n(x : word) public returns (word) { return m(x); } - public function main() -> word { + function main() public returns (word) { return m(1); } } diff --git a/test/examples/cases/default-inst.solc b/test/examples/cases/default-inst.solc index 0cd1b9e6b..b476c1c2c 100644 --- a/test/examples/cases/default-inst.solc +++ b/test/examples/cases/default-inst.solc @@ -1,19 +1,18 @@ -class self:Test { function f(x:self); } +trait Test { function f(x:self); } -default instance a:Test { function f(x:self) {}} +default impl Test { function f(x:self) {}} -data memory(a) = memory(word); -data Proxy(a) = Proxy; +enum memory { memory(word) } +enum Proxy { Proxy } -instance memory(memory(word)):Test { function f(x:self) {}} +impl Test { function f(x:self) {}} -forall a. -function f(p:Proxy(a)) { - let x:memory(a); +function f(p:Proxy) { + let x:a memory; Test.f(x); } function g() { - f(Proxy:Proxy(memory(memory(word)))); // needs to choose default instance in Test.f - f(Proxy:Proxy(memory(word))); // needs to choose concrete instance + f(Proxy as Proxy); // needs to choose default instance in Test.f + f(Proxy as Proxy); // needs to choose concrete instance } diff --git a/test/examples/cases/default-instance-missing.solc b/test/examples/cases/default-instance-missing.solc index 59e710a22..82644203c 100644 --- a/test/examples/cases/default-instance-missing.solc +++ b/test/examples/cases/default-instance-missing.solc @@ -1,17 +1,16 @@ -class self:Test { function f(x:self); } +trait Test { function f(x:self); } -data memory(a) = memory(word); -data Proxy(a) = Proxy; +enum memory { memory(word) } +enum Proxy { Proxy } -instance memory(memory(word)):Test { function f(x:self) {}} +impl Test { function f(x:self) {}} -forall a. -function f(p:Proxy(a)) { - let x:memory(a); +function f(p:Proxy) { + let x:a memory; Test.f(x); } function g() { - f(Proxy:Proxy(memory(memory(word)))); // needs to choose default instance in Test.f - f(Proxy:Proxy(memory(word))); // needs to choose concrete instance + f(Proxy as Proxy); // needs to choose default instance in Test.f + f(Proxy as Proxy); // needs to choose concrete instance } diff --git a/test/examples/cases/default-instance-weak.solc b/test/examples/cases/default-instance-weak.solc index a9002afa5..efdfe6465 100644 --- a/test/examples/cases/default-instance-weak.solc +++ b/test/examples/cases/default-instance-weak.solc @@ -1,18 +1,17 @@ -class self:Test(weak) { function f(x:self) -> weak; } +trait Test { function f(x:self) returns (weak); } -data memory(a) = memory(word); -data Proxy(a) = Proxy; -data Bool = True | False; -default instance a:Test(word) { function f(x:a) -> word { return 42; }} +enum memory { memory(word) } +enum Proxy { Proxy } +enum Bool { True, False } +default impl Test { function f(x:a) returns (word) { return 42; }} -instance memory(memory(word)):Test(Bool) { function f(x:self) { return Bool.True; }} +impl Test { function f(x:self) { return Bool.True; }} // If we choose the default instance to typecheck f, // this will pass type-checking, since ``r`` is word. // But: for a = memory(word), ``r`` will be ``bool`` and this is invalid! -forall a. -function f(p:Proxy(a)) { - let x:memory(a); +function f(p:Proxy) { + let x:a memory; let r :word = Test.f(x); assembly { sstore(0, r) @@ -20,6 +19,6 @@ function f(p:Proxy(a)) { } function g() { - f(Proxy:Proxy(memory(memory(word)))); // valid, since default instance is used - f(Proxy:Proxy(memory(word))); // PROBLEM: now we have a bool cross the assembly barrier + f(Proxy as Proxy); // valid, since default instance is used + f(Proxy as Proxy); // PROBLEM: now we have a bool cross the assembly barrier } diff --git a/test/examples/cases/derive-generic-excluded.solc b/test/examples/cases/derive-generic-excluded.solc index 784f244ce..822b41220 100644 --- a/test/examples/cases/derive-generic-excluded.solc +++ b/test/examples/cases/derive-generic-excluded.solc @@ -2,38 +2,37 @@ // listed types. Pair has its instance suppressed and provided manually; // Box gets its instance generated automatically. -import std.{*}; -import std.Generic.{*}; +import {*} from std; +import {*} from std.Generic; -pragma no-patterson-condition; -pragma no-bounded-variable-condition; -pragma no-generic-instance-for Pair; +pragma solcore noPattersonCondition; +pragma solcore noBoundVariableCondition; +pragma solcore noGenericInstanceFor Pair; -data Pair(a, b) = MkPair(a, b); +enum Pair { MkPair(a, b) } -data Box(a) = MkBox(a); +enum Box { MkBox(a) } // Manual instance for Pair (suppressed from auto-derivation). -forall a b. -instance Pair(a, b) : Generic((a, b)) { - function from(p : Pair(a, b)) -> (a, b) { - match p { - | Pair.MkPair(x, y) => return (x, y); - } +impl Generic, (a, b)> { + function from(p : Pair) returns ((a, b)) { + match (p ) { + case Pair.MkPair(x, y) { return (x, y); + } } } - function to(t : (a, b)) -> Pair(a, b) { - match t { - | (x, y) => return Pair.MkPair(x, y); - } + function to(t : (a, b)) returns (Pair) { + match (t ) { + case (x, y) { return Pair.MkPair(x, y); + } } } } // Box gets its Generic instance auto-derived (not excluded). -function boxRoundtrip(v : word) -> bool { - let b : Box(word) = Box.MkBox(v); +function boxRoundtrip(v : word) returns (bool) { + let b : Box = Box.MkBox(v); let r : word = Generic.from(b); - let b2 : Box(word) = Generic.to(r); - match b2 { - | Box.MkBox(v2) => return eqWord(v, v2); - } + let b2 : Box = Generic.to(r); + match (b2 ) { + case Box.MkBox(v2) { return eqWord(v, v2); + } } } diff --git a/test/examples/cases/derive-generic-sum.solc b/test/examples/cases/derive-generic-sum.solc index aa93b5607..a30a537d0 100644 --- a/test/examples/cases/derive-generic-sum.solc +++ b/test/examples/cases/derive-generic-sum.solc @@ -2,33 +2,33 @@ // Neither Option nor Tree has an explicit Generic instance; both should be // generated automatically by DeriveGeneric. -import std.{*}; -import std.Generic.{*}; +import {*} from std; +import {*} from std.Generic; -pragma no-patterson-condition; -pragma no-bounded-variable-condition; +pragma solcore noPattersonCondition; +pragma solcore noBoundVariableCondition; -data Option(a) = None | Some(a); +enum Option { None, Some(a) } -data Tree(a) = Leaf | Node(Tree(a), a, Tree(a)); +enum Tree { Leaf, Node(Tree, a, Tree) } // Use the auto-derived instances to check that from/to round-trip. -function roundtripNone() -> bool { - let x : Option(word) = Option.None; - let r : sum((), word) = Generic.from(x); - let x2 : Option(word) = Generic.to(r); - match x2 { - | Option.None => return true; - | Option.Some(_) => return false; - } +function roundtripNone() returns (bool) { + let x : Option = Option.None; + let r : sum<(), word> = Generic.from(x); + let x2 : Option = Generic.to(r); + match (x2 ) { + case Option.None { return true; + } case Option.Some(_) { return false; + } } } -function roundtripSome(v : word) -> bool { - let x : Option(word) = Option.Some(v); - let r : sum((), word) = Generic.from(x); - let x2 : Option(word) = Generic.to(r); - match x2 { - | Option.None => return false; - | Option.Some(v2) => return eqWord(v, v2); - } +function roundtripSome(v : word) returns (bool) { + let x : Option = Option.Some(v); + let r : sum<(), word> = Generic.from(x); + let x2 : Option = Generic.to(r); + match (x2 ) { + case Option.None { return false; + } case Option.Some(v2) { return eqWord(v, v2); + } } } diff --git a/test/examples/cases/dispatch.solc b/test/examples/cases/dispatch.solc index f33527b95..bbc17f021 100644 --- a/test/examples/cases/dispatch.solc +++ b/test/examples/cases/dispatch.solc @@ -1,50 +1,50 @@ // --- Preliminaries --- -data Bool = True | False; -data Proxy(a) = Proxy; +enum Bool { True, False } +enum Proxy { Proxy } // --- Core Data Types --- // A contract contains a tuple of methods and a single fallback // TODO: implement receive() -data Contract(methods, fb) = Contract(methods,fb); +enum Contract { Contract(methods, fb) } // A method contains an implementation (fn) as well as it's name and type signature -data Method(name, args, rets, fn) = Method(name, args, rets, fn); +enum Method { Method(name, args, rets, fn) } // Contains the implementation for the fallback (fn) as well as it's type signature -data Fallback(args, rets, fn) = Fallback(args, rets, fn); +enum Fallback { Fallback(args, rets, fn) } // --- Method Selectors --- // For each method in a contract the compiler generates a unique type and // produces a `Selector` instance for that type that returns the selector hash -forall nm . class nm:Selector { - function hash(prx: Proxy(nm)) -> word; +trait Selector { + function hash(prx: Proxy) returns (word); } // Method has a Selector if its name has a Selector -forall name args rets fn . name:Selector => instance Method(name,args,rets,fn):Selector { - function hash(prx: Proxy(Method(name,args,rets,fn))) -> word { - return Selector.hash(Proxy : Proxy(name)); +impl Selector> where name: Selector { + function hash(prx: Proxy>) returns (word) { + return Selector.hash(Proxy as Proxy); } } // --- Method Execution --- // Describes how to execute a given method / fallback -forall ty callvalueCheckStatus . class ty:ExecMethod { - function exec(x: ty, pstatus : Proxy(callvalueCheckStatus)) -> (); +trait ExecMethod { + function exec(x: ty, pstatus : Proxy) returns (()); } // If fn matches the provided args/ret types, then we can execute any method -forall name args rets fn callvalueCheckStatus . fn:invokable(args,ret) => instance Method(name,Proxy(args),Proxy(rets),fn):ExecMethod { - function exec(m : Method(name,args,rets,fn), pstatus : Proxy(callvalueCheckStatus)) -> () { - match m { - | Method(nm,args,rets,fn) => +impl ExecMethod, Proxy, fn>> where fn: invokable { + function exec(m : Method, pstatus : Proxy) returns (()) { + match (m ) { + case Method(nm,args,rets,fn) { // check callvalue - MethodLevelCallvalueCheck.checkCallvalue(Proxy : Proxy(Method(name,args,rets,fn)), pstatus); + MethodLevelCallvalueCheck.checkCallvalue(Proxy as Proxy>, pstatus); // check we have enough calldata for the head of args // abi decode args from calldata @@ -52,18 +52,18 @@ forall name args rets fn callvalueCheckStatus . fn:invokable(args,ret) => instan // abi encode rets to memory // returndata copy encoded returns // evm return - return (); - } + return; + } } } } // If fn matches the provided args/ret types, then we can execute any fallback -forall args rets fn callvalueCheckStatus . fn:invokable(args,ret) => instance Fallback(Proxy(args),Proxy(rets),fn):ExecMethod { - function exec(fb : Fallback(args,rets,fn), pstatus : Proxy (callvalueCheckStatus)) -> () { - match fb { - | Fallback(args, rets, fn) => +impl ExecMethod, Proxy, fn>> where fn: invokable { + function exec(fb : Fallback, pstatus : Proxy) returns (()) { + match (fb ) { + case Fallback(args, rets, fn) { // check callvalue - MethodLevelCallvalueCheck.checkCallvalue(Proxy : Proxy(Fallback(args,rets,fn)), pstatus); + MethodLevelCallvalueCheck.checkCallvalue(Proxy as Proxy>, pstatus); // check we have enough calldata for the head of args // abi decode args from calldata @@ -71,120 +71,120 @@ forall args rets fn callvalueCheckStatus . fn:invokable(args,ret) => instance Fa // abi encode rets to memory // returndata copy encoded returns // evm return - return (); - } + return; + } } } } // --- Method Dispatch --- // For a given tuple of methods this executes the method specified by the first four bytes of calldata -forall ty callvalueCheckStatus . class ty:RunDispatch { - function go(methods : ty, pstatus : Proxy(callvalueCheckStatus)) -> (); +trait RunDispatch { + function go(methods : ty, pstatus : Proxy) returns (()); } // We can dispatch to a single executable method with a known selector // TODO: do we need this instance? -forall m callvalueCheckStatus . m:ExecMethod, m:Selector => instance m:RunDispatch { - function go(method : m, pstatus : Proxy(callvalueCheckStatus)) -> () { - match selector_matches(Proxy : Proxy(m)) { - | Bool.True => ExecMethod.exec(method, pstatus); - | Bool.False => return (); - } +impl RunDispatch where m: ExecMethod, m: Selector { + function go(method : m, pstatus : Proxy) returns (()) { + match (selector_matches(Proxy as Proxy) ) { + case Bool.True { ExecMethod.exec(method, pstatus); + } case Bool.False { return; + } } } } // We can dispatch to a tuple of executable methods with a known selector -forall n m callvalueCheckStatus . n:ExecMethod, n:Selector, m:ExecMethod, m:Selector => instance (n,m):RunDispatch { - function go(methods : (n,m), pstatus : Proxy(callvalueCheckStatus)) -> () { - match methods { - | (method_n, method_m) => - match selector_matches(Proxy : Proxy(n)) { - | Bool.True => ExecMethod.exec(method_n); - | Bool.False => match selector_matches(Proxy : Proxy(m)) { - | Bool.True => ExecMethod.exec(method_m, pstatus); - | Bool.False => return (); - } - } - } +impl RunDispatch<(n, m)> where n: ExecMethod, n: Selector, m: ExecMethod, m: Selector { + function go(methods : (n, m), pstatus : Proxy) returns (()) { + match (methods ) { + case (method_n, method_m) { + match (selector_matches(Proxy as Proxy) ) { + case Bool.True { ExecMethod.exec(method_n); + } case Bool.False { match (selector_matches(Proxy as Proxy) ) { + case Bool.True { ExecMethod.exec(method_m, pstatus); + } case Bool.False { return; + } } + } } + } } } } // Recursive instance -forall n m callvalueCheckStatus . n:ExecMethod, n:Selector, m:RunDispatch => instance (n,m):RunDispatch { - function go(methods : (n,m), pstatus : Proxy(callvalueCheckStatus)) -> () { - match methods { - | (method_n, rest) => - match selector_matches(Proxy : Proxy(n)) { - | Bool.True => ExecMethod.exec(method_n, pstatus); - | Bool.False => RunDispatch.go(rest, pstatus); - } - } +impl RunDispatch<(n, m)> where n: ExecMethod, n: Selector, m: RunDispatch { + function go(methods : (n, m), pstatus : Proxy) returns (()) { + match (methods ) { + case (method_n, rest) { + match (selector_matches(Proxy as Proxy) ) { + case Bool.True { ExecMethod.exec(method_n, pstatus); + } case Bool.False { RunDispatch.go(rest, pstatus); + } } + } } } } // TODO: we only wanna do the calldataload once // Given evidence of a name with a known selector, we can check if it matches the selector in the first four bytes of calldata -forall name . name:Selector => function selector_matches(prx : Proxy(name)) -> Bool { +function selector_matches(prx : Proxy) returns (Bool) where name: Selector { let hash = Selector.hash(prx); let res : word; assembly { let sel := shr(224, calldataload(0)) res := eq(sel, hash) } - match res { - | 0 => return Bool.False; - | _ => return Bool.True; - } + match (res ) { + case 0 { return Bool.False; + } default { return Bool.True; + } } } // --- Callvalue Checks --- // If every method on a contract is non payable, we lift the callvalue check to run before method dispatch // NonPayable instances should be generated by the compiler as part of desugaring -forall ty . class ty:NonPayable {} -forall ty . class ty:AllNonPayable {} -forall n m . n:NonPayable, m:AllNonPayable => instance (n,m):AllNonPayable {} +trait NonPayable {} +trait AllNonPayable {} +impl AllNonPayable<(n, m)> where n: NonPayable, m: AllNonPayable {} -data CallvalueChecked; +enum CallvalueChecked {} -data CallvalueUnchecked; -forall ty . class ty:MethodsMustCheckCalldata {} -instance CallvalueUnchecked:MethodsMustCheckCalldata {} +enum CallvalueUnchecked {} +trait MethodsMustCheckCalldata {} +impl MethodsMustCheckCalldata {} // If every method is non payable we run the callvalue check before method dispatch -forall ty ret . class ty:TopLevelCallvalueCheck(ret) { - function checkCallvalue(prx : Proxy(ty)) -> Proxy(ret); +trait TopLevelCallvalueCheck { + function checkCallvalue(prx : Proxy) returns (Proxy); } -forall methods . default instance methods:TopLevelCallvalueCheck(CallvalueUnchecked) { - function checkCallvalue(prx : Proxy(methods)) -> Proxy(CallvalueUnchecked) { return Proxy : Proxy(CallvalueUnchecked); } +default impl TopLevelCallvalueCheck { + function checkCallvalue(prx : Proxy) returns (Proxy) { return Proxy as Proxy; } } -forall methods . methods:AllNonPayable => instance methods:TopLevelCallvalueCheck(CallvalueChecked) { - function checkCallvalue(prx : Proxy(methods)) -> Proxy(CallvalueChecked) { +impl TopLevelCallvalueCheck where methods: AllNonPayable { + function checkCallvalue(prx : Proxy) returns (Proxy) { assembly { if gt(callvalue(), 0) { mstore(0,0x2) revert(0,32) } } - return Proxy : Proxy(CallvalueChecked); + return Proxy as Proxy; } } // If only some methods are non payable, then we run the check during method execution -forall ty status . class ty:MethodLevelCallvalueCheck { - function checkCallvalue(pty : Proxy(ty), pstatus : Proxy(status)) -> (); +trait MethodLevelCallvalueCheck { + function checkCallvalue(pty : Proxy, pstatus : Proxy) returns (()); } -forall method status . default instance method:MethodLevelCallvalueCheck { - function checkCallvalue(pty : Proxy(method), pstatus : Proxy(status)) -> () { } +default impl MethodLevelCallvalueCheck { + function checkCallvalue(pty : Proxy, pstatus : Proxy) returns (()) { } } -forall method status . method:NonPayable, status:MethodsMustCheckCalldata => instance method:MethodLevelCallvalueCheck { - function checkCallvalue(pty : Proxy(method), pstatus : Proxy(status)) -> (){ +impl MethodLevelCallvalueCheck where method: NonPayable, status: MethodsMustCheckCalldata { + function checkCallvalue(pty : Proxy, pstatus : Proxy) returns (()){ assembly { if gt(callvalue(), 0) { mstore(0, 0x1) @@ -197,22 +197,22 @@ forall method status . method:NonPayable, status:MethodsMustCheckCalldata => ins // --- Contract Execution --- // Describes how to execute a given contract -forall c . class c:RunContract { - function exec(v : c) -> (); +trait RunContract { + function exec(v : c) returns (()); } // If we have a dispatch for the contracts methods, and we know how to execute it's fallback, then we can define an entrypoint -forall methods fb . methods:RunDispatch, fb:ExecMethod => instance Contract(methods, fb):RunContract { - function exec(c : Contract(methods, fb)) -> () { - match c { - | Contract(ms, fb) => +impl RunContract> where methods: RunDispatch, fb: ExecMethod { + function exec(c : Contract) returns (()) { + match (c ) { + case Contract(ms, fb) { // set free memory pointer to the output of memoryguard // https://docs.soliditylang.org/en/v0.8.30/yul.html#memoryguard // TODO: we will need to consider immutables here at some point... // assembly { mstore(0x40, memoryguard(128)) } // if all methods are non payable then check callvalue - let callvalueChecked = TopLevelCallvalueCheck.checkCallvalue(Proxy : Proxy((fb, methods))); + let callvalueChecked = TopLevelCallvalueCheck.checkCallvalue(Proxy as Proxy<(fb, methods)>); // check that we have at least 4 bytes of calldata let haveSelector : word; @@ -220,15 +220,15 @@ forall methods fb . methods:RunDispatch, fb:ExecMethod => instance Contract(meth haveSelector := lt(3, calldatasize()) } - match haveSelector { - | 0 => assembly { revert(0,0) } - | _ => + match (haveSelector ) { + case 0 { assembly { revert(0,0) } + } default { // dispatch to method based on selector RunDispatch.go(ms, callvalueChecked); // run fallback if no methods matched ExecMethod.exec(fb); - } - } + } } + } } } } @@ -236,14 +236,14 @@ forall methods fb . methods:RunDispatch, fb:ExecMethod => instance Contract(meth // compiler generated -function revert_handler() -> () { +function revert_handler() returns (()) { assembly { revert(0,0) } } -data C_Add2_Selector = C_Add2_Selector; +enum C_Add2_Selector { C_Add2_Selector } -instance C_Add2_Selector:Selector { - function hash(prx: Proxy(C_Add2_Selector)) -> word { +impl Selector { + function hash(prx: Proxy) returns (word) { // This would be keccak256("add2(uint256,uint256)") >> 224 // Compiler computes this at compile time return 0x29fcda33; // placeholder value @@ -253,16 +253,16 @@ instance C_Add2_Selector:Selector { // transform contract C { - public function add2(x : word, y : word) -> word { + function add2(x : word, y : word) public returns (word) { let ret : word; assembly { ret := add(x,y) } return ret; } - public function main() -> word { + function main() public returns (word) { let c = Contract( - Method(C_Add2_Selector, Proxy : Proxy((word,word)), Proxy : Proxy(word), add2), - Fallback(Proxy : Proxy(()),Proxy : Proxy(()),revert_handler) + Method(C_Add2_Selector, Proxy as Proxy<(word, word)>, Proxy as Proxy, add2), + Fallback(Proxy as Proxy<()>,Proxy as Proxy<()>,revert_handler) ); RunContract.exec(c); diff --git a/test/examples/cases/dot-expression-assignment-context.solc b/test/examples/cases/dot-expression-assignment-context.solc index 6037f00a3..724fdc67c 100644 --- a/test/examples/cases/dot-expression-assignment-context.solc +++ b/test/examples/cases/dot-expression-assignment-context.solc @@ -1,7 +1,7 @@ -data Option(a) = Some(a) | None; +enum Option { Some(a), None } -function main() -> Option(word) { - let x : Option(word); +function main() returns (Option) { + let x : Option; x = .None; return x; } diff --git a/test/examples/cases/dot-expression-call-arg-context.solc b/test/examples/cases/dot-expression-call-arg-context.solc index ba4781edc..7903816ff 100644 --- a/test/examples/cases/dot-expression-call-arg-context.solc +++ b/test/examples/cases/dot-expression-call-arg-context.solc @@ -1,12 +1,12 @@ -data Option = None | Some(word); +enum Option { None, Some(word) } -function use(x: Option) -> word { - match x { - | Option.Some(v) => return v; - | Option.None => return 0; - } +function use(x: Option) returns (word) { + match (x ) { + case Option.Some(v) { return v; + } case Option.None { return 0; + } } } -function main() -> word { +function main() returns (word) { return use(.Some(7)); } diff --git a/test/examples/cases/dot-expression-constructor.solc b/test/examples/cases/dot-expression-constructor.solc index 5163ac1cf..2798f4857 100644 --- a/test/examples/cases/dot-expression-constructor.solc +++ b/test/examples/cases/dot-expression-constructor.solc @@ -1,12 +1,12 @@ -data Option = None | Some(word); +enum Option { None, Some(word) } -function mkSome(x: word) -> Option { +function mkSome(x: word) returns (Option) { return .Some(x); } -function main() -> word { - match mkSome(7) { - | Option.Some(v) => return v; - | Option.None => return 0; - } +function main() returns (word) { + match (mkSome(7) ) { + case Option.Some(v) { return v; + } case Option.None { return 0; + } } } diff --git a/test/examples/cases/dot-expression-match-return.solc b/test/examples/cases/dot-expression-match-return.solc index 26f6c9469..9e320a027 100644 --- a/test/examples/cases/dot-expression-match-return.solc +++ b/test/examples/cases/dot-expression-match-return.solc @@ -1,13 +1,13 @@ -data Bar = Foo(word); +enum Bar { Foo(word) } -function x(x: Bar) -> Bar { - match x { - | .Foo(w) => return .Foo(w); - } +function x(x: Bar) returns (Bar) { + match (x ) { + case .Foo(w) { return .Foo(w); + } } } -function main() -> word { - match x(Bar.Foo(7)) { - | Bar.Foo(w) => return w; - } +function main() returns (word) { + match (x(Bar.Foo(7)) ) { + case Bar.Foo(w) { return w; + } } } diff --git a/test/examples/cases/dot-expression-nested-context.solc b/test/examples/cases/dot-expression-nested-context.solc index 97d6f1775..effe5742e 100644 --- a/test/examples/cases/dot-expression-nested-context.solc +++ b/test/examples/cases/dot-expression-nested-context.solc @@ -1,5 +1,5 @@ -data Option(a) = Some(a) | None; +enum Option { Some(a), None } -function main() -> Option(Option(word)) { +function main() returns (Option>) { return .Some(.None); } diff --git a/test/examples/cases/dot-expression-no-context-fail.solc b/test/examples/cases/dot-expression-no-context-fail.solc index 485ed7988..6472dc4e6 100644 --- a/test/examples/cases/dot-expression-no-context-fail.solc +++ b/test/examples/cases/dot-expression-no-context-fail.solc @@ -1,6 +1,6 @@ -data Option = None | Some(word); +enum Option { None, Some(word) } -function bad() -> Option { +function bad() returns (Option) { let x = .Some(1); return x; } diff --git a/test/examples/cases/dot-expression-unknown-fail.solc b/test/examples/cases/dot-expression-unknown-fail.solc index 11ab2af7d..6a44271a4 100644 --- a/test/examples/cases/dot-expression-unknown-fail.solc +++ b/test/examples/cases/dot-expression-unknown-fail.solc @@ -1,5 +1,5 @@ -data Option = None | Some(word); +enum Option { None, Some(word) } -function bad() -> Option { +function bad() returns (Option) { return .Nope(1); } diff --git a/test/examples/cases/dot-pattern-constructor.solc b/test/examples/cases/dot-pattern-constructor.solc index 0f2046335..6d1d760df 100644 --- a/test/examples/cases/dot-pattern-constructor.solc +++ b/test/examples/cases/dot-pattern-constructor.solc @@ -1,12 +1,12 @@ -data Option = None | Some(word); +enum Option { None, Some(word) } -function fromOption(x: Option) -> word { - match x { - | .Some(v) => return v; - | .None => return 0; - } +function fromOption(x: Option) returns (word) { + match (x ) { + case .Some(v) { return v; + } case .None { return 0; + } } } -function main() -> word { +function main() returns (word) { return fromOption(Option.Some(3)); } diff --git a/test/examples/cases/dot-pattern-nested-constructor.solc b/test/examples/cases/dot-pattern-nested-constructor.solc index 10cb4a896..42d692c5e 100644 --- a/test/examples/cases/dot-pattern-nested-constructor.solc +++ b/test/examples/cases/dot-pattern-nested-constructor.solc @@ -1,15 +1,15 @@ -data Option(a) = None | Some(a); +enum Option { None, Some(a) } -function join(mmx: Option(Option(word))) -> Option(word) { - match mmx { - | .Some(.Some(x)) => return .Some(x); - | _ => return .None; - } +function join(mmx: Option>) returns (Option) { + match (mmx ) { + case .Some(.Some(x)) { return .Some(x); + } default { return .None; + } } } -function main() -> word { - match join(.Some(.Some(9))) { - | .Some(v) => return v; - | .None => return 0; - } +function main() returns (word) { + match (join(.Some(.Some(9))) ) { + case .Some(v) { return v; + } case .None { return 0; + } } } diff --git a/test/examples/cases/dot-primitive-constructor.solc b/test/examples/cases/dot-primitive-constructor.solc index fb935c67f..5bb1f795d 100644 --- a/test/examples/cases/dot-primitive-constructor.solc +++ b/test/examples/cases/dot-primitive-constructor.solc @@ -1,7 +1,7 @@ -function main() -> word { +function main() returns (word) { let b: bool = .true; - match b { - | .true => return 1; - | .false => return 0; - } + match (b ) { + case .true { return 1; + } case .false { return 0; + } } } diff --git a/test/examples/cases/duplicated-type-name.solc b/test/examples/cases/duplicated-type-name.solc index 186277951..c5164fba9 100644 --- a/test/examples/cases/duplicated-type-name.solc +++ b/test/examples/cases/duplicated-type-name.solc @@ -1,5 +1,5 @@ -data Foo = Bar; -data Foo = Baz; +enum Foo { Bar } +enum Foo { Baz } function main() { let x = Foo.Baz; diff --git a/test/examples/cases/empty-asm.solc b/test/examples/cases/empty-asm.solc index 7c2883057..f7a5b8cd6 100644 --- a/test/examples/cases/empty-asm.solc +++ b/test/examples/cases/empty-asm.solc @@ -1,9 +1,9 @@ -function f(x : word) -> word { - match x { - | 0 => +function f(x : word) returns (word) { + match (x ) { + case 0 { let ret : word; assembly {} return ret; - | _ => return 0; - } + } default { return 0; + } } } diff --git a/test/examples/cases/encoder.solc b/test/examples/cases/encoder.solc index bc470ddde..acdec9983 100644 --- a/test/examples/cases/encoder.solc +++ b/test/examples/cases/encoder.solc @@ -1,35 +1,33 @@ -data TagA = TagA(word); -data TagB = TagB(word); +enum TagA { TagA(word) } +enum TagB { TagB(word) } -forall self rep. -class self:Tag(rep) { - function getTag(x:self) -> rep; +trait Tag { + function getTag(x:self) returns (rep); } -data TypeA = TypeA(word); -instance TypeA:Tag(TagA) { - function getTag(x:TypeA) -> TagA { - match x { | TypeA(w) => return TagA(w); } +enum TypeA { TypeA(word) } +impl Tag { + function getTag(x:TypeA) returns (TagA) { + match (x ) { case TypeA(w) { return TagA(w); } } } } -data TypeB = TypeB(word); -instance TypeB:Tag(TagB) { - function getTag(x:TypeB) -> TagB { - match x { | TypeB(w) => return TagB(w); } +enum TypeB { TypeB(word) } +impl Tag { + function getTag(x:TypeB) returns (TagB) { + match (x ) { case TypeB(w) { return TagB(w); } } } } -forall a b rep1 rep2 . a:Tag(rep1), b:Tag(rep2) => -function tagFirst(x:a, y:b) -> rep1 { +function tagFirst(x:a, y:b) returns (rep1) where a: Tag, b: Tag { return Tag.getTag(x); } contract C { constructor() {} - public function main() -> word { + function main() public returns (word) { let r : TagA = tagFirst(TypeA(42), TypeB(7)); - match r { | TagA(w) => return w; } + match (r ) { case TagA(w) { return w; } } } } diff --git a/test/examples/cases/encoder1.solc b/test/examples/cases/encoder1.solc index ac4185624..75d0ff6cb 100644 --- a/test/examples/cases/encoder1.solc +++ b/test/examples/cases/encoder1.solc @@ -1,25 +1,23 @@ -import std.{*}; +import {*} from std; -forall self rep. -class self:Encoder(rep) { - function encode(x:self, hint:word) -> rep; +trait Encoder { + function encode(x:self, hint:word) returns (rep); } -data Foo = Foo(word); -instance Foo:Encoder(word) { - function encode(x:Foo, hint:word) -> word { - match x { | Foo(w) => return w; } +enum Foo { Foo(word) } +impl Encoder { + function encode(x:Foo, hint:word) returns (word) { + match (x ) { case Foo(w) { return w; } } } } -forall a rep . a:Encoder(rep) => -function encodeAndDiscard(x:a) -> () { +function encodeAndDiscard(x:a) returns (()) where a: Encoder { let enc : rep = Encoder.encode(x, 0); - return (); + return; } contract C { - public function main() -> word { + function main() public returns (word) { encodeAndDiscard(Foo(42)); return 0; } diff --git a/test/examples/cases/fallback-with-args.solc b/test/examples/cases/fallback-with-args.solc index 59387aed3..cd546e54a 100644 --- a/test/examples/cases/fallback-with-args.solc +++ b/test/examples/cases/fallback-with-args.solc @@ -1,10 +1,10 @@ -import std.{*}; -import std.dispatch.{*}; +import {*} from std; +import {*} from std.dispatch; contract BadFallback { constructor() {} - fallback(x: uint256) -> () { + fallback(x: uint256) external { revert("fallback-was-called"); } } diff --git a/test/examples/cases/fallback-with-return.solc b/test/examples/cases/fallback-with-return.solc index ca9e52230..ba5f62107 100644 --- a/test/examples/cases/fallback-with-return.solc +++ b/test/examples/cases/fallback-with-return.solc @@ -1,10 +1,10 @@ -import std.{*}; -import std.dispatch.{*}; +import {*} from std; +import {*} from std.dispatch; contract BadFallback { constructor() {} - fallback() -> uint256 { + fallback() external returns (uint256) { return uint256(0); } } diff --git a/test/examples/cases/false-redundant-warning.solc b/test/examples/cases/false-redundant-warning.solc index 88f95679f..8985ba039 100644 --- a/test/examples/cases/false-redundant-warning.solc +++ b/test/examples/cases/false-redundant-warning.solc @@ -1,15 +1,15 @@ -data Bool = False | True; +enum Bool { False, True } -function test(x : Bool, y : Bool) -> Bool { - match x, y { - | Bool.True, z => return z; - | w, Bool.True => return w; - | a, b => return b; - } +function test(x : Bool, y : Bool) returns (Bool) { + match (x, y ) { + case (Bool.True, z ) { return z; + } case (w, Bool.True ) { return w; + } case (a, b ) { return b; + } } } contract FalseRedundantWarning { - public function main() -> Bool { - test(Bool.False, Bool.True) + function main() public returns (Bool) { + return test(Bool.False, Bool.True); } } diff --git a/test/examples/cases/field-access.solc b/test/examples/cases/field-access.solc index b53e151ff..18b1439ef 100644 --- a/test/examples/cases/field-access.solc +++ b/test/examples/cases/field-access.solc @@ -1,18 +1,18 @@ -import std.{*}; +import {*} from std; contract PoC { field : word; - public function set_x(b: bool) -> bool { + function set_x(b: bool) public returns (bool) { field = b; // BUG: `word` shouldn't be unified with `bool`. return b; } - public function init(foo: bool) -> () { + function init(foo: bool) public returns (()) { field = 2; } - public function main () -> () { + function main () public returns (()) { } } diff --git a/test/examples/cases/field-helper-cxt-collision.solc b/test/examples/cases/field-helper-cxt-collision.solc index 994f6568c..aa2112228 100644 --- a/test/examples/cases/field-helper-cxt-collision.solc +++ b/test/examples/cases/field-helper-cxt-collision.solc @@ -1,14 +1,14 @@ -import std.{*}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +import {*} from std; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; -data FooCxt = FooCxt; +enum FooCxt { FooCxt } contract Foo { x: word; - public function get() -> word { + function get() public returns (word) { return x; } } diff --git a/test/examples/cases/field-name-error.solc b/test/examples/cases/field-name-error.solc index fd1bc3c5e..f7d982842 100644 --- a/test/examples/cases/field-name-error.solc +++ b/test/examples/cases/field-name-error.solc @@ -1,12 +1,12 @@ -import std.{*}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +import {*} from std; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; contract PoC { x : word; - public function main () -> word { + function main () public returns (word) { return 0; } } diff --git a/test/examples/cases/foo-class.solc b/test/examples/cases/foo-class.solc index bb78a2efe..65e2f5fc3 100644 --- a/test/examples/cases/foo-class.solc +++ b/test/examples/cases/foo-class.solc @@ -1,4 +1,3 @@ -forall b self . -class self:Foo(b) { - function foo(x:self) -> b; +trait Foo { + function foo(x:self) returns (b); } diff --git a/test/examples/cases/for-body-shadow.solc b/test/examples/cases/for-body-shadow.solc index 94fc9fc88..a1f60bb2b 100644 --- a/test/examples/cases/for-body-shadow.solc +++ b/test/examples/cases/for-body-shadow.solc @@ -1,7 +1,7 @@ -import std.{Num,Add,Sub,Eq,Ord,Bounded,Typedef,le}; +import {Num,Add,Sub,Eq,Ord,Bounded,Typedef,le} from std; contract C { - public function main() -> word { + function main() public returns (word) { let x : word = 100; let i : word = 0; let s : word = 0; diff --git a/test/examples/cases/for-break.solc b/test/examples/cases/for-break.solc index 79e827d90..a4ef2ad97 100644 --- a/test/examples/cases/for-break.solc +++ b/test/examples/cases/for-break.solc @@ -1,6 +1,6 @@ -import std.{lt,Ord,Add,Sub,Bounded,Num,Eq,Typedef}; +import {lt,Ord,Add,Sub,Bounded,Num,Eq,Typedef} from std; contract BreakTest { - public function main() -> word { + function main() public returns (word) { let result : word = 0; for (let i : word = 0; i < 10; i = i + 1) { if (i == 5) { diff --git a/test/examples/cases/for-continue.solc b/test/examples/cases/for-continue.solc index 03c68ed3e..acf93627e 100644 --- a/test/examples/cases/for-continue.solc +++ b/test/examples/cases/for-continue.solc @@ -1,6 +1,6 @@ -import std.{lt,Ord,Add,Sub,Bounded,Num,Eq,Typedef}; +import {lt,Ord,Add,Sub,Bounded,Num,Eq,Typedef} from std; contract ContinueTest { - public function main() -> word { + function main() public returns (word) { let result : word = 0; for (let i : word = 0; i < 10; i = i + 1) { if (i < 5) { diff --git a/test/examples/cases/for-empty-init.solc b/test/examples/cases/for-empty-init.solc index 5bbaa539b..008a0e99c 100644 --- a/test/examples/cases/for-empty-init.solc +++ b/test/examples/cases/for-empty-init.solc @@ -1,7 +1,7 @@ -import std.{Num,Add,Sub,Eq,Ord,Bounded,Typedef,le}; +import {Num,Add,Sub,Eq,Ord,Bounded,Typedef,le} from std; contract ForEmptyInit { - function main() -> word { + function main() returns (word) { let i : word = 1; let s = 0; for(; i <= 10; i = i + 1) { s = s + i; } diff --git a/test/examples/cases/for-init-shadow.solc b/test/examples/cases/for-init-shadow.solc index d6ceaf8bb..e79dc9315 100644 --- a/test/examples/cases/for-init-shadow.solc +++ b/test/examples/cases/for-init-shadow.solc @@ -1,7 +1,7 @@ -import std.{Num,Add,Sub,Eq,Ord,Bounded,Typedef,le}; +import {Num,Add,Sub,Eq,Ord,Bounded,Typedef,le} from std; contract Prefor { - public function main() -> word { + function main() public returns (word) { let i : word = 100; let s : word = 0; for(let i=1;i<=10;i=i+1) { s = s + i; } diff --git a/test/examples/cases/for-inner-block.solc b/test/examples/cases/for-inner-block.solc index 307903700..e251eeb25 100644 --- a/test/examples/cases/for-inner-block.solc +++ b/test/examples/cases/for-inner-block.solc @@ -1,6 +1,6 @@ -import std.{lt,Ord,Add,Sub,Bounded,Num,Eq,Typedef}; +import {lt,Ord,Add,Sub,Bounded,Num,Eq,Typedef} from std; contract ForInner { - public function main() -> word { + function main() public returns (word) { let result : word = 0; for (let height : word = 0; height < 7; height = height + 1) { if (true) { result = height; } else {} diff --git a/test/examples/cases/for-let-post.solc b/test/examples/cases/for-let-post.solc index a7f1b11d3..a1f8ae935 100644 --- a/test/examples/cases/for-let-post.solc +++ b/test/examples/cases/for-let-post.solc @@ -1,7 +1,7 @@ -import std.{Num,Add,Sub,Eq,Ord,Bounded,Typedef,le}; +import {Num,Add,Sub,Eq,Ord,Bounded,Typedef,le} from std; contract C { - public function main() -> word { + function main() public returns (word) { let i : word = 0; let s : word = 99; for(i=0;i<=0;let j=1) { s = j; i = i + 1; } diff --git a/test/examples/cases/for-let.solc b/test/examples/cases/for-let.solc index b5900f17e..807cf86ca 100644 --- a/test/examples/cases/for-let.solc +++ b/test/examples/cases/for-let.solc @@ -1,7 +1,7 @@ -import std.{Num,Add,Sub,Eq,Ord,Bounded,Typedef,le}; +import {Num,Add,Sub,Eq,Ord,Bounded,Typedef,le} from std; contract Prefor { - public function main() -> word { + function main() public returns (word) { let s : word = 0; for(let i=1;i<=10;i=i+1) { s = s + i;} diff --git a/test/examples/cases/for-loop.solc b/test/examples/cases/for-loop.solc index d910c943e..125119157 100644 --- a/test/examples/cases/for-loop.solc +++ b/test/examples/cases/for-loop.solc @@ -1,7 +1,7 @@ -import std.{Num,Add,Sub,Eq,Ord,Bounded,Typedef,le}; +import {Num,Add,Sub,Eq,Ord,Bounded,Typedef,le} from std; contract Prefor { - public function main() -> word { + function main() public returns (word) { let i:word; let s : word = 0; for(i=1;i<=10;i=i+1) { s = s + i;} diff --git a/test/examples/cases/for-multi-init.solc b/test/examples/cases/for-multi-init.solc index 5f134c4a0..46c1763b3 100644 --- a/test/examples/cases/for-multi-init.solc +++ b/test/examples/cases/for-multi-init.solc @@ -1,7 +1,7 @@ -import std.{Num,Add,Sub,Eq,Ord,Bounded,Typedef,le}; +import {Num,Add,Sub,Eq,Ord,Bounded,Typedef,le} from std; contract ForMultiInit { - function main() -> word { + function main() returns (word) { let i = 0; let j = 0; for (i = 1, j = 10; i <= 3; i = i + 1) { diff --git a/test/examples/cases/for-multi-post.solc b/test/examples/cases/for-multi-post.solc index b0183e9a3..e45590268 100644 --- a/test/examples/cases/for-multi-post.solc +++ b/test/examples/cases/for-multi-post.solc @@ -1,7 +1,7 @@ -import std.{Num,Add,Sub,Eq,Ord,Bounded,Typedef,le}; +import {Num,Add,Sub,Eq,Ord,Bounded,Typedef,le} from std; contract ForMultiPost { - function main() -> word { + function main() returns (word) { let j = 0; for (let i = 0; i <= 3; i = i + 1, j = j + 2) { j = j + i; diff --git a/test/examples/cases/fresh-pat-arg-synonym.solc b/test/examples/cases/fresh-pat-arg-synonym.solc index 876e5bdae..8a5edefc0 100644 --- a/test/examples/cases/fresh-pat-arg-synonym.solc +++ b/test/examples/cases/fresh-pat-arg-synonym.solc @@ -1,10 +1,10 @@ -type W = word; +type W is word; -function f(x:W) -> W { x } +function f(x:W) returns (W) { return x; } contract C { - public function main () -> word { + function main () public returns (word) { return f(42); } } diff --git a/test/examples/cases/fresh-pat-arg.solc b/test/examples/cases/fresh-pat-arg.solc index b7e0958b2..dd3512fab 100644 --- a/test/examples/cases/fresh-pat-arg.solc +++ b/test/examples/cases/fresh-pat-arg.solc @@ -1,7 +1,7 @@ -function g(x:word) -> word { x } +function g(x:word) returns (word) { return x; } -forall a. function h(x:a) -> a { x } +function h(x:a) returns (a) { return x; } contract C { - public function main() -> word { g(h(42)) } + function main() public returns (word) { return g(h(42)); } } diff --git a/test/examples/cases/fresh-variable-shadowing.solc b/test/examples/cases/fresh-variable-shadowing.solc index 930f81ba3..0ecf09b8f 100644 --- a/test/examples/cases/fresh-variable-shadowing.solc +++ b/test/examples/cases/fresh-variable-shadowing.solc @@ -1,14 +1,14 @@ -data Bool = False | True; +enum Bool { False, True } -function test(v0 : Bool, p : Bool) -> Bool { - match p { - | Bool.True => return Bool.False; - | z => return v0; - } +function test(v0 : Bool, p : Bool) returns (Bool) { + match (p ) { + case Bool.True { return Bool.False; + } case z { return v0; + } } } contract FreshVariableShadowing { - public function main() -> Bool { - test(Bool.True, Bool.False) + function main() public returns (Bool) { + return test(Bool.True, Bool.False); } } diff --git a/test/examples/cases/generic-manual-no-pragma.solc b/test/examples/cases/generic-manual-no-pragma.solc index 6551643c5..471edf2a6 100644 --- a/test/examples/cases/generic-manual-no-pragma.solc +++ b/test/examples/cases/generic-manual-no-pragma.solc @@ -1,18 +1,18 @@ // Error case: manual Generic instance without pragma no-generic-instance-for. // The compiler must reject this with a conflict error. -import std.Generic.{*}; +import {*} from std.Generic; -pragma no-patterson-condition; -pragma no-bounded-variable-condition; +pragma solcore noPattersonCondition; +pragma solcore noBoundVariableCondition; -data Foo = MkFoo(word); +enum Foo { MkFoo(word) } -instance Foo : Generic(word) { - function from(x : Foo) -> word { - match x { | Foo.MkFoo(v) => return v; } +impl Generic { + function from(x : Foo) returns (word) { + match (x ) { case Foo.MkFoo(v) { return v; } } } - function to(v : word) -> Foo { + function to(v : word) returns (Foo) { return Foo.MkFoo(v); } } diff --git a/test/examples/cases/generic-product-no-pragma.solc b/test/examples/cases/generic-product-no-pragma.solc index bb2fb4db8..9c37d4f8d 100644 --- a/test/examples/cases/generic-product-no-pragma.solc +++ b/test/examples/cases/generic-product-no-pragma.solc @@ -1,21 +1,21 @@ -import std.{*}; -import std.dispatch.{*}; -import std.Generic.{*}; -import std.ABIGeneric.{*}; +import {*} from std; +import {*} from std.dispatch; +import {*} from std.Generic; +import {*} from std.ABIGeneric; -pragma no-patterson-condition; -pragma no-coverage-condition; -pragma no-bounded-variable-condition; +pragma solcore noPattersonCondition; +pragma solcore noCoverageCondition; +pragma solcore noBoundVariableCondition; -data Point = Point(uint256, uint256); +enum Point { Point(uint256, uint256) } // Manual Generic instance without pragma no-generic-instance-for Point. // The compiler must reject this with a conflict error. -instance Point : Generic((uint256, uint256)) { - function from(p : Point) -> (uint256, uint256) { - match p { | Point(x, y) => return (x, y); } +impl Generic { + function from(p : Point) returns ((uint256, uint256)) { + match (p ) { case Point(x, y) { return (x, y); } } } - function to(t : (uint256, uint256)) -> Point { - match t { | (x, y) => return Point(x, y); } + function to(t : (uint256, uint256)) returns (Point) { + match (t ) { case (x, y) { return Point(x, y); } } } } diff --git a/test/examples/cases/generic-sum-no-pragma.solc b/test/examples/cases/generic-sum-no-pragma.solc index 49923afb7..7fbfd806f 100644 --- a/test/examples/cases/generic-sum-no-pragma.solc +++ b/test/examples/cases/generic-sum-no-pragma.solc @@ -1,27 +1,27 @@ -import std.{*}; -import std.dispatch.{*}; -import std.Generic.{*}; -import std.ABIGeneric.{*}; +import {*} from std; +import {*} from std.dispatch; +import {*} from std.Generic; +import {*} from std.ABIGeneric; -pragma no-patterson-condition; -pragma no-coverage-condition; -pragma no-bounded-variable-condition; +pragma solcore noPattersonCondition; +pragma solcore noCoverageCondition; +pragma solcore noBoundVariableCondition; -data Option(a) = None | Some(a); +enum Option { None, Some(a) } // Manual Generic instance without pragma no-generic-instance-for Option. // The compiler must reject this with a conflict error. -instance Option(uint256) : Generic(sum((), uint256)) { - function from(x : Option(uint256)) -> sum((), uint256) { - match x { - | Option.None => return inl(()); - | Option.Some(v) => return inr(v); - } +impl Generic, sum<(), uint256>> { + function from(x : Option) returns (sum<(), uint256>) { + match (x ) { + case Option.None { return inl(()); + } case Option.Some(v) { return inr(v); + } } } - function to(r : sum((), uint256)) -> Option(uint256) { - match r { - | inl(_) => return Option.None; - | inr(v) => return Option.Some(v); - } + function to(r : sum<(), uint256>) returns (Option) { + match (r ) { + case inl(_) { return Option.None; + } case inr(v) { return Option.Some(v); + } } } } diff --git a/test/examples/cases/if-examples.solc b/test/examples/cases/if-examples.solc index a440c275d..8ef4a0141 100644 --- a/test/examples/cases/if-examples.solc +++ b/test/examples/cases/if-examples.solc @@ -1,11 +1,11 @@ -function toBool(x : word) -> bool { - match x { - | 0 => return false; - | _ => return true; - } +function toBool(x : word) returns (bool) { + match (x ) { + case 0 { return false; + } default { return true; + } } } -function gt(x : word, y : word) -> bool { +function gt(x : word, y : word) returns (bool) { let res : word; assembly { res := gt(x,y) @@ -13,7 +13,7 @@ function gt(x : word, y : word) -> bool { return toBool(res); } -function max(x : word, y : word) -> word { +function max(x : word, y : word) returns (word) { let res : word; if (gt(x,y)) { res = x; @@ -23,11 +23,11 @@ function max(x : word, y : word) -> word { return res; } -function not(x:bool) -> bool { +function not(x:bool) returns (bool) { if (x) { return false; } else { return true; } } -function foo(x : word) -> bool { +function foo(x : word) returns (bool) { if (gt(x,0)) { return true; } else { @@ -37,7 +37,7 @@ function foo(x : word) -> bool { contract IfExamples { - public function main() -> word { - return (if not(foo(42)) then 0 else 1); + function main() public returns (word) { + return (( not(foo(42)) ? 0 : 1)); } } diff --git a/test/examples/cases/import-std.solc b/test/examples/cases/import-std.solc index cbb62e40b..16ee4c361 100644 --- a/test/examples/cases/import-std.solc +++ b/test/examples/cases/import-std.solc @@ -1,10 +1,10 @@ import std; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; contract Test { - public function main() -> word { + function main() public returns (word) { return std.addWord(21, 21); } } diff --git a/test/examples/cases/inc-closure.solc b/test/examples/cases/inc-closure.solc index 210cf69b2..a55d9fbc4 100644 --- a/test/examples/cases/inc-closure.solc +++ b/test/examples/cases/inc-closure.solc @@ -1,4 +1,4 @@ -function inc(x : word) -> word { +function inc(x : word) returns (word) { let f = lam () { let res : word ; assembly { @@ -11,7 +11,7 @@ function inc(x : word) -> word { contract Foo { - public function main () -> word { + function main () public returns (word) { return inc(0); } } diff --git a/test/examples/cases/index-example.solc b/test/examples/cases/index-example.solc index db138d6c0..ce5267bbc 100644 --- a/test/examples/cases/index-example.solc +++ b/test/examples/cases/index-example.solc @@ -1,38 +1,36 @@ -data storage(a) = storage(word); -data storageRef(a) = storageRef(word); -data Proxy(a) = Proxy; +enum storage { storage(word) } +enum storageRef { storageRef(word) } +enum Proxy { Proxy } -data mapping(member, index) = mapping(word, Proxy(member), Proxy(index)); // storage by default +enum mapping { mapping(word, Proxy, Proxy) } // storage by default // data mapRef(a) = mapRef(word); //ref to a map elem -forall lhs rhs . class lhs:Assign(rhs) { - function assign(l:lhs, r:rhs) -> (); +trait Assign { + function assign(l:lhs, r:rhs) returns (()); } -forall a . instance storageRef(a):Assign(a) { - function assign(l:storageRef(a), y:a) { +impl Assign, a> { + function assign(l:storageRef, y:a) { } } -forall self fieldType offsetType . class self:CStructField(fieldType, offsetType) {} -data StructField(structType, fieldSelector) = StructField(structType); +trait CStructField {} +enum StructField { StructField(structType) } -data MemberAccessProxy(a, field, offset) = MemberAccessProxy(a, field); +enum MemberAccessProxy { MemberAccessProxy(a, field) } -forall self memberRefType . class self:LValueMemberAccess(memberRefType) { - function memberAccess(x:self) -> memberRefType; +trait LValueMemberAccess { + function memberAccess(x:self) returns (memberRefType); } // ------------------------------------------------------------------ // Contract field access // ------------------------------------------------------------------ -forall cxt fieldSelector fieldType offsetType - . StructField(cxt, fieldSelector):CStructField(fieldType, offsetType) - => instance MemberAccessProxy(cxt, fieldSelector, offsetType):LValueMemberAccess(storageRef(fieldType)) { - function memberAccess(x:MemberAccessProxy(cxt, fieldSelector, offsetType)) -> storageRef(fieldType) { +impl LValueMemberAccess, storageRef> where StructField: CStructField { + function memberAccess(x:MemberAccessProxy) returns (storageRef) { return storageRef(0x100); } } @@ -41,20 +39,19 @@ forall cxt fieldSelector fieldType offsetType // Indexed access // ------------------------------------------------------------------ -data mapping(index, member) = mapping(word); -data IndexAccessProxy(map, index, member) = IndexAccessProxy(map, index); -data IndexAccessProxy2(map, index, member) = IndexAccessProxy2(map, index, Proxy(member)); +enum mapping { mapping(word) } +enum IndexAccessProxy { IndexAccessProxy(map, index) } +enum IndexAccessProxy2 { IndexAccessProxy2(map, index, Proxy) } -forall map index member. - instance IndexAccessProxy(storageRef(mapping(index,member)), index, member):LValueMemberAccess(storageRef(member)) { - function memberAccess(x:IndexAccessProxy(storageRef(map), index, member)) -> storageRef(member) { +impl LValueMemberAccess member)>, index, member>, storageRef> { + function memberAccess(x:IndexAccessProxy, index, member>) returns (storageRef) { return storageRef(0); } } -data MintCtx = MintCtx; -data balances_sel = balances_sel; -instance StructField(MintCtx, balances_sel):CStructField(mapping(word,word), ()) {} +enum MintCtx { MintCtx } +enum balances_sel { balances_sel } +impl CStructField, mapping(word => word), ()> {} function mint(amount:word) { let bal_prx = MemberAccessProxy(MintCtx, balances_sel); diff --git a/test/examples/cases/instance-closure-error-invalid-member.solc b/test/examples/cases/instance-closure-error-invalid-member.solc index ecd0fc2a2..8bb2d5bad 100644 --- a/test/examples/cases/instance-closure-error-invalid-member.solc +++ b/test/examples/cases/instance-closure-error-invalid-member.solc @@ -1,9 +1,9 @@ -forall t . class t:CtFun { - function ct(x : t) -> ((t) -> t); +trait CtFun { + function ct(x : t) returns (function(t) internal returns (t)); } -instance word:CtFun { - function ct(x : word) -> ((word) -> word) { +impl CtFun { + function ct(x : word) returns (function(word) internal returns (word)) { return lam(y : bool) { return x; }; diff --git a/test/examples/cases/instance-closure-error.solc b/test/examples/cases/instance-closure-error.solc index 0d6d22fc4..6f3ae6dc0 100644 --- a/test/examples/cases/instance-closure-error.solc +++ b/test/examples/cases/instance-closure-error.solc @@ -1,9 +1,9 @@ -forall t . class t:CtFun { - function ct(x : t) -> ((t) -> t); +trait CtFun { + function ct(x : t) returns (function(t) internal returns (t)); } -instance word:CtFun { - function ct(x : word) -> ((word) -> word) { +impl CtFun { + function ct(x : word) returns (function(word) internal returns (word)) { return lam(y : word) { return x; }; diff --git a/test/examples/cases/instance-context-wrong-kind.solc b/test/examples/cases/instance-context-wrong-kind.solc index 8487114fb..a8e6a0eda 100644 --- a/test/examples/cases/instance-context-wrong-kind.solc +++ b/test/examples/cases/instance-context-wrong-kind.solc @@ -1,5 +1,5 @@ -forall a b . class a : Foo(b) {} +trait Foo {} -forall a. class a:C {} +trait C {} -forall t. t:Foo => instance (word,t):C {} +impl C<(word, t)> where t: Foo {} diff --git a/test/examples/cases/instance-synonym-int.solc b/test/examples/cases/instance-synonym-int.solc index e705196d4..3999932a3 100644 --- a/test/examples/cases/instance-synonym-int.solc +++ b/test/examples/cases/instance-synonym-int.solc @@ -1,17 +1,16 @@ -type W = word; +type W is word; -forall i. -class i : FromWord { - function fromWord(x:word) -> i; +trait FromWord { + function fromWord(x:word) returns (i); } -instance word : FromWord { - function fromWord(x:word) -> word { x } +impl FromWord { + function fromWord(x:word) returns (word) { return x; } } contract C { - public function main () -> W { + function main () public returns (W) { let r : W = FromWord.fromWord(42); return r; } diff --git a/test/examples/cases/instance-synonym.solc b/test/examples/cases/instance-synonym.solc index 17d1520d6..15655d4c0 100644 --- a/test/examples/cases/instance-synonym.solc +++ b/test/examples/cases/instance-synonym.solc @@ -1,17 +1,17 @@ -type W = word; +type W is word; -forall self . class self:IdTy { - function id(x:self) -> self; +trait IdTy { + function id(x:self) returns (self); } -instance W:IdTy { - function id(x:W) -> W { +impl IdTy { + function id(x:W) returns (W) { return x; } } contract C { - public function main() -> word { + function main() public returns (word) { return IdTy.id(42); } } diff --git a/test/examples/cases/instance-wrong-sig.solc b/test/examples/cases/instance-wrong-sig.solc index ea5b8d7e3..ae4116393 100644 --- a/test/examples/cases/instance-wrong-sig.solc +++ b/test/examples/cases/instance-wrong-sig.solc @@ -1,15 +1,15 @@ -data uint256 = uint256(word); -data Proxy(a) = Proxy; -forall self . class self:ABIAttribs { - function headSize(ty:Proxy(self)) -> word; - function isStatic(ty:Proxy(self)) -> bool; +enum uint256 { uint256(word) } +enum Proxy { Proxy } +trait ABIAttribs { + function headSize(ty:Proxy) returns (word); + function isStatic(ty:Proxy) returns (bool); } -instance ():ABIAttribs { - function headSize(ty : Proxy(uint256)) -> word { return 0; } - function isStatic(ty : Proxy(uint256)) -> bool { return true; } +impl ABIAttribs<()> { + function headSize(ty : Proxy) returns (word) { return 0; } + function isStatic(ty : Proxy) returns (bool) { return true; } } -instance uint256:ABIAttribs { - function headSize(ty : Proxy(uint256)) -> word { return 32; } - function isStatic(ty : Proxy(uint256)) -> bool { return true; } +impl ABIAttribs { + function headSize(ty : Proxy) returns (word) { return 32; } + function isStatic(ty : Proxy) returns (bool) { return true; } } diff --git a/test/examples/cases/invokable-issue.solc b/test/examples/cases/invokable-issue.solc index a282f233c..90e0516c5 100644 --- a/test/examples/cases/invokable-issue.solc +++ b/test/examples/cases/invokable-issue.solc @@ -1,13 +1,12 @@ -forall abs rep . class abs:Typedef(rep) { - function abs(x:rep) -> abs; - function rep(x:abs) -> rep; +trait Typedef { + function abs(x:rep) returns (abs); + function rep(x:abs) returns (rep); } -forall t. -/* default */ instance t:Typedef(t) { - function abs(x:t) -> t { return x; } - function rep(x:t) -> t { return x; } +/* default */ +impl Typedef { + function abs(x:t) returns (t) { return x; } + function rep(x:t) returns (t) { return x; } } -forall abs rep res. abs:Typedef(rep) => -function lift1ac(f:(rep) -> res, x:rep) -> res { f(Typedef.rep(x)) } +function lift1ac(f:function(rep) internal returns (res), x:rep) returns (res) where abs: Typedef { return f(Typedef.rep(x)); } diff --git a/test/examples/cases/ixa.solc b/test/examples/cases/ixa.solc index 1cddc66ce..d9dd070da 100644 --- a/test/examples/cases/ixa.solc +++ b/test/examples/cases/ixa.solc @@ -1,18 +1,18 @@ // --- preamble / duplicated std defs --- -data Proxy(a) = Proxy; +enum Proxy { Proxy } // dynamic arrays with a runtime size. cannot exist on stack so no data constructor (i.e. should be used in combination with memory / storage pointers). -data array(a); +enum array {} // a typed pointer to a location in memory -data memory(a) = memory(word); +enum memory { memory(word) } // word arithmetc -forall t . class t:Add { function add(l: t, r: t) -> t; } -forall t . class t:Mul { function mul(l: t, r: t) -> t; } -instance word:Add { - function add(l: word, r: word) -> word { +trait Add { function add(l: t, r: t) returns (t); } +trait Mul { function mul(l: t, r: t) returns (t); } +impl Add { + function add(l: word, r: word) returns (word) { let rw : word; assembly { rw := add(l,r) @@ -20,8 +20,8 @@ instance word:Add { return rw; } } -instance word:Mul { - function mul(l: word, r: word) -> word { +impl Mul { + function mul(l: word, r: word) returns (word) { let rw : word; assembly { rw := mul(l,r) @@ -32,101 +32,101 @@ instance word:Mul { // --- MemoryType --- -forall a . class a:MemoryType { - function load(loc : word) -> a; - function store(loc: word, val : a) -> (); - function size(prx : Proxy(a)) -> word; +trait MemoryType { + function load(loc : word) returns (a); + function store(loc: word, val : a) returns (()); + function size(prx : Proxy) returns (word); } -instance word:MemoryType { - function load(loc : word) -> word { +impl MemoryType { + function load(loc : word) returns (word) { let ret : word; assembly { ret := mload(loc) } return ret; } - function store(loc : word, val : word) -> () { + function store(loc : word, val : word) returns (()) { assembly { mstore(loc,val) } } - function size(prx : Proxy(word)) -> word { + function size(prx : Proxy) returns (word) { return 32; } } -forall a . instance memory(array(a)):MemoryType { - function load(loc: word) -> memory(array(a)) { +impl MemoryType { + function load(loc: word) returns (a[] memory) { let ret : word; assembly { ret := mload(loc) } return memory(ret); } - function store(loc : word, val : memory(array(a))) -> () { - match val { - | memory(ptr) => assembly { mstore(loc,ptr) } - } + function store(loc : word, val : a[] memory) returns (()) { + match (val ) { + case memory(ptr) { assembly { mstore(loc,ptr) } + } } } - function size(prx : Proxy(memory(a))) -> word { + function size(prx : Proxy) returns (word) { return 32; } } // --- Assignment --- -forall lhs rhs . class lhs:Assign(rhs) { - function assign(l : lhs, r : rhs) -> (); +trait Assign { + function assign(l : lhs, r : rhs) returns (()); } -instance memory(word):Assign(word) { - function assign(ptr : memory(word), val : word) -> () { - match ptr { - | memory(loc) => assembly { +impl Assign { + function assign(ptr : word memory, val : word) returns (()) { + match (ptr ) { + case memory(loc) { assembly { mstore(loc, val) } - } + } } } } // --- Index Access --- -forall col_idx val . class col_idx:RValueIdxAccess(val) { - function lookup(ci : col_idx) -> val; +trait RValueIdxAccess { + function lookup(ci : col_idx) returns (val); } -forall col_idx val . class col_idx:LValueIdxAccess(val) { - function lookup(ci : col_idx) -> val; +trait LValueIdxAccess { + function lookup(ci : col_idx) returns (val); } -forall a . a:MemoryType => instance (memory(array(a)), word):RValueIdxAccess(a) { - function lookup(col_idx : (memory(array(a)), word)) -> a { - let sz = MemoryType.size(Proxy : Proxy(a)); - match col_idx { - | (col, idx) => match col { - | memory(loc) => +impl RValueIdxAccess<(a[] memory, word), a> where a: MemoryType { + function lookup(col_idx : (a[] memory, word)) returns (a) { + let sz = MemoryType.size(Proxy as Proxy); + match (col_idx ) { + case (col, idx) { match (col ) { + case memory(loc) { return MemoryType.load(Add.add(loc, Mul.mul(idx, sz))); - } - } + } } + } } } } -forall a . a:MemoryType => instance (memory(array(a)), word):LValueIdxAccess(memory(a)) { - function lookup(col_idx : (memory(array(a)), word)) -> memory(a) { - let sz = MemoryType.size(Proxy : Proxy(a)); - match col_idx { - | (col, idx) => match col { - | memory(loc) => return memory(Add.add(loc, Mul.mul(idx, sz))); - } - } +impl LValueIdxAccess<(a[] memory, word), a memory> where a: MemoryType { + function lookup(col_idx : (a[] memory, word)) returns (a memory) { + let sz = MemoryType.size(Proxy as Proxy); + match (col_idx ) { + case (col, idx) { match (col ) { + case memory(loc) { return memory(Add.add(loc, Mul.mul(idx, sz))); + } } + } } } } // --- Examples --- -function main() -> () { - let x : memory(array(memory(array(word)))) = memory(0); +function main() returns (()) { + let x : word[] memory[] memory = memory(0); let y : word = 0; - let z : memory(array(word)) = memory(0); + let z : word[] memory = memory(0); let i0 : word = 0; let i1 : word = 1; diff --git a/test/examples/cases/join.solc b/test/examples/cases/join.solc index e320eece0..602b924f0 100644 --- a/test/examples/cases/join.solc +++ b/test/examples/cases/join.solc @@ -1,26 +1,26 @@ contract Option { - data Option(a) = None | Some(a); - data Bool = False | True; + enum Option { None, Some(a) } + enum Bool { False, True } - public function maybe(n : word, o : Option(word)) -> word { - match o { - | Option.None => return n; - | Option.Some(x) => return x; - } + function maybe(n : word, o : Option) public returns (word) { + match (o ) { + case Option.None { return n; + } case Option.Some(x) { return x; + } } } - public function join(mmx : Option(Option(word))) -> Option(word) { + function join(mmx : Option>) public returns (Option) { let result = Option.None; - match mmx { - | Option.Some(Option.Some(x)) => result = Option.Some(x); - | Option.None => result = Option.None; - | Option.Some(Option.None) => result = Option.None; - | _ => result = Option.None; - } + match (mmx ) { + case Option.Some(Option.Some(x)) { result = Option.Some(x); + } case Option.None { result = Option.None; + } case Option.Some(Option.None) { result = Option.None; + } default { result = Option.None; + } } return result; } - public function main() -> word { + function main() public returns (word) { return maybe(0, join(Option.Some(Option.Some(0)))); } } diff --git a/test/examples/cases/joinErr.solc b/test/examples/cases/joinErr.solc index 6ae546972..6aad4de53 100644 --- a/test/examples/cases/joinErr.solc +++ b/test/examples/cases/joinErr.solc @@ -1,25 +1,25 @@ contract Option { - data Option(a) = None | Some(a); - data Bool = False | True; + enum Option { None, Some(a) } + enum Bool { False, True } - public function maybe(n : word, o : Option(word)) -> word { - match o { - | Option.None => return n; - | Option.Some(x) => return x; - } + function maybe(n : word, o : Option) public returns (word) { + match (o ) { + case Option.None { return n; + } case Option.Some(x) { return x; + } } } - public function join(mmx : Option(Option(word))) -> Option(word) { + function join(mmx : Option>) public returns (Option) { let result = Option.None; - match mmx { - | Option.Some(Option.Some(x)) => result = Option.Some(x); - | Option.None => result = Option.None; - } + match (mmx ) { + case Option.Some(Option.Some(x)) { result = Option.Some(x); + } case Option.None { result = Option.None; + } } return result; } - public function main() -> word { + function main() public returns (word) { return maybe(0, join(Option.Some(Option.Some(Bool.False)))); } } diff --git a/test/examples/cases/listeq.solc b/test/examples/cases/listeq.solc index 21299f76e..4248955c9 100644 --- a/test/examples/cases/listeq.solc +++ b/test/examples/cases/listeq.solc @@ -1,8 +1,8 @@ -data List(a) = Nil | Cons(a,List(a)); -data Bool = False | True; +enum List { Nil, Cons(a, List) } +enum Bool { False, True } -forall a . class a : Eq { - function eq (x : a, y : a) -> Bool ; +trait Eq { + function eq (x : a, y : a) returns (Bool) ; } function foo () { diff --git a/test/examples/cases/listid.solc b/test/examples/cases/listid.solc index b483fa4fe..54d4e0e64 100644 --- a/test/examples/cases/listid.solc +++ b/test/examples/cases/listid.solc @@ -1,12 +1,12 @@ -data List(a) = Nil | Cons(a, List(a)); +enum List { Nil, Cons(a, List) } -forall a . function id(x : a) -> a { +function id(x : a) returns (a) { return x; } -function listid(xs : List(word)) -> List(word) { - match xs { - | List.Nil => return List.Nil ; - | List.Cons(x,xs) => return List.Cons(id(x), listid(xs)); - } +function listid(xs : List) returns (List) { + match (xs ) { + case List.Nil { return List.Nil ; + } case List.Cons(x,xs) { return List.Cons(id(x), listid(xs)); + } } } diff --git a/test/examples/cases/ltimp.solc b/test/examples/cases/ltimp.solc index c31fc5f39..c71d6c553 100644 --- a/test/examples/cases/ltimp.solc +++ b/test/examples/cases/ltimp.solc @@ -1,5 +1,5 @@ -import ltproxy.{ltproxy}; +import {ltproxy} from ltproxy; contract LtImp { - public function main() -> bool { ltproxy() } + function main() public returns (bool) { return ltproxy(); } } diff --git a/test/examples/cases/ltproxy.solc b/test/examples/cases/ltproxy.solc index 15e88c87f..493118bf5 100644 --- a/test/examples/cases/ltproxy.solc +++ b/test/examples/cases/ltproxy.solc @@ -1,7 +1,7 @@ -import std.{lt}; +import {lt} from std; export { ltproxy }; -function ltproxy() -> bool { +function ltproxy() returns (bool) { let zero : word = 0; return (zero < 42); } diff --git a/test/examples/cases/mainproxy.solc b/test/examples/cases/mainproxy.solc index 1e3f5c87f..9aa22862f 100644 --- a/test/examples/cases/mainproxy.solc +++ b/test/examples/cases/mainproxy.solc @@ -1,22 +1,22 @@ -data Proxy(a) = Proxy; +enum Proxy { Proxy } -class self:BaseMemoryType { - function memorySize(x:Proxy(self)) -> word; +trait BaseMemoryType { + function memorySize(x:Proxy) returns (word); } -instance word:BaseMemoryType { - function memorySize(x:Proxy(self)) -> word { +impl BaseMemoryType { + function memorySize(x:Proxy) returns (word) { return 32; } } -function morefun(p:Proxy(t)) -> word { return BaseMemoryType.memorySize(Proxy:Proxy(t)); +function morefun(p:Proxy) returns (word) { return BaseMemoryType.memorySize(Proxy as Proxy); } contract TestMemoryType { - public function main() -> word { - return morefun(Proxy:Proxy(word)); + function main() public returns (word) { + return morefun(Proxy as Proxy); } } diff --git a/test/examples/cases/match-bitwise.solc b/test/examples/cases/match-bitwise.solc index 509088f94..f604a00ba 100644 --- a/test/examples/cases/match-bitwise.solc +++ b/test/examples/cases/match-bitwise.solc @@ -1,8 +1,8 @@ -import std.{*}; -import std.opcodes.{mstore}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +import {*} from std; +import {mstore} from std.opcodes; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; // Regression for the `|` ambiguity between the bitwise-or operator and the // match-arm separator. Each arm below ends in a *bare* expression statement @@ -10,17 +10,17 @@ pragma no-bounded-variable-condition ; // parser read `mstore(...) | => ...` as a single bitwise-or // expression and break the `match`. The `|` *inside* the parentheses is a // genuine bitwise-or; the `|` that starts each arm is a separator. -function emit(x: word) -> () { - match x { - | 0 => mstore(0, x | 1) - | 1 => mstore(0, x & 1) - | _ => mstore(0, x) - } +function emit(x: word) returns (()) { + match (x ) { + case 0 { mstore(0, x | 1); + } case 1 { mstore(0, x & 1); + } default { mstore(0, x); + } } } contract MatchBitwise { // `0 | 1` still folds to 1 at the top level. - public function main() -> word { + function main() public returns (word) { emit(0); return 0 | 1; } diff --git a/test/examples/cases/match-compiler-undef-asm.solc b/test/examples/cases/match-compiler-undef-asm.solc index 28b89fdcc..1ca3549ec 100644 --- a/test/examples/cases/match-compiler-undef-asm.solc +++ b/test/examples/cases/match-compiler-undef-asm.solc @@ -1,19 +1,19 @@ -data Foo(a) = Foo(word); +enum Foo { Foo(word) } -forall a . function read(x : Foo(a)) -> word { +function read(x : Foo) returns (word) { let res : word; match (x) { - | Foo(w) => + case Foo(w) { assembly { res := w } - } + } } return res; } contract Bla { - public function main () -> word { + function main () public returns (word) { return read(Foo(42)); } } diff --git a/test/examples/cases/match-yul.solc b/test/examples/cases/match-yul.solc index a9fe458b0..7b7b8a353 100644 --- a/test/examples/cases/match-yul.solc +++ b/test/examples/cases/match-yul.solc @@ -1,15 +1,15 @@ -data Wrapper = Wrapper(word); +enum Wrapper { Wrapper(word) } contract C { - public function main() -> word { + function main() public returns (word) { return foo(Wrapper(1)); } - public function foo(w:Wrapper) -> word { + function foo(w:Wrapper) public returns (word) { let result : word; - match w { - | Wrapper(ptr) => + match (w ) { + case Wrapper(ptr) { //let ptr2 : word = ptr; assembly { result := calldataload(ptr) } - } + } } return result; } } diff --git a/test/examples/cases/memory.solc b/test/examples/cases/memory.solc index 9ed30b4b3..360a2b9a9 100644 --- a/test/examples/cases/memory.solc +++ b/test/examples/cases/memory.solc @@ -1,7 +1,7 @@ -data Memory(t) = Memory(word); -data Bytes = Bytes; +enum Memory { Memory(word) } +enum Bytes { Bytes } -function get_bytes() -> Memory(Bytes) { +function get_bytes() returns (Memory) { let ptr : word; assembly { ptr := mload(0x40) diff --git a/test/examples/cases/missing-instance.solc b/test/examples/cases/missing-instance.solc index 6bdaf00aa..3284ef889 100644 --- a/test/examples/cases/missing-instance.solc +++ b/test/examples/cases/missing-instance.solc @@ -1,23 +1,23 @@ // Note: this class has no instances! -forall abs rep . class abs:Typedef(rep) { - function rep(x:abs) -> rep; - function abs(x:rep) -> abs; +trait Typedef { + function rep(x:abs) returns (rep); + function abs(x:rep) returns (abs); } -forall self . class self:MemoryType { - function load(ptr:word) -> self; +trait MemoryType { + function load(ptr:word) returns (self); } -instance word:MemoryType { - function load(ptr:word) -> word { - return Typedef.abs(MemoryType.load(ptr) : word); +impl MemoryType { + function load(ptr:word) returns (word) { + return Typedef.abs(MemoryType.load(ptr) as word); // `abs` does not make sense here, but it triggers the bug: // the typechecker should complain about missing instance here } } contract C { - public function main() -> word { + function main() public returns (word) { let ptr : word = 0; // if we inline the let below into return then another bug occurs: main is typed as forall a. () -> a // let w:word = MemoryType.load(0); diff --git a/test/examples/cases/mod-example.solc b/test/examples/cases/mod-example.solc index c69f188e0..6b91f0e9a 100644 --- a/test/examples/cases/mod-example.solc +++ b/test/examples/cases/mod-example.solc @@ -1,7 +1,7 @@ -import std.{*}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; -function foo(x: word, y: word) -> word { +import {*} from std; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; +function foo(x: word, y: word) returns (word) { return x % y; } diff --git a/test/examples/cases/modifier.solc b/test/examples/cases/modifier.solc index ad6c50095..23a458fdf 100644 --- a/test/examples/cases/modifier.solc +++ b/test/examples/cases/modifier.solc @@ -1,5 +1,5 @@ contract C { - public function add(x: word, y:word) -> word { + function add(x: word, y:word) public returns (word) { let r : word; assembly { r := add(x, y) @@ -8,14 +8,14 @@ contract C { } // modifier pattern: wrap add with before/after code - public function modifiedAdd(x : word, y : word) -> word { + function modifiedAdd(x : word, y : word) public returns (word) { // before solidity placeholder let result = add(x, y); // Solidity's placeholder: _; // after solidity placeholder return result; } - public function main() -> word { + function main() public returns (word) { return modifiedAdd(2, 1); } } diff --git a/test/examples/cases/modulo.solc b/test/examples/cases/modulo.solc index 0c05ad3dc..da36132e6 100644 --- a/test/examples/cases/modulo.solc +++ b/test/examples/cases/modulo.solc @@ -1,11 +1,11 @@ -import std.{*}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +import {*} from std; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; // Exercises the `%` operator and the `%=` compound assignment // (the Mod class), plus the mod constant folding. -function f(x: word, y: word) -> word { +function f(x: word, y: word) returns (word) { let acc : word = x % y; acc %= y; // (x % y) % y == x % y once reduced return acc; @@ -13,5 +13,5 @@ function f(x: word, y: word) -> word { contract Modulo { // 17 % 5 == 2, 2 % 5 == 2 — folded at compile time. - public function main() -> word { return f(17, 5); } + function main() public returns (word) { return f(17, 5); } } diff --git a/test/examples/cases/monomorphic-require.solc b/test/examples/cases/monomorphic-require.solc index df7b1a2a9..40b9de962 100644 --- a/test/examples/cases/monomorphic-require.solc +++ b/test/examples/cases/monomorphic-require.solc @@ -1,22 +1,21 @@ // This should trigger a warning and an error in the specialiser // due to unability to resolve result type of require -import std.{uint256,lt,not,Eq,ne,Proxy,bytes4,string}; -import std.dispatch.{*}; +import {uint256,lt,not,Eq,ne,Proxy,bytes4,string} from std; +import {*} from std.dispatch; -forall a. -function myrevert(offset:word, length:word) -> a { +function myrevert(offset:word, length:word) returns (a) { assembly { revert(offset, length) } } -function require(cond: bool) -> () { +function require(cond: bool) returns (()) { if (!cond) { - myrevert(0,0):(); + myrevert(0,0) as (); } } -function callvalue() -> uint256 { +function callvalue() returns (uint256) { let res : word; assembly { res := callvalue() @@ -25,12 +24,12 @@ function callvalue() -> uint256 { } contract Deposit { -public function deposit() -> () { +function deposit() public returns (()) { require(callvalue() != uint256(0)); - return (); + return; } -public function main() -> () { +function main() public returns (()) { deposit(); } } \ No newline at end of file diff --git a/test/examples/cases/morefun.solc b/test/examples/cases/morefun.solc index fbcd6058c..4a150c2b2 100644 --- a/test/examples/cases/morefun.solc +++ b/test/examples/cases/morefun.solc @@ -1,9 +1,9 @@ -data Proxy(a) = Proxy; +enum Proxy { Proxy } -forall a . class a:C { - function fun(p:Proxy(a)) -> word; +trait C { + function fun(p:Proxy) returns (word); } -forall t . t : C => function morefun(p:Proxy(t)) -> word { - return C.fun(Proxy:Proxy(t)); +function morefun(p:Proxy) returns (word) where t: C { + return C.fun(Proxy as Proxy); } diff --git a/test/examples/cases/mptc-both-templates.solc b/test/examples/cases/mptc-both-templates.solc index 222c102a1..1932d5dcd 100644 --- a/test/examples/cases/mptc-both-templates.solc +++ b/test/examples/cases/mptc-both-templates.solc @@ -2,33 +2,31 @@ // both directions. Both should discover the same binding rep=word; the second // application is idempotent (extSpSubst with the same binding is a no-op). -data Box = Box(word); +enum Box { Box(word) } -forall self rep. -class self:Convert(rep) { - function toRep(x:self) -> rep; - function fromRep(x:rep) -> self; +trait Convert { + function toRep(x:self) returns (rep); + function fromRep(x:rep) returns (self); } -instance Box:Convert(word) { - function toRep(x:Box) -> word { - match x { | Box(w) => return w; } +impl Convert { + function toRep(x:Box) returns (word) { + match (x ) { case Box(w) { return w; } } } - function fromRep(x:word) -> Box { + function fromRep(x:word) returns (Box) { return Box(x); } } -forall a rep . a:Convert(rep) => -function roundtrip(x:a) -> a { +function roundtrip(x:a) returns (a) where a: Convert { let r : rep = Convert.toRep(x); return Convert.fromRep(r); } contract C { constructor() {} - public function main() -> word { + function main() public returns (word) { let b : Box = roundtrip(Box(99)); - match b { | Box(w) => return w; } + match (b ) { case Box(w) { return w; } } } } diff --git a/test/examples/cases/mptc-chain-phantom.solc b/test/examples/cases/mptc-chain-phantom.solc index f5822c367..00ac16b33 100644 --- a/test/examples/cases/mptc-chain-phantom.solc +++ b/test/examples/cases/mptc-chain-phantom.solc @@ -7,27 +7,25 @@ // sink's specialisation name is being built, which would produce sink$rep // (wrong) instead of sink$word (correct). -data Foo = Foo(word); +enum Foo { Foo(word) } -forall self rep. -class self:Encoder(rep) { - function encode(x:self, hint:word) -> rep; +trait Encoder { + function encode(x:self, hint:word) returns (rep); } -forall rep r. -class rep:Sink(r) { - function sink(x:rep) -> (); +trait Sink { + function sink(x:rep) returns (()); } -instance Foo:Encoder(word) { - function encode(x:Foo, hint:word) -> word { - match x { | Foo(v) => return v; } +impl Encoder { + function encode(x:Foo, hint:word) returns (word) { + match (x ) { case Foo(v) { return v; } } } } -instance word:Sink(word) { - function sink(x:word) -> () { - return (); +impl Sink { + function sink(x:word) returns (()) { + return; } } @@ -35,16 +33,15 @@ instance word:Sink(word) { // Inside the body, encode returns rep and sink consumes rep. // resolveMPTCsFromPreds must bind rep=word so that sink specialises // to sink$word (not sink$rep). -forall a rep . a:Encoder(rep), rep:Sink(word) => -function f(x:a) -> () { +function f(x:a) returns (()) where a: Encoder, rep: Sink { let r : rep = Encoder.encode(x, 0); Sink.sink(r); - return (); + return; } contract C { constructor() {} - public function main() -> word { + function main() public returns (word) { f(Foo(42)); return 0; } diff --git a/test/examples/cases/mptc-guard-extras-concrete.solc b/test/examples/cases/mptc-guard-extras-concrete.solc index 588af17fb..d0c0044c2 100644 --- a/test/examples/cases/mptc-guard-extras-concrete.solc +++ b/test/examples/cases/mptc-guard-extras-concrete.solc @@ -3,27 +3,25 @@ // `word` directly in the constraint, so freetv extras = [] and the function // compiles through normal type inference without phantom variable discovery. -data Box = Box(word); +enum Box { Box(word) } -forall self rep. -class self:Unbox(rep) { - function unbox(x:self) -> rep; +trait Unbox { + function unbox(x:self) returns (rep); } -instance Box:Unbox(word) { - function unbox(x:Box) -> word { - match x { | Box(w) => return w; } +impl Unbox { + function unbox(x:Box) returns (word) { + match (x ) { case Box(w) { return w; } } } } -forall a . a:Unbox(word) => -function extractWord(x:a) -> word { +function extractWord(x:a) returns (word) where a: Unbox { return Unbox.unbox(x); } contract C { constructor() {} - public function main() -> word { + function main() public returns (word) { return extractWord(Box(42)); } } diff --git a/test/examples/cases/mptc-multi-instance.solc b/test/examples/cases/mptc-multi-instance.solc index 20d74d238..80687d403 100644 --- a/test/examples/cases/mptc-multi-instance.solc +++ b/test/examples/cases/mptc-multi-instance.solc @@ -4,38 +4,36 @@ // so only the Foo entry fires and rep is resolved to RepFoo. // Similarly for getTag(Bar(2)) rep resolves to RepBar. -data Foo = Foo(word); -data Bar = Bar(word); -data RepFoo = RepFoo(word); -data RepBar = RepBar(word); +enum Foo { Foo(word) } +enum Bar { Bar(word) } +enum RepFoo { RepFoo(word) } +enum RepBar { RepBar(word) } -forall self rep. -class self:Tagged(rep) { - function tag(x:self) -> rep; +trait Tagged { + function tag(x:self) returns (rep); } -instance Foo:Tagged(RepFoo) { - function tag(x:Foo) -> RepFoo { - match x { | Foo(w) => return RepFoo(w); } +impl Tagged { + function tag(x:Foo) returns (RepFoo) { + match (x ) { case Foo(w) { return RepFoo(w); } } } } -instance Bar:Tagged(RepBar) { - function tag(x:Bar) -> RepBar { - match x { | Bar(w) => return RepBar(w); } +impl Tagged { + function tag(x:Bar) returns (RepBar) { + match (x ) { case Bar(w) { return RepBar(w); } } } } -forall a rep . a:Tagged(rep) => -function getTag(x:a) -> rep { +function getTag(x:a) returns (rep) where a: Tagged { return Tagged.tag(x); } contract C { constructor() {} - public function main() -> word { + function main() public returns (word) { let rf : RepFoo = getTag(Foo(1)); let rb : RepBar = getTag(Bar(2)); - match rf { | RepFoo(w) => return w; } + match (rf ) { case RepFoo(w) { return w; } } } } diff --git a/test/examples/cases/mptc-nop-mainty-free.solc b/test/examples/cases/mptc-nop-mainty-free.solc index 0ddc08e2a..9349dbda0 100644 --- a/test/examples/cases/mptc-nop-mainty-free.solc +++ b/test/examples/cases/mptc-nop-mainty-free.solc @@ -13,27 +13,25 @@ // interfere with the normal specialisation of `mapEncode` when called // from a concrete call site. -data Foo = Foo(word); +enum Foo { Foo(word) } -forall self rep. -class self:Encoder(rep) { - function encode(x:self, hint:word) -> rep; +trait Encoder { + function encode(x:self, hint:word) returns (rep); } -instance Foo:Encoder(word) { - function encode(x:Foo, hint:word) -> word { - match x { | Foo(v) => return v; } +impl Encoder { + function encode(x:Foo, hint:word) returns (word) { + match (x ) { case Foo(v) { return v; } } } } -forall a rep. a:Encoder(rep) => -function extractVal(x:a) -> rep { +function extractVal(x:a) returns (rep) where a: Encoder { return Encoder.encode(x, 0); } contract C { constructor() {} - public function main() -> word { + function main() public returns (word) { return extractVal(Foo(7)); } } diff --git a/test/examples/cases/mptc-partial-instance.solc b/test/examples/cases/mptc-partial-instance.solc index ac1f8743b..2509c6bd3 100644 --- a/test/examples/cases/mptc-partial-instance.solc +++ b/test/examples/cases/mptc-partial-instance.solc @@ -5,33 +5,33 @@ // resolveMPTCsFromPreds detects this (concreteExtras still has free vars) and skips // the instance, letting normal type inference determine the extra type instead. -pragma no-coverage-condition Nth; +pragma solcore noCoverageCondition Nth; -data Zero; -data Succ(a); -data Proxy(a) = Proxy; +enum Zero {} +enum Succ {} +enum Proxy { Proxy } -forall a b c. class a:Nth(b, c) { - function nth(x:Proxy(a), y:b) -> c; +trait Nth { + function nth(x:Proxy, y:b) returns (c); } -forall a b. instance Zero:Nth((a,b), a) { - function nth(x:Proxy(Zero), y:(a,b)) -> a { - match y { | (a, b) => return a; } +impl Nth { + function nth(x:Proxy, y:(a, b)) returns (a) { + match (y ) { case (a, b) { return a; } } } } -forall n a b c. n:Nth(b,c) => instance Succ(n):Nth((a,b), c) { - function nth(x:Proxy(Succ(n)), y:(a,b)) -> c { - match y { | (a, b) => return Nth.nth(Proxy : Proxy(n), b); } +impl Nth, (a, b), c> where n: Nth { + function nth(x:Proxy>, y:(a, b)) returns (c) { + match (y ) { case (a, b) { return Nth.nth(Proxy as Proxy, b); } } } } contract C { constructor() {} - public function main() -> word { + function main() public returns (word) { let p : (word, word, word) = (1, 2, 3); - let x : word = Nth.nth(Proxy : Proxy(Zero), p); + let x : word = Nth.nth(Proxy as Proxy, p); return x; } } diff --git a/test/examples/cases/mptc-template-a-only.solc b/test/examples/cases/mptc-template-a-only.solc index c42458ebf..a46996c35 100644 --- a/test/examples/cases/mptc-template-a-only.solc +++ b/test/examples/cases/mptc-template-a-only.solc @@ -3,27 +3,25 @@ // fire. The specialiser must discover rep=word solely via Template A: // specmgu (Box -> word) (Box -> freshV) => freshV = word => rep = word -data Box = Box(word); +enum Box { Box(word) } -forall self rep. -class self:Unbox(rep) { - function unbox(x:self) -> rep; +trait Unbox { + function unbox(x:self) returns (rep); } -instance Box:Unbox(word) { - function unbox(x:Box) -> word { - match x { | Box(w) => return w; } +impl Unbox { + function unbox(x:Box) returns (word) { + match (x ) { case Box(w) { return w; } } } } -forall a rep . a:Unbox(rep) => -function extract(x:a) -> rep { +function extract(x:a) returns (rep) where a: Unbox { return Unbox.unbox(x); } contract C { constructor() {} - public function main() -> word { + function main() public returns (word) { return extract(Box(42)); } } diff --git a/test/examples/cases/mptc-template-b-only.solc b/test/examples/cases/mptc-template-b-only.solc index 07ac91c88..f7a1ff42e 100644 --- a/test/examples/cases/mptc-template-b-only.solc +++ b/test/examples/cases/mptc-template-b-only.solc @@ -4,28 +4,26 @@ // specmgu (word -> Box) (freshV -> Box) => freshV = word => rep = word // The `hint:a` argument makes a=Box concrete at the call site. -data Box = Box(word); +enum Box { Box(word) } -forall self rep. -class self:Rebox(rep) { - function rebox(x:rep) -> self; +trait Rebox { + function rebox(x:rep) returns (self); } -instance Box:Rebox(word) { - function rebox(x:word) -> Box { +impl Rebox { + function rebox(x:word) returns (Box) { return Box(x); } } -forall a rep . a:Rebox(rep) => -function rewrap(val:rep, hint:a) -> a { +function rewrap(val:rep, hint:a) returns (a) where a: Rebox { return Rebox.rebox(val); } contract C { constructor() {} - public function main() -> word { + function main() public returns (word) { let b : Box = rewrap(7, Box(0)); - match b { | Box(w) => return w; } + match (b ) { case Box(w) { return w; } } } } diff --git a/test/examples/cases/multi-stmt-var-leaf.solc b/test/examples/cases/multi-stmt-var-leaf.solc index aff2196ad..d672a55a3 100644 --- a/test/examples/cases/multi-stmt-var-leaf.solc +++ b/test/examples/cases/multi-stmt-var-leaf.solc @@ -1,11 +1,11 @@ -data Bool = False | True; +enum Bool { False, True } contract MultiStmtVarLeaf { - public function main(x:Bool) -> Bool { - match x { - | y => + function main(x:Bool) public returns (Bool) { + match (x ) { + case y { let z = y; return z; - } + } } } } diff --git a/test/examples/cases/nano-desugared.solc b/test/examples/cases/nano-desugared.solc index 055dc9f8e..bf1af65ce 100644 --- a/test/examples/cases/nano-desugared.solc +++ b/test/examples/cases/nano-desugared.solc @@ -10,7 +10,7 @@ function subW (x : word, y : word) { } return res; } -function addU (x : uint, y : uint) -> uint { +function addU (x : uint, y : uint) returns (uint) { let res : word ; let xw : word = Num.toWord(x) ; let yw : word = Num.toWord(y) ; @@ -18,14 +18,14 @@ function addU (x : uint, y : uint) -> uint { } return uint(res); } -function hash1 (x : word) -> word { +function hash1 (x : word) returns (word) { let result : word = 0 ; assembly { mstore(0, x) result := keccak256(0, 32) } return result; } -function hash2 (x : word, y : word) -> word { +function hash2 (x : word, y : word) returns (word) { let result : word = 0 ; assembly { mstore(0, x) mstore(32, y) @@ -33,181 +33,181 @@ function hash2 (x : word, y : word) -> word { } return result; } -data Bool = False | True ; -function not (b : Bool) -> Bool { +enum Bool { False, True } +function not (b : Bool) returns (Bool) { match (b) { - | Bool.False => + case Bool.False { return Bool.True; - | Bool.True => + } case Bool.True { return Bool.False; - } + } } } -function or (x : Bool, y : Bool) -> Bool { +function or (x : Bool, y : Bool) returns (Bool) { match (x) { - | Bool.False => + case Bool.False { return y; - | Bool.True => + } case Bool.True { return Bool.True; - } + } } } function fromBool (b) { match (b) { - | Bool.False => + case Bool.False { return 0; - | Bool.True => + } case Bool.True { return 1; - } + } } } function toBool (x : word) { match (x) { - | 0 => + case 0 { return Bool.False; - | _ => + } default { return Bool.True; - } -} -forall a . class a : Num { - function toWord (x : a) -> word; - function fromWord (x : word) -> a; - function add (x : a, y : a) -> a; - function sub (x : a, y : a) -> a; - function eq (x : a, y : a) -> Bool; - function gt (x : a, y : a) -> Bool; -} -instance word : Num { - function toWord (x : word) -> word { + } } +} +trait Num { + function toWord (x : a) returns (word); + function fromWord (x : word) returns (a); + function add (x : a, y : a) returns (a); + function sub (x : a, y : a) returns (a); + function eq (x : a, y : a) returns (Bool); + function gt (x : a, y : a) returns (Bool); +} +impl Num { + function toWord (x : word) returns (word) { return x; } - function fromWord (x : word) -> word { + function fromWord (x : word) returns (word) { return x; } - function add (x : word, y : word) -> word { + function add (x : word, y : word) returns (word) { return addW(x, y); } - function sub (x : word, y : word) -> word { + function sub (x : word, y : word) returns (word) { return addW(x, y); } - function eq (x : word, y : word) -> Bool { + function eq (x : word, y : word) returns (Bool) { let res : word ; assembly { res := eq(x, y) } return toBool(res); } - function gt (x : word, y : word) -> Bool { + function gt (x : word, y : word) returns (Bool) { let res : word ; assembly { res := gt(x, y) } return toBool(res); } } -forall a . a : Num => function ge (x : a, y : a) -> Bool { +function ge (x : a, y : a) returns (Bool) where a: Num { return or(Num.gt(x, y), Num.eq(x, y)); } -data uint = uint(word) ; -instance uint : Num { - function toWord (x : uint) -> word { +enum uint { uint(word) } +impl Num { + function toWord (x : uint) returns (word) { match (x) { - | uint(y) => + case uint(y) { return y; - } + } } } - function fromWord (x : word) -> uint { + function fromWord (x : word) returns (uint) { return uint(x); } - function add (x : uint, y : uint) -> uint { + function add (x : uint, y : uint) returns (uint) { return uint(addW(Num.toWord(x), Num.toWord(y))); } - function sub (x : uint, y : uint) -> uint { + function sub (x : uint, y : uint) returns (uint) { return uint(subW(Num.toWord(x), Num.toWord(y))); } - function eq (x : uint, y : uint) -> Bool { + function eq (x : uint, y : uint) returns (Bool) { return Num.eq(Num.toWord(x), Num.toWord(y)); } - function gt (x : uint, y : uint) -> Bool { + function gt (x : uint, y : uint) returns (Bool) { return Num.gt(Num.toWord(x), Num.toWord(y)); } } -forall abs rep . class abs : Typedef (rep) { - function rep (x : abs) -> rep; - function abs (x : rep) -> abs; +trait Typedef { + function rep (x : abs) returns (rep); + function abs (x : rep) returns (abs); } -instance word : Typedef (word) { - function rep (x : word) -> word { +impl Typedef { + function rep (x : word) returns (word) { return x; } - function abs (x : word) -> word { + function abs (x : word) returns (word) { return x; } } -instance uint : Typedef (word) { - function rep (x : uint) -> word { +impl Typedef { + function rep (x : uint) returns (word) { match (x) { - | uint(y) => + case uint(y) { return y; - } + } } } - function abs (x : word) -> uint { + function abs (x : word) returns (uint) { return uint(x); } } -data address = address(word) ; -instance address : Typedef (word) { - function rep (x : address) -> word { +enum address { address(word) } +impl Typedef { + function rep (x : address) returns (word) { match (x) { - | address(y) => + case address(y) { return y; - } + } } } - function abs (x : word) -> address { + function abs (x : word) returns (address) { return address(x); } } -data storage (a) = storage(word) ; -data ContractStorage (cxt) = ContractStorage(cxt) ; -data storageRef (a) = storageRef(word) ; -data Proxy (a) = Proxy ; -data mapping (member, index) = mapping(word, Proxy(member), Proxy(index)) ; -data mapRef (a) = mapRef(word) ; -forall a . instance storage(a) : Typedef (word) { - function rep (x : storage(a)) -> word { +enum storage { storage(word) } +enum ContractStorage { ContractStorage(cxt) } +enum storageRef { storageRef(word) } +enum Proxy { Proxy } +enum mapping { mapping(word, Proxy, Proxy) } +enum mapRef { mapRef(word) } +impl Typedef { + function rep (x : a storage) returns (word) { match (x) { - | storage(y) => + case storage(y) { return y; - } + } } } - function abs (x : word) -> storage(a) { + function abs (x : word) returns (a storage) { return storage(x); } } -forall a . instance storageRef(a) : Typedef (word) { - function rep (x : storageRef(a)) -> word { +impl Typedef, word> { + function rep (x : storageRef) returns (word) { match (x) { - | storageRef(y) => + case storageRef(y) { return y; - } + } } } - function abs (x : word) -> storageRef(a) { + function abs (x : word) returns (storageRef) { return storageRef(x); } } -forall lhs rhs . class lhs : Assign (rhs) { - function assign (l : lhs, r : rhs) -> (); +trait Assign { + function assign (l : lhs, r : rhs) returns (()); } -data ref (a) = ref(a) ; -forall a . instance ref(a) : Assign (a) { - function assign (l : ref(a), r : a) -> () { - return (); +enum ref { ref(a) } +impl Assign, a> { + function assign (l : ref, r : a) returns (()) { + return; } } -forall self . class self : StorageType { - function sload (ptr : word) -> self; - function store (ptr : word, value : self) -> (); +trait StorageType { + function sload (ptr : word) returns (self); + function store (ptr : word, value : self) returns (()); } -forall self . class self : StorageSize { - function size (x : Proxy(self)) -> word; +trait StorageSize { + function size (x : Proxy) returns (word); } -function sload_ (x : word) -> word { +function sload_ (x : word) returns (word) { let res : word ; assembly { res := sload(x) } @@ -217,151 +217,151 @@ function sstore_ (a : word, v : word) { assembly { sstore(a, v) } } -instance word : StorageType { - function sload (ptr : word) -> word { +impl StorageType { + function sload (ptr : word) returns (word) { let r : word ; assembly { r := sload(ptr) } return r; } - function store (ptr : word, value : word) -> () { + function store (ptr : word, value : word) returns (()) { assembly { sstore(ptr, value) } } } -instance uint : StorageType { - function sload (ptr : word) -> uint { - return Typedef.abs(sload_(ptr)) : uint; +impl StorageType { + function sload (ptr : word) returns (uint) { + return Typedef.abs(sload_(ptr)) as uint; } - function store (ptr : word, value : uint) -> () { + function store (ptr : word, value : uint) returns (()) { return sstore_(ptr, Typedef.rep(value)); } } -instance address : StorageType { - function sload (ptr : word) -> address { - return Typedef.abs(sload_(ptr)) : address; +impl StorageType
{ + function sload (ptr : word) returns (address) { + return Typedef.abs(sload_(ptr)) as address; } - function store (ptr : word, value : address) -> () { + function store (ptr : word, value : address) returns (()) { return sstore_(ptr, Typedef.rep(value)); } } -forall a . a : StorageType => instance storageRef(a) : Assign (a) { - function assign (l : storageRef(a), y : a) -> () { +impl Assign, a> where a: StorageType { + function assign (l : storageRef, y : a) returns (()) { StorageType.store(Typedef.rep(l), y); } } -forall self fieldType offsetType . class self :CStructField(fieldType, offsetType) { +trait CStructField { } -data StructField (structType, fieldSelector) = StructField(structType) ; -data MemberAccessProxy (a, field, offset) = MemberAccessProxy(a, field) ; -forall a field offset . function memberAccessD1 (x : MemberAccessProxy(a, field, offset)) -> a { +enum StructField { StructField(structType) } +enum MemberAccessProxy { MemberAccessProxy(a, field) } +function memberAccessD1 (x : MemberAccessProxy) returns (a) { match (x) { - | MemberAccessProxy(y, z) => + case MemberAccessProxy(y, z) { return y; - } + } } } -forall self memberRefType . class self : LValueMemberAccess (memberRefType) { - function memberAccess (x : self) -> memberRefType; +trait LValueMemberAccess { + function memberAccess (x : self) returns (memberRefType); } -forall self memberValueType . class self : RValueMemberAccess (memberValueType) { - function memberAccess (x : self) -> memberValueType; +trait RValueMemberAccess { + function memberAccess (x : self) returns (memberValueType); } -forall structType fieldSelector fieldType offsetType . StructField(structType, fieldSelector) :CStructField(fieldType, offsetType), offsetType : StorageSize => instance MemberAccessProxy(storage(structType), fieldSelector, offsetType) : LValueMemberAccess (storageRef(fieldType)) { - function memberAccess (x : MemberAccessProxy(storage(structType), fieldSelector, offsetType)) -> storageRef(fieldType) { +impl LValueMemberAccess, storageRef> where StructField: CStructField, offsetType: StorageSize { + function memberAccess (x : MemberAccessProxy) returns (storageRef) { let ptr : word = Typedef.rep(memberAccessD1(x)) ; - let size : word = StorageSize.size(Proxy : Proxy(offsetType)) ; + let size : word = StorageSize.size(Proxy as Proxy) ; assembly { ptr := add(ptr, size) } return storageRef(ptr); } } -instance () : StorageSize { - function size (x : Proxy(())) -> word { +impl StorageSize<()> { + function size (x : Proxy<()>) returns (word) { return 0; } } -instance word : StorageSize { - function size (x : Proxy(word)) -> word { +impl StorageSize { + function size (x : Proxy) returns (word) { return 1; } } -instance uint : StorageSize { - function size (x : Proxy(uint)) -> word { +impl StorageSize { + function size (x : Proxy) returns (word) { return 1; } } -instance address : StorageSize { - function size (x : Proxy(address)) -> word { +impl StorageSize
{ + function size (x : Proxy
) returns (word) { return 1; } } -forall a b . a : StorageSize, b : StorageSize => instance (a, b) : StorageSize { - function size (x : Proxy((a, b))) -> word { - let a_sz : word = StorageSize.size(Proxy : Proxy(a)) ; - let b_sz : word = StorageSize.size(Proxy : Proxy(b)) ; +impl StorageSize<(a, b)> where a: StorageSize, b: StorageSize { + function size (x : Proxy<(a, b)>) returns (word) { + let a_sz : word = StorageSize.size(Proxy as Proxy) ; + let b_sz : word = StorageSize.size(Proxy as Proxy) ; assembly { a_sz := add(a_sz, b_sz) } return a_sz; } } -forall cxt fieldSelector fieldType offsetType . StructField(ContractStorage(cxt), fieldSelector) :CStructField(fieldType, offsetType), offsetType : StorageSize => instance MemberAccessProxy(ContractStorage(cxt), fieldSelector, offsetType) : LValueMemberAccess (storageRef(fieldType)) { - function memberAccess (x : MemberAccessProxy(ContractStorage(cxt), fieldSelector, offsetType)) -> storageRef(fieldType) { +impl LValueMemberAccess, fieldSelector, offsetType>, storageRef> where StructField, fieldSelector>: CStructField, offsetType: StorageSize { + function memberAccess (x : MemberAccessProxy, fieldSelector, offsetType>) returns (storageRef) { let ptr : word = 256 ; - let offsetSize : word = StorageSize.size(Proxy : Proxy(offsetType)) ; + let offsetSize : word = StorageSize.size(Proxy as Proxy) ; assembly { ptr := add(ptr, offsetSize) } return storageRef(ptr); } } -forall cxt fieldSelector fieldType offsetType . StructField(ContractStorage(cxt), fieldSelector) :CStructField(fieldType, offsetType), fieldType : StorageType, offsetType : StorageSize => instance MemberAccessProxy(ContractStorage(cxt), fieldSelector, offsetType) : RValueMemberAccess (fieldType) { - function memberAccess (x : MemberAccessProxy(ContractStorage(cxt), fieldSelector, offsetType)) -> fieldType { +impl RValueMemberAccess, fieldSelector, offsetType>, fieldType> where StructField, fieldSelector>: CStructField, fieldType: StorageType, offsetType: StorageSize { + function memberAccess (x : MemberAccessProxy, fieldSelector, offsetType>) returns (fieldType) { let ptr : word = 256 ; - let offsetSize : word = StorageSize.size(Proxy : Proxy(offsetType)) ; - return StorageType.sload(addW(ptr, offsetSize)) : fieldType; + let offsetSize : word = StorageSize.size(Proxy as Proxy) ; + return StorageType.sload(addW(ptr, offsetSize)) as fieldType; } } -data mapping (index, member) = mapping(word) ; -forall member index . instance mapping(index, member) : Typedef (word) { - function rep (x : mapping(index, member)) -> word { +enum mapping { mapping(word) } +impl Typedef member), word> { + function rep (x : mapping(index => member)) returns (word) { match (x) { - | mapping(y) => + case mapping(y) { return y; - } + } } } - function abs (x : word) -> mapping(index, member) { + function abs (x : word) returns (mapping(index => member)) { return mapping(x); } } -forall index member . instance mapping(index, member) : StorageSize { - function size (x : Proxy(mapping(index, member))) -> word { +impl StorageSize member)> { + function size (x : Proxy member)>) returns (word) { return 1; } } -data IndexAccessProxy (map, index, member) = IndexAccessProxy(map, index) ; -forall index member . index : Typedef (word) => instance IndexAccessProxy(storageRef(mapping(index, member)), index, member) : LValueMemberAccess (storageRef(member)) { - function memberAccess (x : IndexAccessProxy(storageRef(mapping(index, member)), index, member)) -> storageRef(member) { +enum IndexAccessProxy { IndexAccessProxy(map, index) } +impl LValueMemberAccess member)>, index, member>, storageRef> where index: Typedef { + function memberAccess (x : IndexAccessProxy member)>, index, member>) returns (storageRef) { return storageRef(indexStorageSlot(x)); } } -forall map index member . index : Typedef (word), member : StorageType, map : Typedef (word) => instance IndexAccessProxy(map, index, member) : RValueMemberAccess (member) { - function memberAccess (x : IndexAccessProxy(map, index, member)) -> member { +impl RValueMemberAccess, member> where index: Typedef, member: StorageType, map: Typedef { + function memberAccess (x : IndexAccessProxy) returns (member) { let slot : word = indexStorageSlot(x) ; return StorageType.sload(slot); } } -forall index map member . map : Typedef (word), index : Typedef (word) => function indexStorageSlot (x : IndexAccessProxy(map, index, member)) -> word { +function indexStorageSlot (x : IndexAccessProxy) returns (word) where map: Typedef, index: Typedef { match (x) { - | IndexAccessProxy(map, i) => + case IndexAccessProxy(map, i) { let mapptr : word = Typedef.rep(map) ; let rawidx : word = Typedef.rep(i) ; let loc : word = hash2(mapptr, rawidx) ; return loc; - } + } } } -forall a b . a : RValueMemberAccess (b) => function rval (x : a) -> b { +function rval (x : a) returns (b) where a: RValueMemberAccess { return RValueMemberAccess.memberAccess(x); } -function caller () -> address { +function caller () returns (address) { let res : word ; assembly { res := caller() } @@ -372,68 +372,68 @@ function require1fail () { assembly { mstore(0, 2320231852978620534530211544385868) revert(0, 32) } - return (); + return; } function require1 (cond : Bool) { match (cond) { - | Bool.False => + case Bool.False { return require1fail(); - | Bool.True => - return (); - } + } case Bool.True { + return; + } } } -function nop () -> () { - return (); +function nop () returns (()) { + return; } -data UintCxt = UintCxt ; -data reserved_sel = reserved_sel ; -instance StructField(ContractStorage(UintCxt), reserved_sel) :CStructField(word, ()) { +enum UintCxt { UintCxt } +enum reserved_sel { reserved_sel } +impl CStructField, reserved_sel>, word, ()> { } -data msg_sender_sel = msg_sender_sel ; -instance StructField(ContractStorage(UintCxt), msg_sender_sel) :CStructField(address, (word, ())) { +enum msg_sender_sel { msg_sender_sel } +impl CStructField, msg_sender_sel>, address, (word, ())> { } -data owner_sel = owner_sel ; -instance StructField(ContractStorage(UintCxt), owner_sel) :CStructField(address, (word, (address, ()))) { +enum owner_sel { owner_sel } +impl CStructField, owner_sel>, address, (word, (address, ()))> { } -data decimals_sel = decimals_sel ; -instance StructField(ContractStorage(UintCxt), decimals_sel) :CStructField(uint, (word, (address, (address, ())))) { +enum decimals_sel { decimals_sel } +impl CStructField, decimals_sel>, uint, (word, (address, (address, ())))> { } -data totalSupply_sel = totalSupply_sel ; -instance StructField(ContractStorage(UintCxt), totalSupply_sel) :CStructField(uint, (word, (address, (address, (uint, ()))))) { +enum totalSupply_sel { totalSupply_sel } +impl CStructField, totalSupply_sel>, uint, (word, (address, (address, (uint, ()))))> { } -data balances_sel = balances_sel ; -instance StructField(ContractStorage(UintCxt), balances_sel) :CStructField(mapping(address, uint), (word, (address, (address, (uint, (uint, ())))))) { +enum balances_sel { balances_sel } +impl CStructField, balances_sel>, mapping(address => uint), (word, (address, (address, (uint, (uint, ())))))> { } contract Uint { - public function mint (amount : uint) { + function mint (amount : uint) public { Assign.assign(LValueMemberAccess.memberAccess(IndexAccessProxy(LValueMemberAccess.memberAccess(MemberAccessProxy(ContractStorage(UintCxt), balances_sel)), rval(MemberAccessProxy(ContractStorage(UintCxt), owner_sel)))), Num.add(rval(IndexAccessProxy(LValueMemberAccess.memberAccess(MemberAccessProxy(ContractStorage(UintCxt), balances_sel)), rval(MemberAccessProxy(ContractStorage(UintCxt), owner_sel)))), amount)); Assign.assign(LValueMemberAccess.memberAccess(MemberAccessProxy(ContractStorage(UintCxt), totalSupply_sel)), Num.add(rval(MemberAccessProxy(ContractStorage(UintCxt), totalSupply_sel)), amount)); } - public function transferFrom (src : address, dst : address, amt : uint) -> Bool { + function transferFrom (src : address, dst : address, amt : uint) public returns (Bool) { require1(ge(rval(IndexAccessProxy(LValueMemberAccess.memberAccess(MemberAccessProxy(ContractStorage(UintCxt), balances_sel)), src)), amt)); withdraw(src, amt); deposit(dst, amt); return Bool.True; } - public function withdraw (src : address, amt : uint) { - Assign.assign(LValueMemberAccess.memberAccess(IndexAccessProxy(LValueMemberAccess.memberAccess(MemberAccessProxy(ContractStorage(UintCxt), balances_sel)), src)), Num.sub(rval(IndexAccessProxy(LValueMemberAccess.memberAccess(MemberAccessProxy(ContractStorage(UintCxt), balances_sel)), src)), amt) : uint); + function withdraw (src : address, amt : uint) public { + Assign.assign(LValueMemberAccess.memberAccess(IndexAccessProxy(LValueMemberAccess.memberAccess(MemberAccessProxy(ContractStorage(UintCxt), balances_sel)), src)), Num.sub(rval(IndexAccessProxy(LValueMemberAccess.memberAccess(MemberAccessProxy(ContractStorage(UintCxt), balances_sel)), src)), amt) as uint); } - public function deposit (dst : address, amt : uint) { - Assign.assign(LValueMemberAccess.memberAccess(IndexAccessProxy(LValueMemberAccess.memberAccess(MemberAccessProxy(ContractStorage(UintCxt), balances_sel)), dst)), Num.add(rval(IndexAccessProxy(LValueMemberAccess.memberAccess(MemberAccessProxy(ContractStorage(UintCxt), balances_sel)), dst)), amt) : uint); + function deposit (dst : address, amt : uint) public { + Assign.assign(LValueMemberAccess.memberAccess(IndexAccessProxy(LValueMemberAccess.memberAccess(MemberAccessProxy(ContractStorage(UintCxt), balances_sel)), dst)), Num.add(rval(IndexAccessProxy(LValueMemberAccess.memberAccess(MemberAccessProxy(ContractStorage(UintCxt), balances_sel)), dst)), amt) as uint); } - public function init () { + function init () public { Assign.assign(LValueMemberAccess.memberAccess(MemberAccessProxy(ContractStorage(UintCxt), owner_sel)), address(81985529216486895)); Assign.assign(LValueMemberAccess.memberAccess(MemberAccessProxy(ContractStorage(UintCxt), msg_sender_sel)), caller()); Assign.assign(LValueMemberAccess.memberAccess(MemberAccessProxy(ContractStorage(UintCxt), decimals_sel)), Num.fromWord(18)); } - public function main () -> uint { + function main () public returns (uint) { init(); mint(uint(1000)); mint(uint(1000)); let amt = uint(1) ; let src : address = rval(MemberAccessProxy(ContractStorage(UintCxt), owner_sel)) ; transferFrom(rval(MemberAccessProxy(ContractStorage(UintCxt), owner_sel)), rval(MemberAccessProxy(ContractStorage(UintCxt), msg_sender_sel)), uint(42)); - require1(Bool.True) : (); - return rval(IndexAccessProxy(LValueMemberAccess.memberAccess(MemberAccessProxy(ContractStorage(UintCxt), balances_sel)), rval(MemberAccessProxy(ContractStorage(UintCxt), msg_sender_sel)))):uint; + require1(Bool.True) as (); + return rval(IndexAccessProxy(LValueMemberAccess.memberAccess(MemberAccessProxy(ContractStorage(UintCxt), balances_sel)), rval(MemberAccessProxy(ContractStorage(UintCxt), msg_sender_sel)))) as uint; } } diff --git a/test/examples/cases/nid.solc b/test/examples/cases/nid.solc index 24d8a88c6..77ac3439b 100644 --- a/test/examples/cases/nid.solc +++ b/test/examples/cases/nid.solc @@ -1,8 +1,8 @@ -function id (x : word) -> word { +function id (x : word) returns (word) { return x; } -function nid (x : word) -> word { +function nid (x : word) returns (word) { return id(x); } diff --git a/test/examples/cases/noclosure.solc b/test/examples/cases/noclosure.solc index f961cdae6..dc2af933d 100644 --- a/test/examples/cases/noclosure.solc +++ b/test/examples/cases/noclosure.solc @@ -1,4 +1,4 @@ -function foo (z : word) -> word { +function foo (z : word) returns (word) { let f = lam (x : word, y : word) { return primAddWord(x,primAddWord(y,1)); }; diff --git a/test/examples/cases/noconstr.solc b/test/examples/cases/noconstr.solc index 286d0ee6f..1a29fe5f4 100644 --- a/test/examples/cases/noconstr.solc +++ b/test/examples/cases/noconstr.solc @@ -1,17 +1,17 @@ -class a : Foo { - function foo (x : a) -> word; +trait Foo { + function foo (x : a) returns (word); } // here the constraint a : Foo is // defered to outer scope where the // error should be detected. -function bla (x : a) -> word { +function bla (x : a) returns (word) { return Foo.foo(x); } contract Test { - public function main() { + function main() public { return bla(1); } } diff --git a/test/examples/cases/notif.solc b/test/examples/cases/notif.solc index ee4e9244b..b7bcadf3d 100644 --- a/test/examples/cases/notif.solc +++ b/test/examples/cases/notif.solc @@ -1,4 +1,4 @@ -function not(x : bool) -> bool { +function not(x : bool) returns (bool) { if (x) { return false ; } else { @@ -6,7 +6,7 @@ function not(x : bool) -> bool { } } -function not2(x : bool) -> bool { +function not2(x : bool) returns (bool) { if (x) { return false ; } diff --git a/test/examples/cases/option2.solc b/test/examples/cases/option2.solc index b60d551d8..75ce1d7c7 100644 --- a/test/examples/cases/option2.solc +++ b/test/examples/cases/option2.solc @@ -1,34 +1,34 @@ contract Option { - data Option(a) = None | Some(a); + enum Option { None, Some(a) } - public function just(x : word) -> Option(word) { return Option.Some(x); } + function just(x : word) public returns (Option) { return Option.Some(x); } - public function maybe(n : word, o : Option(word)) -> word { - match o { - | Option.None => return n; - | Option.Some(x) => return x; - } + function maybe(n : word, o : Option) public returns (word) { + match (o ) { + case Option.None { return n; + } case Option.Some(x) { return x; + } } } - public function join(mmx : Option(Option(word))) -> Option(word) { - match mmx { - | Option.None => return Option.None; - | Option.Some(Option.None) => return Option.None; - | Option.Some(Option.Some(x)) => return Option.Some(x); - } + function join(mmx : Option>) public returns (Option) { + match (mmx ) { + case Option.None { return Option.None; + } case Option.Some(Option.None) { return Option.None; + } case Option.Some(Option.Some(x)) { return Option.Some(x); + } } } - public function join2(mmx : Option(Option(word))) -> Option(word) { - match mmx { - | Option.Some(m) => match m { - | Option.None => return Option.None; - | Option.Some(x) => return Option.Some(x); - } - | _ => return Option.None; - } + function join2(mmx : Option>) public returns (Option) { + match (mmx ) { + case Option.Some(m) { match (m ) { + case Option.None { return Option.None; + } case Option.Some(x) { return Option.Some(x); + } } + } default { return Option.None; + } } } - public function main() -> word { + function main() public returns (word) { // return maybe(0, join(Option.Some(Option.Some(42)))); return 42; } diff --git a/test/examples/cases/overlap-synonym-detected.solc b/test/examples/cases/overlap-synonym-detected.solc index fe64a6537..48edb5432 100644 --- a/test/examples/cases/overlap-synonym-detected.solc +++ b/test/examples/cases/overlap-synonym-detected.solc @@ -1,13 +1,13 @@ -type W = word; +type W is word; -forall self . class self:IdTy { - function id(x:self) -> self; +trait IdTy { + function id(x:self) returns (self); } -instance W:IdTy { - function id(x:W) -> W { return x; } +impl IdTy { + function id(x:W) returns (W) { return x; } } -instance word:IdTy { - function id(x:word) -> word { return 0; } +impl IdTy { + function id(x:word) returns (word) { return 0; } } diff --git a/test/examples/cases/overlap-synonym-missed-order.solc b/test/examples/cases/overlap-synonym-missed-order.solc index faa30e068..48d6c64a6 100644 --- a/test/examples/cases/overlap-synonym-missed-order.solc +++ b/test/examples/cases/overlap-synonym-missed-order.solc @@ -1,13 +1,13 @@ -type W = word; +type W is word; -forall self . class self:IdTy { - function id(x:self) -> self; +trait IdTy { + function id(x:self) returns (self); } -instance word:IdTy { - function id(x:word) -> word { return 0; } +impl IdTy { + function id(x:word) returns (word) { return 0; } } -instance W:IdTy { - function id(x:W) -> W { return x; } +impl IdTy { + function id(x:W) returns (W) { return x; } } diff --git a/test/examples/cases/overlap-synonym-missed-two-synonyms.solc b/test/examples/cases/overlap-synonym-missed-two-synonyms.solc index 31cb10fac..b8d7fe03b 100644 --- a/test/examples/cases/overlap-synonym-missed-two-synonyms.solc +++ b/test/examples/cases/overlap-synonym-missed-two-synonyms.solc @@ -1,14 +1,14 @@ -type W = word; -type V = word; +type W is word; +type V is word; -forall self . class self:IdTy { - function id(x:self) -> self; +trait IdTy { + function id(x:self) returns (self); } -instance W:IdTy { - function id(x:W) -> W { return x; } +impl IdTy { + function id(x:W) returns (W) { return x; } } -instance V:IdTy { - function id(x:V) -> V { return 0; } +impl IdTy { + function id(x:V) returns (V) { return 0; } } diff --git a/test/examples/cases/overlapping-heads.solc b/test/examples/cases/overlapping-heads.solc index 152ad54f2..f972b554f 100644 --- a/test/examples/cases/overlapping-heads.solc +++ b/test/examples/cases/overlapping-heads.solc @@ -1,15 +1,15 @@ -forall a b . class a : Foo(b) { - function foo (x : a, y : word) -> b; +trait Foo { + function foo (x : a, y : word) returns (b); } -instance () : Foo (()) { - function foo (x : (), y : word) -> () { - return (); +impl Foo<(), ()> { + function foo (x : (), y : word) returns (()) { + return; } } -forall a . instance a : Foo (()) { - function foo (x : a, y : word) -> () { - return (); +impl Foo { + function foo (x : a, y : word) returns (()) { + return; } } diff --git a/test/examples/cases/pair-bug.solc b/test/examples/cases/pair-bug.solc index 3006338ff..59911b973 100644 --- a/test/examples/cases/pair-bug.solc +++ b/test/examples/cases/pair-bug.solc @@ -1,9 +1,9 @@ -import std.{*}; +import {*} from std; contract TupleRet { constructor() {} - function pair() -> (uint256, uint256) { + function pair() returns ((uint256, uint256)) { return (uint256(7), uint256(11)); } } diff --git a/test/examples/cases/pars.solc b/test/examples/cases/pars.solc index d25d89f64..4fc22101a 100644 --- a/test/examples/cases/pars.solc +++ b/test/examples/cases/pars.solc @@ -1,3 +1,3 @@ contract Pars { - public function main() -> (){ let f:word; 42:word; (); } + function main() public returns (()){ let f:word; 42 as word; (); } } diff --git a/test/examples/cases/patterson-bug.solc b/test/examples/cases/patterson-bug.solc index 4e636df6f..8407a27f5 100644 --- a/test/examples/cases/patterson-bug.solc +++ b/test/examples/cases/patterson-bug.solc @@ -1,29 +1,29 @@ -data storage(a) = storage(word); -data storageRef(a) = storageRef(word); -data Proxy(a) = Proxy; +enum storage { storage(word) } +enum storageRef { storageRef(word) } +enum Proxy { Proxy } -data mapping(member, index) = mapping(word, Proxy(member), Proxy(index)); // storage by default +enum mapping { mapping(word, Proxy, Proxy) } // storage by default // data mapRef(a) = mapRef(word); //ref to a map elem -forall lhs rhs . class lhs:Assign(rhs) { - function assign(l:lhs, r:rhs) -> (); +trait Assign { + function assign(l:lhs, r:rhs) returns (()); } -forall a . instance storageRef(a):Assign(a) { - function assign(l:storageRef(a), y:a) -> () { +impl Assign, a> { + function assign(l:storageRef, y:a) returns (()) { } } -forall self fieldType offsetType . class self:CStructField(fieldType, offsetType) {} -data StructField(structType, fieldSelector) = StructField(structType); +trait CStructField {} +enum StructField { StructField(structType) } -data MemberAccessProxy(a, field, offset) = MemberAccessProxy(a, field); +enum MemberAccessProxy { MemberAccessProxy(a, field) } -forall self memberRefType . class self:LValueMemberAccess(memberRefType) { - function memberAccess(x:self) -> memberRefType; +trait LValueMemberAccess { + function memberAccess(x:self) returns (memberRefType); } // ------------------------------------------------------------------ @@ -31,10 +31,8 @@ forall self memberRefType . class self:LValueMemberAccess(memberRefType) { // ------------------------------------------------------------------ -forall cxt fieldSelector fieldType offsetType - . StructField(cxt, fieldSelector):CStructField(fieldType, offsetType) - => instance MemberAccessProxy(cxt, fieldSelector, offsetType):LValueMemberAccess(storageRef(fieldType)) { - function memberAccess(x:MemberAccessProxy(cxt, fieldSelector, offsetType)) -> storageRef(fieldType) { +impl LValueMemberAccess, storageRef> where StructField: CStructField { + function memberAccess(x:MemberAccessProxy) returns (storageRef) { return storageRef(0x100); } } @@ -43,20 +41,19 @@ forall cxt fieldSelector fieldType offsetType // Indexed access // ------------------------------------------------------------------ -data mapping(index, member) = mapping(word); -data IndexAccessProxy(map, index, member) = IndexAccessProxy(map, index); -data IndexAccessProxy2(map, index, member) = IndexAccessProxy2(map, index, Proxy(member)); +enum mapping { mapping(word) } +enum IndexAccessProxy { IndexAccessProxy(map, index) } +enum IndexAccessProxy2 { IndexAccessProxy2(map, index, Proxy) } -forall map index member. - instance IndexAccessProxy(storageRef(mapping(index,member)), index, member):LValueMemberAccess(storageRef(member)) { - function memberAccess(x:IndexAccessProxy(storageRef(map), index, member)) -> storageRef(member) { +impl LValueMemberAccess member)>, index, member>, storageRef> { + function memberAccess(x:IndexAccessProxy, index, member>) returns (storageRef) { return storageRef(0); } } -data MintCtx = MintCtx; -data balances_sel = balances_sel; -instance StructField(MintCtx, balances_sel):CStructField(mapping(word,word), ()) {} +enum MintCtx { MintCtx } +enum balances_sel { balances_sel } +impl CStructField, mapping(word => word), ()> {} function mint(amount:word) { let bal_prx = MemberAccessProxy(MintCtx, balances_sel); diff --git a/test/examples/cases/payable-toplevel-function.solc b/test/examples/cases/payable-toplevel-function.solc index 18f778fcf..c9fbc3ef7 100644 --- a/test/examples/cases/payable-toplevel-function.solc +++ b/test/examples/cases/payable-toplevel-function.solc @@ -1,5 +1,5 @@ // `payable` is only valid on a function/fallback inside a contract, // never on a top-level function. This must fail to parse. -payable function deposit() -> uint256 { +function deposit() payable returns (uint256) { return 0; } diff --git a/test/examples/cases/phantom-type-return-con.solc b/test/examples/cases/phantom-type-return-con.solc index 87ae13647..c97040d5a 100644 --- a/test/examples/cases/phantom-type-return-con.solc +++ b/test/examples/cases/phantom-type-return-con.solc @@ -1,16 +1,16 @@ -data Foo(a) = Foo(word); - forall a . function wrap(x : word) -> Foo(a) { +enum Foo { Foo(word) } + function wrap(x : word) returns (Foo) { return Foo(x); } - function unwrap() -> word { + function unwrap() returns (word) { match(wrap(42)) { - | Foo(w) => return w; - } + case Foo(w) { return w; + } } } contract C { - public function main() -> word { + function main() public returns (word) { return unwrap(); } } diff --git a/test/examples/cases/polymatch-error.solc b/test/examples/cases/polymatch-error.solc index 96462fd73..550ef6cb1 100644 --- a/test/examples/cases/polymatch-error.solc +++ b/test/examples/cases/polymatch-error.solc @@ -1,12 +1,12 @@ -forall a b . function fst(p: (a, b)) -> a { - match p { - | (a, _) => return a; - } +function fst(p: (a, b)) returns (a) { + match (p ) { + case (a, _) { return a; + } } } contract TestUnitMatch { - public function main() -> () { - match ((), ()) { - | x => return fst(x); - } + function main() public returns (()) { + match (((), ())) { + case x { return fst(x); + } } } } diff --git a/test/examples/cases/polymorphic-require.solc b/test/examples/cases/polymorphic-require.solc index dbab329e2..8b3e535ef 100644 --- a/test/examples/cases/polymorphic-require.solc +++ b/test/examples/cases/polymorphic-require.solc @@ -1,10 +1,9 @@ // This should trigger a warning and an error in the specialiser // due to unability to resolve result type of require -import std.{uint256,lt,not,Eq,ne,Proxy,bytes4,string}; -import std.dispatch.{*}; +import {uint256,lt,not,Eq,ne,Proxy,bytes4,string} from std; +import {*} from std.dispatch; -forall a. -function require(cond: bool) -> a { +function require(cond: bool) returns (a) { if (!cond) { assembly { revert(0, 0) @@ -12,7 +11,7 @@ function require(cond: bool) -> a { } } -function callvalue() -> uint256 { +function callvalue() returns (uint256) { let res : word; assembly { res := callvalue() @@ -21,12 +20,12 @@ function callvalue() -> uint256 { } contract Deposit { -public function deposit() -> () { +function deposit() public returns (()) { require(callvalue() != uint256(0)); - return (); + return; } -public function main() -> () { +function main() public returns (()) { deposit(); } } \ No newline at end of file diff --git a/test/examples/cases/pragma_merge_base.solc b/test/examples/cases/pragma_merge_base.solc index 98d66cfb9..5325e196c 100644 --- a/test/examples/cases/pragma_merge_base.solc +++ b/test/examples/cases/pragma_merge_base.solc @@ -2,46 +2,46 @@ // This file contains violations of all three condition types with pragmas to disable checks // Pragmas to disable checks for specific classes -pragma no-patterson-condition TestClassP1, TestClassB1, TestClassP3, TestClassB3; -pragma no-coverage-condition TestClassC1, TestClassP3; -pragma no-bounded-variable-condition TestClassB1, TestClassB3; +pragma solcore noPattersonCondition TestClassP1, TestClassB1, TestClassP3, TestClassB3; +pragma solcore noCoverageCondition TestClassC1, TestClassP3; +pragma solcore noBoundVariableCondition TestClassB1, TestClassB3; // --- Test Classes --- -forall a . class a:TestClassP1 {} -forall a . class a:TestClassP2 {} -forall a b . class a:TestClassP3(b) {} +trait TestClassP1 {} +trait TestClassP2 {} +trait TestClassP3 {} -forall a b . class a:TestClassC1(b) {} -forall a b c . class a:TestClassC2(b,c) {} +trait TestClassC1 {} +trait TestClassC2 {} -forall a b . class a:TestClassB1(b) {} -forall a b . class a:TestClassB2(b) {} -forall a . class a:TestClassB3 {} +trait TestClassB1 {} +trait TestClassB2 {} +trait TestClassB3 {} // --- Data Types --- -data TestType1(x) = TestType1; -data TestType2 = TestType2; +enum TestType1 { TestType1 } +enum TestType2 { TestType2 } // Fails Patterson: context constraint not smaller then head -forall U . (U,word):TestClassP1 => instance U:TestClassP1 {} +impl TestClassP1 where (U, word): TestClassP1 {} // Patterson OK: No context predicates -instance TestType2:TestClassP2 {} +impl TestClassP2 {} // --- Coverage Condition --- // Fails Coverage: Variable 'a' only appears in weak position (parameter to TestClassC1) -forall a b . instance TestType1(b):TestClassC1(a) {} +impl TestClassC1, a> {} // Coverage OK: All variables in strong positions -instance TestType2:TestClassC2(TestType2, TestType2) {} +impl TestClassC2 {} // === Bound Variable Violations === // Fails Bound Variable & Patterson: Variable 'c' appears in context but not in instance head -forall a c . c:TestClassB2(a) => instance TestType1(a):TestClassB1(a) {} +impl TestClassB1, a> where c: TestClassB2 {} // Bound Variable OK: Simple instance without context -instance TestType1(TestType2):TestClassB2(TestType2) {} +impl TestClassB2, TestType2> {} diff --git a/test/examples/cases/pragma_merge_fail_coverage.solc b/test/examples/cases/pragma_merge_fail_coverage.solc index 2576051ff..24d78ee14 100644 --- a/test/examples/cases/pragma_merge_fail_coverage.solc +++ b/test/examples/cases/pragma_merge_fail_coverage.solc @@ -1,10 +1,10 @@ // Negative test for pragma merging - should fail import pragma_merge_base; -forall a . class a:TestFailClass {} +trait TestFailClass {} -data FailType(x) = FailType; +enum FailType { FailType } // should fail because TestFailCoverage doesn't have no-coverage-condition -forall a b . class a:TestFailCoverage(b) {} -forall x y . instance FailType(x):TestFailCoverage(y) {} +trait TestFailCoverage {} +impl TestFailCoverage, y> {} diff --git a/test/examples/cases/pragma_merge_fail_patterson.solc b/test/examples/cases/pragma_merge_fail_patterson.solc index 896372908..0187b84a7 100644 --- a/test/examples/cases/pragma_merge_fail_patterson.solc +++ b/test/examples/cases/pragma_merge_fail_patterson.solc @@ -5,7 +5,7 @@ import pragma_merge_base; // --- Patterson Violation --- -forall a . class a:TestFailClass {} +trait TestFailClass {} // Should fail because TestFailClass doesn't have no-patterson-condition -forall U . U:TestClassP1, U:TestClassP2, U:TestClassP3 => instance U:TestFailClass {} +impl TestFailClass where U: TestClassP1, U: TestClassP2, U: TestClassP3 {} diff --git a/test/examples/cases/pragma_merge_import.solc b/test/examples/cases/pragma_merge_import.solc index 418fb766f..44e7f726e 100644 --- a/test/examples/cases/pragma_merge_import.solc +++ b/test/examples/cases/pragma_merge_import.solc @@ -6,17 +6,17 @@ import pragma_merge_base; // Add more pragmas - these should merge with imported ones -forall a b . class a:TestClassC3(b) {} -forall a . class a:TestClassB4 {} +trait TestClassC3 {} +trait TestClassB4 {} // fails coverage & patterson (pragma set here) -forall i j . (i,j):TestClassP1 => instance i:TestClassC3(j) {} +impl TestClassC3 where (i, j): TestClassP1 {} // fails coverage & patterson (pragma set in base) -forall i j . (i,j):TestClassP1 => instance i:TestClassP3(j) {} +impl TestClassP3 where (i, j): TestClassP1 {} // fails bound var & patterson (pragma set here) -forall a c . c:TestClassB1(a) => instance TestType1(a):TestClassB4 {} +impl TestClassB4> where c: TestClassB1 {} // fails bound var & patterson (pragma set in base) -forall a c . c:TestClassB1(a) => instance TestType1(a):TestClassB3 {} +impl TestClassB3> where c: TestClassB1 {} diff --git a/test/examples/cases/pragma_merge_verify.solc b/test/examples/cases/pragma_merge_verify.solc index 123f9b51d..05d1787ac 100644 --- a/test/examples/cases/pragma_merge_verify.solc +++ b/test/examples/cases/pragma_merge_verify.solc @@ -4,10 +4,10 @@ import pragma_merge_base; -data VerifyType(x) = VerifyType; +enum VerifyType { VerifyType } // Would fail without imported pragma no-patterson-condition TestClassP3 -forall a . (a,word):TestClassP3(a) => instance a:TestClassP3(word) {} +impl TestClassP3 where (a, word): TestClassP3 {} // Would fail without imported pragma no-coverage-condition TestClassC1 -forall p q . instance VerifyType(p):TestClassC1(q) {} +impl TestClassC1, q> {} diff --git a/test/examples/cases/pragma_test_patterson.solc b/test/examples/cases/pragma_test_patterson.solc index fd7be0edb..91062406a 100644 --- a/test/examples/cases/pragma_test_patterson.solc +++ b/test/examples/cases/pragma_test_patterson.solc @@ -1,9 +1,9 @@ // Simple Patterson test - should fail without pragma -forall a . class a:C1 {} -forall a . class a:C2 {} +trait C1 {} +trait C2 {} -data T(x) = T; +enum T { T } // This violates Patterson: context measure (2) >= conclusion measure (2) -forall U . U:C1, U:C2 => instance T(U):C1 {} \ No newline at end of file +impl C1> where U: C1, U: C2 {} \ No newline at end of file diff --git a/test/examples/cases/proxy-desugar.solc b/test/examples/cases/proxy-desugar.solc index 82be4deac..91ee05e48 100644 --- a/test/examples/cases/proxy-desugar.solc +++ b/test/examples/cases/proxy-desugar.solc @@ -1,12 +1,12 @@ -import std.{*}; -pragma no-patterson-condition; -pragma no-coverage-condition; -pragma no-bounded-variable-condition; +import {*} from std; +pragma solcore noPattersonCondition; +pragma solcore noCoverageCondition; +pragma solcore noBoundVariableCondition; -function foo(x : @word) -> word { +function foo(x : Proxy) returns (word) { return 0; } -function fuz(y : word) -> word { - return y + foo(@word); +function fuz(y : word) returns (word) { + return y + foo(Proxy as Proxy); } diff --git a/test/examples/cases/proxy.solc b/test/examples/cases/proxy.solc index a3e304246..584a1ba67 100644 --- a/test/examples/cases/proxy.solc +++ b/test/examples/cases/proxy.solc @@ -1,11 +1,10 @@ -data Proxy(a) = Proxy; +enum Proxy { Proxy } -forall self . class self:BaseMemoryType { - function memorySize(x:Proxy(self)) -> word; +trait BaseMemoryType { + function memorySize(x:Proxy) returns (word); } -forall t . t : BaseMemoryType => -function morefun(p:Proxy(t)) -> word { - return BaseMemoryType.memorySize(Proxy:Proxy(t)); +function morefun(p:Proxy) returns (word) where t: BaseMemoryType { + return BaseMemoryType.memorySize(Proxy as Proxy); } diff --git a/test/examples/cases/proxy1.solc b/test/examples/cases/proxy1.solc index 34da29bec..f1fd1d2d1 100644 --- a/test/examples/cases/proxy1.solc +++ b/test/examples/cases/proxy1.solc @@ -1,9 +1,9 @@ -data Proxy(a) = Proxy; +enum Proxy { Proxy } -forall a. class a:C { - function fun(p:Proxy(a)) -> word; +trait C { + function fun(p:Proxy) returns (word); } -forall t. function morefun(p:Proxy(t)) -> word { - return C.fun(Proxy:Proxy(t)); +function morefun(p:Proxy) returns (word) { + return C.fun(Proxy as Proxy); } diff --git a/test/examples/cases/public-constructor.solc b/test/examples/cases/public-constructor.solc index 99728d164..c594c9a1e 100644 --- a/test/examples/cases/public-constructor.solc +++ b/test/examples/cases/public-constructor.solc @@ -1,10 +1,10 @@ -import std.{*}; -import std.dispatch.{*}; +import {*} from std; +import {*} from std.dispatch; contract PublicConstructor { - public constructor() {} + constructor() public {} - public function answer() -> uint256 { + function answer() public returns (uint256) { return uint256(42); } } diff --git a/test/examples/cases/public-fallback.solc b/test/examples/cases/public-fallback.solc index a4a378214..d7649a1ff 100644 --- a/test/examples/cases/public-fallback.solc +++ b/test/examples/cases/public-fallback.solc @@ -1,10 +1,10 @@ -import std.{*}; -import std.dispatch.{*}; +import {*} from std; +import {*} from std.dispatch; contract PublicFallback { constructor() {} - public fallback() -> () { + fallback() external public { revert("fallback-was-called"); } } diff --git a/test/examples/cases/public-top-level-function.solc b/test/examples/cases/public-top-level-function.solc index 4e553ad18..9ed8d7c7e 100644 --- a/test/examples/cases/public-top-level-function.solc +++ b/test/examples/cases/public-top-level-function.solc @@ -1,8 +1,8 @@ -import std.{*}; -import std.dispatch.{*}; +import {*} from std; +import {*} from std.dispatch; // `public` is a contract-function visibility modifier. Applying it to a // top-level function (outside any `contract { … }` body) must be rejected. -public function answer() -> uint256 { +function answer() public returns (uint256) { return uint256(42); } diff --git a/test/examples/cases/rec.solc b/test/examples/cases/rec.solc index 53aec28d0..54e57d48b 100644 --- a/test/examples/cases/rec.solc +++ b/test/examples/cases/rec.solc @@ -1,6 +1,6 @@ -function rec (n : word, b : word, f : word) -> word { - match n { - | 0 => return b; - | m => return f(primAddWord(m,1), rec(m, b, f)); - } +function rec (n : word, b : word, f : word) returns (word) { + match (n ) { + case 0 { return b; + } case m { return f(primAddWord(m,1), rec(m, b, f)); + } } } diff --git a/test/examples/cases/redundant-match.solc b/test/examples/cases/redundant-match.solc index f7913c2b2..945805e0c 100644 --- a/test/examples/cases/redundant-match.solc +++ b/test/examples/cases/redundant-match.solc @@ -1,13 +1,13 @@ -data Bool = False | True; +enum Bool { False, True } - function f(x : Bool) -> Bool { - match x { - | z => return z; - | Bool.True => return Bool.True; - | Bool.False => return Bool.False; - } + function f(x : Bool) returns (Bool) { + match (x ) { + case z { return z; + } case Bool.True { return Bool.True; + } case Bool.False { return Bool.False; + } } } contract Test { - public function main() -> Bool { f(Bool.True) } + function main() public returns (Bool) { return f(Bool.True); } } diff --git a/test/examples/cases/reference-encoding-good.solc b/test/examples/cases/reference-encoding-good.solc index c7c7dd10e..742781b98 100644 --- a/test/examples/cases/reference-encoding-good.solc +++ b/test/examples/cases/reference-encoding-good.solc @@ -1,135 +1,134 @@ /////// Construction -forall abs rep . class abs:Typedef(rep) { - function rep(x:abs) -> rep; - function abs(x:rep) -> abs; +trait Typedef { + function rep(x:abs) returns (rep); + function abs(x:rep) returns (abs); } -instance word:Typedef(word) { - function rep(x:word) -> word { return x; } - function abs(x:word) -> word { return x; } +impl Typedef { + function rep(x:word) returns (word) { return x; } + function abs(x:word) returns (word) { return x; } } -data uint = uint(word); +enum uint { uint(word) } -instance uint:Typedef(word) { - function rep(x:uint) -> word { - match x { - | uint(y) => return y; - } +impl Typedef { + function rep(x:uint) returns (word) { + match (x ) { + case uint(y) { return y; + } } } - function abs(x:word) -> uint { + function abs(x:word) returns (uint) { return uint(x); } } -data memory(a) = memory(word); -data memoryRef(a) = memoryRef(word); -data Proxy(a) = Proxy; +enum memory { memory(word) } +enum memoryRef { memoryRef(word) } +enum Proxy { Proxy } -forall a . instance memory(a):Typedef(word) { - function rep(x:memory(a)) -> word { - match x { - | memory(y) => return y; - } +impl Typedef { + function rep(x:a memory) returns (word) { + match (x ) { + case memory(y) { return y; + } } } - function abs(x:word) -> memory(a) { + function abs(x:word) returns (a memory) { return memory(x); } } -forall a . instance memoryRef(a):Typedef(word) { - function rep(x:memoryRef(a)) -> word { - match x { - | memoryRef(y) => return y; - } +impl Typedef, word> { + function rep(x:memoryRef) returns (word) { + match (x ) { + case memoryRef(y) { return y; + } } } - function abs(x:word) -> memoryRef(a) { + function abs(x:word) returns (memoryRef) { return memoryRef(x); } } -forall lhs rhs . class lhs:Assign(rhs) { - function assign(l:lhs, r:rhs) -> (); +trait Assign { + function assign(l:lhs, r:rhs) returns (()); } -data ref(a) = ref(a); +enum ref { ref(a) } -forall a . instance ref(a):Assign(a) { - function assign(l:ref(a), r:a) -> () { +impl Assign, a> { + function assign(l:ref, r:a) returns (()) { // builtin "stack store" - return (); + return; } } -forall self . class self:MemoryType { - function load(ptr:word) -> self; - function store(ptr:word, value:self) -> (); +trait MemoryType { + function load(ptr:word) returns (self); + function store(ptr:word, value:self) returns (()); } -forall self . class self:MemorySize { - function size(x:Proxy(self)) -> word; +trait MemorySize { + function size(x:Proxy) returns (word); } -instance word:MemoryType { - function load(ptr:word) -> word { +impl MemoryType { + function load(ptr:word) returns (word) { let r:word; assembly { r := mload(ptr) } return r; } - function store(ptr:word, value:word) -> () { + function store(ptr:word, value:word) returns (()) { assembly { mstore(ptr, value) } } } -instance uint:MemoryType { - function load(ptr:word) -> uint { +impl MemoryType { + function load(ptr:word) returns (uint) { return Typedef.abs(MemoryType.load(ptr)); } - function store(ptr:word, value:uint) -> () { + function store(ptr:word, value:uint) returns (()) { return MemoryType.store(ptr, Typedef.rep(value)); } } -forall a . a : MemoryType => instance memoryRef(a):Assign(a) { - function assign(l:memoryRef(a), y:a) -> () { +impl Assign, a> where a: MemoryType { + function assign(l:memoryRef, y:a) returns (()) { MemoryType.store(Typedef.rep(l), y); } } -data MemberAccessProxy(a, field) = MemberAccessProxy(a, field); +enum MemberAccessProxy { MemberAccessProxy(a, field) } -forall a field . -function memberAccessD1(x:MemberAccessProxy(a, field)) -> a { - match x { - | MemberAccessProxy(y,z) => return y; - } +function memberAccessD1(x:MemberAccessProxy) returns (a) { + match (x ) { + case MemberAccessProxy(y,z) { return y; + } } } -forall self memberRefType . class self:LValueMemberAccess(memberRefType) { - function memberAccess(x:self) -> memberRefType; +trait LValueMemberAccess { + function memberAccess(x:self) returns (memberRefType); } -forall self memberValueType . class self:RValueMemberAccess(memberValueType) { - function memberAccess(x:self) -> memberValueType; +trait RValueMemberAccess { + function memberAccess(x:self) returns (memberValueType); } // This is *a lot* of pragmas... -pragma no-coverage-condition CStructField, LValueMemberAccess, RValueMemberAccess; -pragma no-patterson-condition LValueMemberAccess, RValueMemberAccess; -pragma no-bounded-variable-condition LValueMemberAccess, RValueMemberAccess; -forall self fieldType offsetType . class self:CStructField(fieldType, offsetType) {} -data StructField(structType, fieldSelector) = StructField(structType); - -forall structType fieldSelector fieldType offsetType . StructField(structType, fieldSelector):CStructField(fieldType, offsetType), offsetType:MemorySize => instance MemberAccessProxy(memory(structType), fieldSelector):LValueMemberAccess(memoryRef(fieldType)) { - function memberAccess(x:MemberAccessProxy(memory(structType), fieldSelector)) -> memoryRef(fieldType) { +pragma solcore noCoverageCondition CStructField, LValueMemberAccess, RValueMemberAccess; +pragma solcore noPattersonCondition LValueMemberAccess, RValueMemberAccess; +pragma solcore noBoundVariableCondition LValueMemberAccess, RValueMemberAccess; +trait CStructField {} +enum StructField { StructField(structType) } + +impl LValueMemberAccess, memoryRef> where StructField: CStructField, offsetType: MemorySize { + function memberAccess(x:MemberAccessProxy) returns (memoryRef) { let ptr:word = Typedef.rep(memberAccessD1(x)); - let size:word = MemorySize.size(Proxy:Proxy(offsetType)); + let size:word = MemorySize.size(Proxy as Proxy); assembly { ptr := add(ptr, size) } @@ -137,29 +136,29 @@ forall structType fieldSelector fieldType offsetType . StructField(structType, f } } -instance ():MemorySize { - function size(x:Proxy(())) -> word { +impl MemorySize<()> { + function size(x:Proxy<()>) returns (word) { return 0; } } -instance word:MemorySize { - function size(x:Proxy(word)) -> word { +impl MemorySize { + function size(x:Proxy) returns (word) { return 32; } } -instance uint:MemorySize { - function size(x:Proxy(uint)) -> word { +impl MemorySize { + function size(x:Proxy) returns (word) { return 32; } } -forall a b . a:MemorySize, b:MemorySize => instance (a,b):MemorySize { - function size(x:Proxy((a,b))) -> word { - let a_sz:word = MemorySize.size(Proxy:Proxy(a)); - let b_sz:word = MemorySize.size(Proxy:Proxy(b)); +impl MemorySize<(a, b)> where a: MemorySize, b: MemorySize { + function size(x:Proxy<(a, b)>) returns (word) { + let a_sz:word = MemorySize.size(Proxy as Proxy); + let b_sz:word = MemorySize.size(Proxy as Proxy); assembly { a_sz := add(a_sz, b_sz) } @@ -167,38 +166,38 @@ forall a b . a:MemorySize, b:MemorySize => instance (a,b):MemorySize { } } -forall structType fieldSelector fieldType offsetType . StructField(structType, fieldSelector):CStructField(fieldType, offsetType), fieldType:MemoryType, offsetType:MemorySize => instance MemberAccessProxy(memory(structType), fieldSelector):RValueMemberAccess(fieldType) { - function memberAccess(x:MemberAccessProxy(memory(structType), fieldSelector)) -> fieldType { +impl RValueMemberAccess, fieldType> where StructField: CStructField, fieldType: MemoryType, offsetType: MemorySize { + function memberAccess(x:MemberAccessProxy) returns (fieldType) { let ptr:word = Typedef.rep(memberAccessD1(x)); - let size:word = MemorySize.size(Proxy:Proxy(offsetType)); + let size:word = MemorySize.size(Proxy as Proxy); // BUG: Something wrong here? Complains about ptr not being word... assembly { ptr := add(ptr, size) } - return MemoryType.load(Typedef.abs(ptr)):fieldType; + return MemoryType.load(Typedef.abs(ptr)) as fieldType; } } ////// Testing // struct S { x:word; y:uint; z:word; } -data S = S(word, uint, word); -data x_sel = x_sel; -data y_sel = y_sel; -data z_sel = z_sel; +enum S { S(word, uint, word) } +enum x_sel { x_sel } +enum y_sel { y_sel } +enum z_sel { z_sel } -instance StructField(S, x_sel):CStructField(word, ()) {} -instance StructField(S, y_sel):CStructField(uint, word) {} +impl CStructField, word, ()> {} +impl CStructField, uint, word> {} // BUG: This next one should really be the following, but that breaks weirdly: // (I get a patterson condition violation on an invoke instance for g) // instance StructField(S, z_sel):CStructField(word, (word,uint)) {} // So instead I use: -instance StructField(S, z_sel):CStructField(word, word) {} +impl CStructField, word, word> {} -function f() -> () { - let x:memory(word); - let y:memory(word); +function f() returns (()) { + let x:word memory; + let y:word memory; // x = y Assign.assign(ref(x), y); /* @@ -211,8 +210,8 @@ function f() -> () { */ } -function g() -> () { - let s:memory(S) = Typedef.abs(0x80); +function g() returns (()) { + let s:S memory = Typedef.abs(0x80); let y:word = 42; let z:uint = uint(42); // s.x = y @@ -227,7 +226,7 @@ function g() -> () { Assign.assign(LValueMemberAccess.memberAccess(MemberAccessProxy(s, z_sel)), RValueMemberAccess.memberAccess(MemberAccessProxy(s, x_sel))); } contract C { - public function main() -> () { + function main() public returns (()) { f(); g(); } diff --git a/test/examples/cases/reference-encoding-good1.solc b/test/examples/cases/reference-encoding-good1.solc index 42f1d4af2..99df61ceb 100644 --- a/test/examples/cases/reference-encoding-good1.solc +++ b/test/examples/cases/reference-encoding-good1.solc @@ -1,136 +1,135 @@ /////// Construction -forall abs rep . class abs:Typedef(rep) { - function rep(x:abs) -> rep; - function abs(x:rep) -> abs; +trait Typedef { + function rep(x:abs) returns (rep); + function abs(x:rep) returns (abs); } -data uint = uint(word); +enum uint { uint(word) } -instance uint:Typedef(word) { - function rep(x:uint) -> word { - match x { - | uint(y) => return y; - } +impl Typedef { + function rep(x:uint) returns (word) { + match (x ) { + case uint(y) { return y; + } } } - function abs(x:word) -> uint { + function abs(x:word) returns (uint) { return uint(x); } } -data memory(a) = memory(word); -data memoryRef(a) = memoryRef(word); -data Proxy(a) = Proxy; +enum memory { memory(word) } +enum memoryRef { memoryRef(word) } +enum Proxy { Proxy } -forall a . instance memory(a):Typedef(word) { - function rep(x:memory(a)) -> word { - match x { - | memory(y) => return y; - } +impl Typedef { + function rep(x:a memory) returns (word) { + match (x ) { + case memory(y) { return y; + } } } - function abs(x:word) -> memory(a) { + function abs(x:word) returns (a memory) { return memory(x); } } -forall a . instance memoryRef(a):Typedef(word) { - function rep(x:memoryRef(a)) -> word { - match x { - | memoryRef(y) => return y; - } +impl Typedef, word> { + function rep(x:memoryRef) returns (word) { + match (x ) { + case memoryRef(y) { return y; + } } } - function abs(x:word) -> memoryRef(a) { + function abs(x:word) returns (memoryRef) { return memoryRef(x); } } -forall lhs rhs . class lhs:Assign(rhs) { - function assign(l:lhs, r:rhs) -> (); +trait Assign { + function assign(l:lhs, r:rhs) returns (()); } -data ref(a) = ref(a); +enum ref { ref(a) } -forall a . instance ref(a):Assign(a) { - function assign(l:ref(a), r:a) -> () { +impl Assign, a> { + function assign(l:ref, r:a) returns (()) { // builtin "stack store" - return (); + return; } } -forall self . class self:MemoryType { - function load(ptr:word) -> self; - function store(ptr:word, value:self) -> (); +trait MemoryType { + function load(ptr:word) returns (self); + function store(ptr:word, value:self) returns (()); } -forall self . class self:MemorySize { - function size(x:Proxy(self)) -> word; +trait MemorySize { + function size(x:Proxy) returns (word); } -instance word:MemoryType { - function load(ptr:word) -> word { +impl MemoryType { + function load(ptr:word) returns (word) { let r:word; assembly { r := mload(ptr) } return r; } - function store(ptr:word, value:word) -> () { + function store(ptr:word, value:word) returns (()) { assembly { mstore(ptr, value) } } } -instance uint:MemoryType { - function load(ptr:word) -> uint { +impl MemoryType { + function load(ptr:word) returns (uint) { return Typedef.abs(MemoryType.load(ptr)); } - function store(ptr:word, value:uint) -> () { + function store(ptr:word, value:uint) returns (()) { return MemoryType.store(ptr, Typedef.rep(value)); } } -forall a . a : MemoryType => instance memoryRef(a):Assign(a) { - function assign(l:memoryRef(a), y:a) -> () { +impl Assign, a> where a: MemoryType { + function assign(l:memoryRef, y:a) returns (()) { MemoryType.store(Typedef.rep(l), y); } } -instance word:Typedef(word) { - function rep(x:word) -> word { return x; } - function abs(x:word) -> word { return x; } +impl Typedef { + function rep(x:word) returns (word) { return x; } + function abs(x:word) returns (word) { return x; } } -data MemberAccessProxy(a, field) = MemberAccessProxy(a, field); +enum MemberAccessProxy { MemberAccessProxy(a, field) } -forall a field . -function memberAccessD1(x:MemberAccessProxy(a, field)) -> a { - match x { - | MemberAccessProxy(y,z) => return y; - } +function memberAccessD1(x:MemberAccessProxy) returns (a) { + match (x ) { + case MemberAccessProxy(y,z) { return y; + } } } -forall self memberRefType . class self:LValueMemberAccess(memberRefType) { - function memberAccess(x:self) -> memberRefType; +trait LValueMemberAccess { + function memberAccess(x:self) returns (memberRefType); } -forall self memberValueType . class self:RValueMemberAccess(memberValueType) { - function memberAccess(x:self) -> memberValueType; +trait RValueMemberAccess { + function memberAccess(x:self) returns (memberValueType); } // This is *a lot* of pragmas... -pragma no-coverage-condition CStructField, LValueMemberAccess, RValueMemberAccess; -pragma no-patterson-condition LValueMemberAccess, RValueMemberAccess; -pragma no-bounded-variable-condition LValueMemberAccess, RValueMemberAccess; -forall self fieldType offsetType . class self:CStructField(fieldType, offsetType) {} -data StructField(structType, fieldSelector) = StructField(structType); - -forall structType fieldSelector fieldType offsetType . StructField(structType, fieldSelector):CStructField(fieldType, offsetType), offsetType:MemorySize => instance MemberAccessProxy(memory(structType), fieldSelector):LValueMemberAccess(memoryRef(fieldType)) { - function memberAccess(x:MemberAccessProxy(memory(structType), fieldSelector)) -> memoryRef(fieldType) { +pragma solcore noCoverageCondition CStructField, LValueMemberAccess, RValueMemberAccess; +pragma solcore noPattersonCondition LValueMemberAccess, RValueMemberAccess; +pragma solcore noBoundVariableCondition LValueMemberAccess, RValueMemberAccess; +trait CStructField {} +enum StructField { StructField(structType) } + +impl LValueMemberAccess, memoryRef> where StructField: CStructField, offsetType: MemorySize { + function memberAccess(x:MemberAccessProxy) returns (memoryRef) { let ptr:word = Typedef.rep(memberAccessD1(x)); - let size:word = MemorySize.size(Proxy:Proxy(offsetType)); + let size:word = MemorySize.size(Proxy as Proxy); assembly { ptr := add(ptr, size) } @@ -138,29 +137,29 @@ forall structType fieldSelector fieldType offsetType . StructField(structType, f } } -instance ():MemorySize { - function size(x:Proxy(())) -> word { +impl MemorySize<()> { + function size(x:Proxy<()>) returns (word) { return 0; } } -instance word:MemorySize { - function size(x:Proxy(word)) -> word { +impl MemorySize { + function size(x:Proxy) returns (word) { return 32; } } -instance uint:MemorySize { - function size(x:Proxy(uint)) -> word { +impl MemorySize { + function size(x:Proxy) returns (word) { return 32; } } -forall a b . a:MemorySize, b:MemorySize => instance (a,b):MemorySize { - function size(x:Proxy((a,b))) -> word { - let a_sz:word = MemorySize.size(Proxy:Proxy(a)); - let b_sz:word = MemorySize.size(Proxy:Proxy(b)); +impl MemorySize<(a, b)> where a: MemorySize, b: MemorySize { + function size(x:Proxy<(a, b)>) returns (word) { + let a_sz:word = MemorySize.size(Proxy as Proxy); + let b_sz:word = MemorySize.size(Proxy as Proxy); assembly { a_sz := add(a_sz, b_sz) } @@ -168,38 +167,38 @@ forall a b . a:MemorySize, b:MemorySize => instance (a,b):MemorySize { } } -forall structType fieldSelector fieldType offsetType . StructField(structType, fieldSelector):CStructField(fieldType, offsetType), fieldType:MemoryType, offsetType:MemorySize => instance MemberAccessProxy(memory(structType), fieldSelector):RValueMemberAccess(fieldType) { - function memberAccess(x:MemberAccessProxy(memory(structType), fieldSelector)) -> fieldType { +impl RValueMemberAccess, fieldType> where StructField: CStructField, fieldType: MemoryType, offsetType: MemorySize { + function memberAccess(x:MemberAccessProxy) returns (fieldType) { let ptr:word = Typedef.rep(memberAccessD1(x)); - let size:word = MemorySize.size(Proxy:Proxy(offsetType)); + let size:word = MemorySize.size(Proxy as Proxy); // BUG: Something wrong here? Complains about ptr not being word... assembly { ptr := add(ptr, size) } - return MemoryType.load(Typedef.abs(ptr)):fieldType; + return MemoryType.load(Typedef.abs(ptr)) as fieldType; } } ////// Testing // struct S { x:word; y:uint; z:word; } -data S = S(word, uint, word); -data x_sel = x_sel; -data y_sel = y_sel; -data z_sel = z_sel; +enum S { S(word, uint, word) } +enum x_sel { x_sel } +enum y_sel { y_sel } +enum z_sel { z_sel } -instance StructField(S, x_sel):CStructField(word, ()) {} -instance StructField(S, y_sel):CStructField(uint, word) {} +impl CStructField, word, ()> {} +impl CStructField, uint, word> {} // BUG: This next one should really be the following, but that breaks weirdly: // (I get a patterson condition violation on an invoke instance for g) // instance StructField(S, z_sel):CStructField(word, (word,uint)) {} // So instead I use: -instance StructField(S, z_sel):CStructField(word, word) {} +impl CStructField, word, word> {} -function f() -> () { - let x:memory(word); - let y:memory(word); +function f() returns (()) { + let x:word memory; + let y:word memory; // x = y Assign.assign(ref(x), y); /* @@ -212,8 +211,8 @@ function f() -> () { */ } -function g() -> () { - let s:memory(S) = Typedef.abs(0x80); +function g() returns (()) { + let s:S memory = Typedef.abs(0x80); let y:word = 42; let z:uint = uint(42); // s.x = y @@ -228,7 +227,7 @@ function g() -> () { Assign.assign(LValueMemberAccess.memberAccess(MemberAccessProxy(s, z_sel)), RValueMemberAccess.memberAccess(MemberAccessProxy(s, x_sel))); } contract C { - public function main() -> () { + function main() public returns (()) { f(); g(); } diff --git a/test/examples/cases/reference-encoding.solc b/test/examples/cases/reference-encoding.solc index 93ae8736a..147823cd7 100644 --- a/test/examples/cases/reference-encoding.solc +++ b/test/examples/cases/reference-encoding.solc @@ -1,128 +1,127 @@ /////// Construction -class abs:Typedef(rep) { - function rep(x:abs) -> rep; - function abs(x:rep) -> abs; +trait Typedef { + function rep(x:abs) returns (rep); + function abs(x:rep) returns (abs); } -data uint = uint(word); +enum uint { uint(word) } -instance uint:Typedef(word) { - function rep(x:uint) -> word { - match x { - | uint(y) => return y; - } +impl Typedef { + function rep(x:uint) returns (word) { + match (x ) { + case uint(y) { return y; + } } } - function abs(x:word) -> uint { + function abs(x:word) returns (uint) { return uint(x); } } -data memory(a) = memory(word); -data memoryRef(a) = memoryRef(word); -data Proxy(a) = Proxy; +enum memory { memory(word) } +enum memoryRef { memoryRef(word) } +enum Proxy { Proxy } -instance memory(a):Typedef(word) { - function rep(x:memory(a)) -> word { - match x { - | memory(y) => return y; - } +impl Typedef { + function rep(x:a memory) returns (word) { + match (x ) { + case memory(y) { return y; + } } } - function abs(x:word) -> memory(a) { + function abs(x:word) returns (a memory) { return memory(x); } } -instance memoryRef(a):Typedef(word) { - function rep(x:memoryRef(a)) -> word { - match x { - | memoryRef(y) => return y; - } +impl Typedef, word> { + function rep(x:memoryRef) returns (word) { + match (x ) { + case memoryRef(y) { return y; + } } } - function abs(x:word) -> memoryRef(a) { + function abs(x:word) returns (memoryRef) { return memoryRef(x); } } -class lhs:Assign(rhs) { - function assign(l:lhs, r:rhs) -> (); +trait Assign { + function assign(l:lhs, r:rhs) returns (()); } -data ref(a) = ref(a); +enum ref { ref(a) } -instance ref(a):Assign(a) { - function assign(l:ref(a), r:a) -> () { +impl Assign, a> { + function assign(l:ref, r:a) returns (()) { // builtin "stack store" - return (); + return; } } -class self:MemoryType { - function load(ptr:word) -> self; - function store(ptr:word, value:self) -> (); +trait MemoryType { + function load(ptr:word) returns (self); + function store(ptr:word, value:self) returns (()); } -class self:MemorySize { - function size(x:Proxy(self)) -> word; +trait MemorySize { + function size(x:Proxy) returns (word); } -instance word:MemoryType { - function load(ptr:word) -> word { +impl MemoryType { + function load(ptr:word) returns (word) { let r:word; assembly { r := mload(ptr) } return r; } - function store(ptr:word, value:word) -> () { + function store(ptr:word, value:word) returns (()) { assembly { mstore(ptr, value) } } } -instance uint:MemoryType { - function load(ptr:word) -> uint { +impl MemoryType { + function load(ptr:word) returns (uint) { return Typedef.abs(MemoryType.load(ptr)); } - function store(ptr:word, value:uint) -> () { + function store(ptr:word, value:uint) returns (()) { return MemoryType.store(ptr, Typedef.rep(value)); } } -forall a . a : MemoryType => instance memoryRef(a):Assign(a) { - function assign(l:memoryRef(a), y:a) { +impl Assign, a> where a: MemoryType { + function assign(l:memoryRef, y:a) { MemoryType.store(Typedef.rep(l), y); } } -data MemberAccessProxy(a, field) = MemberAccessProxy(a, field); +enum MemberAccessProxy { MemberAccessProxy(a, field) } -forall a field . -function memberAccessD1(x:MemberAccessProxy(a, field)) -> a { - match x { - | MemberAccessProxy(y,z) => return y; - } +function memberAccessD1(x:MemberAccessProxy) returns (a) { + match (x ) { + case MemberAccessProxy(y,z) { return y; + } } } -class self:LValueMemberAccess(memberRefType) { - function memberAccess(x:self) -> memberRefType; +trait LValueMemberAccess { + function memberAccess(x:self) returns (memberRefType); } -class self:RValueMemberAccess(memberValueType) { - function memberAccess(x:self) -> memberValueType; +trait RValueMemberAccess { + function memberAccess(x:self) returns (memberValueType); } // This is *a lot* of pragmas... -class self:CStructField(fieldType, offsetType) {} -data StructField(structType, fieldSelector) = StructField(structType); +trait CStructField {} +enum StructField { StructField(structType) } -forall structType fieldSelector fieldType offsetType . StructField(structType, fieldSelector):CStructField(fieldType, offsetType), offsetType:MemorySize => instance MemberAccessProxy(memory(structType), fieldSelector):LValueMemberAccess(memoryRef(fieldType)) { - function memberAccess(x:MemberAccessProxy(memory(structType), fieldSelector)) -> memoryRef(fieldType) { +impl LValueMemberAccess, memoryRef> where StructField: CStructField, offsetType: MemorySize { + function memberAccess(x:MemberAccessProxy) returns (memoryRef) { let ptr:word = Typedef.rep(memberAccessD1(x)); - let size:word = MemorySize.size(Proxy:Proxy(offsetType)); + let size:word = MemorySize.size(Proxy as Proxy); assembly { ptr := add(ptr, size) } @@ -130,29 +129,29 @@ forall structType fieldSelector fieldType offsetType . StructField(structType, f } } -instance ():MemorySize { - function size(x:Proxy(())) -> word { +impl MemorySize<()> { + function size(x:Proxy<()>) returns (word) { return 0; } } -instance word:MemorySize { - function size(x:Proxy(word)) -> word { +impl MemorySize { + function size(x:Proxy) returns (word) { return 32; } } -instance uint:MemorySize { - function size(x:Proxy(uint)) -> word { +impl MemorySize { + function size(x:Proxy) returns (word) { return 32; } } -forall a b. a:MemorySize, b:MemorySize => instance (a,b):MemorySize { - function size(x:Proxy((a,b))) -> word { - let a_sz:word = MemorySize.size(Proxy:Proxy(a)); - let b_sz:word = MemorySize.size(Proxy:Proxy(b)); +impl MemorySize<(a, b)> where a: MemorySize, b: MemorySize { + function size(x:Proxy<(a, b)>) returns (word) { + let a_sz:word = MemorySize.size(Proxy as Proxy); + let b_sz:word = MemorySize.size(Proxy as Proxy); assembly { a_sz := add(a_sz, b_sz) } @@ -160,38 +159,38 @@ forall a b. a:MemorySize, b:MemorySize => instance (a,b):MemorySize { } } -forall structType fieldSelector fieldType offsetType . StructField(structType, fieldSelector):CStructField(fieldType, offsetType), fieldType:MemoryType, offsetType:MemorySize => instance MemberAccessProxy(memory(structType), fieldSelector):RValueMemberAccess(fieldType) { - function memberAccess(x:MemberAccessProxy(memory(structType), fieldSelector)) -> fieldType { +impl RValueMemberAccess, fieldType> where StructField: CStructField, fieldType: MemoryType, offsetType: MemorySize { + function memberAccess(x:MemberAccessProxy) returns (fieldType) { let ptr:word = Typedef.rep(memberAccessD1(x)); - let size:word = MemorySize.size(Proxy:Proxy(offsetType)); + let size:word = MemorySize.size(Proxy as Proxy); // BUG: Something wrong here? Complains about ptr not being word... /*assembly { ptr := add(ptr, size) }*/ - return MemoryType.load(Typedef.abs(ptr)):fieldType; + return MemoryType.load(Typedef.abs(ptr)) as fieldType; } } ////// Testing // struct S { x:word; y:uint; z:word; } -data S = S(word, uint, word); -data x_sel = x_sel; -data y_sel = y_sel; -data z_sel = z_sel; +enum S { S(word, uint, word) } +enum x_sel { x_sel } +enum y_sel { y_sel } +enum z_sel { z_sel } -instance StructField(S, x_sel):CStructField(word, ()) {} -instance StructField(S, y_sel):CStructField(uint, word) {} +impl CStructField, word, ()> {} +impl CStructField, uint, word> {} // BUG: This next one should really be the following, but that breaks weirdly: // (I get a patterson condition violation on an invoke instance for g) // instance StructField(S, z_sel):CStructField(word, (word,uint)) {} // So instead I use: -instance StructField(S, z_sel):CStructField(word, word) {} +impl CStructField, word, word> {} function f() { - let x:memory(word); - let y:memory(word); + let x:word memory; + let y:word memory; // x = y Assign.assign(ref(x), y); /* @@ -205,7 +204,7 @@ function f() { } function g() { - let s:memory(S) = Typedef.abs(0x80); + let s:S memory = Typedef.abs(0x80); let y:word = 42; let z:uint = uint(42); // s.x = y @@ -220,7 +219,7 @@ function g() { Assign.assign(LValueMemberAccess.memberAccess(MemberAccessProxy(s, z_sel)), RValueMemberAccess.memberAccess(MemberAccessProxy(s, x_sel))); } contract C { - public function main() { + function main() public { f(); g(); } diff --git a/test/examples/cases/reference-test.solc b/test/examples/cases/reference-test.solc index a7922668c..28bea7d49 100644 --- a/test/examples/cases/reference-test.solc +++ b/test/examples/cases/reference-test.solc @@ -1,54 +1,53 @@ -data memory(a) = memory(word); +enum memory { memory(word) } -class abs:Typedef(rep) { - function abs(v:rep) -> abs; - function rep(v:abs) -> rep; +trait Typedef { + function abs(v:rep) returns (abs); + function rep(v:abs) returns (rep); } -instance memory(a):Typedef(word) { - function abs(ptr:word) -> memory(a) { +impl Typedef { + function abs(ptr:word) returns (a memory) { return memory(ptr); } - function rep(v:memory(a)) -> word { - match v { - | memory(ptr) => return ptr; - } + function rep(v:a memory) returns (word) { + match (v ) { + case memory(ptr) { return ptr; + } } } } -class self:Test { - function test(x:self) -> word; +trait Test { + function test(x:self) returns (word); } -instance word:Test { - function test(x:word) -> word { +impl Test { + function test(x:word) returns (word) { return x; } } -data test(a) = test(memory(a)); +enum test { test(a memory) } -instance test(a):Typedef(memory(a)) { - function rep(x:test(a)) -> memory(a) { - match x { - | test(m) => return m; - } +impl Typedef, a memory> { + function rep(x:test) returns (a memory) { + match (x ) { + case test(m) { return m; + } } } - function abs(m:memory(a)) -> test(a) { + function abs(m:a memory) returns (test) { return test(m); } } -forall abs rep . test(abs):Typedef(rep), rep:Test => - instance test(abs):Test { - function test(x:test(abs)) -> word { +impl Test> where test: Typedef, rep: Test { + function test(x:test) returns (word) { return Test.test(Typedef.rep(x)); } } contract C { - public function main() { - let x:test(word) = test(memory(42)); + function main() public { + let x:test = test(memory(42)); let ptr:word = Test.test(x); } } diff --git a/test/examples/cases/reference.solc b/test/examples/cases/reference.solc index 23c8e63f5..f78ffdc42 100644 --- a/test/examples/cases/reference.solc +++ b/test/examples/cases/reference.solc @@ -1,26 +1,26 @@ -class ref : Ref(deref) { - function load (r:ref) -> deref; - function store(r:ref, v:deref) -> unit; +trait Ref { + function load (r:ref) returns (deref); + function store(r:ref, v:deref) returns (unit); } -data stack(a) = stack(a); +enum stack { stack(a) } -instance stack(a) : Ref(a) { +impl Ref, a> { } -data MemberAccess(ty, field) = MemberAccess(ty); +enum MemberAccess { MemberAccess(ty) } -data PairFst = PairFst; -data PairSnd = PairSnd; +enum PairFst { PairFst } +enum PairSnd { PairSnd } -data XRef(st, field, fieldType) = XRef(st, field); -forall r : Ref (a,b) . instance XRef(r, PairFst, a) : Ref(a) {} -forall r : Ref (a,b) . instance XRef(r, PairSnd, b) : Ref(b) {} +enum XRef { XRef(st, field) } +impl Ref, a> where r: Ref {} +impl Ref, b> where r: Ref {} contract AssignNested { - public function main() { - let x : stack( (word, (word, word)) ); - let z : stack( (word, (word, word)) ); + function main() public { + let x : stack<(word, (word, word))>; + let z : stack<(word, (word, word))>; // either of the next lines is fine on their own, but not together Ref.store( XRef(z,PairFst), 21); diff --git a/test/examples/cases/references-daniel.solc b/test/examples/cases/references-daniel.solc index 4260971e4..4ef6414eb 100644 --- a/test/examples/cases/references-daniel.solc +++ b/test/examples/cases/references-daniel.solc @@ -1,235 +1,229 @@ /////// Construction -class abs:Typedef(rep) { - function rep(x:abs) -> rep; - function abs(x:rep) -> abs; +trait Typedef { + function rep(x:abs) returns (rep); + function abs(x:rep) returns (abs); } -data xunit = xunit; +enum xunit { xunit } -data uint = uint(word); +enum uint { uint(word) } -instance uint:Typedef(word) { - function rep(x:uint) -> word { - match x { - | uint(y) => return y; - } +impl Typedef { + function rep(x:uint) returns (word) { + match (x ) { + case uint(y) { return y; + } } } - function abs(x:word) -> uint { + function abs(x:word) returns (uint) { return uint(x); } } -data memory(a) = memory(word); -data memoryRef(a) = memoryRef(word); -data Proxy(a) = Proxy; +enum memory { memory(word) } +enum memoryRef { memoryRef(word) } +enum Proxy { Proxy } -instance memory(a):Typedef(word) { - function rep(x:memory(a)) -> word { - match x { - | memory(y) => return y; - } +impl Typedef { + function rep(x:a memory) returns (word) { + match (x ) { + case memory(y) { return y; + } } } - function abs(x:word) -> memory(a) { + function abs(x:word) returns (a memory) { return memory(x); } } -instance memoryRef(a):Typedef(word) { - function rep(x:memoryRef(a)) -> word { - match x { - | memoryRef(y) => return y; - } +impl Typedef, word> { + function rep(x:memoryRef) returns (word) { + match (x ) { + case memoryRef(y) { return y; + } } } - function abs(x:word) -> memoryRef(a) { + function abs(x:word) returns (memoryRef) { return memoryRef(x); } } -class lhs:Assign(rhs) { - function assign(l:lhs, r:rhs) -> (); +trait Assign { + function assign(l:lhs, r:rhs) returns (()); } -data ref(a) = ref(a); +enum ref { ref(a) } -instance ref(a):Assign(a) { - function assign(l:ref(a), r:a) -> () { +impl Assign, a> { + function assign(l:ref, r:a) returns (()) { // builtin "stack store" - return (); + return; } } -class self:MemoryType { - function load(ptr:word) -> self; - function store(ptr:word, value:self) -> (); +trait MemoryType { + function load(ptr:word) returns (self); + function store(ptr:word, value:self) returns (()); } -class self:MemorySize { - function size(x:Proxy(self)) -> word; +trait MemorySize { + function size(x:Proxy) returns (word); } -instance word:MemoryType { - function load(ptr:word) -> word { +impl MemoryType { + function load(ptr:word) returns (word) { let r:word; assembly { r := mload(ptr) } return r; } - function store(ptr:word, value:word) -> () { + function store(ptr:word, value:word) returns (()) { assembly { mstore(ptr, value) } } } -instance uint:MemoryType { - function load(ptr:word) -> uint { +impl MemoryType { + function load(ptr:word) returns (uint) { return Typedef.abs(MemoryType.load(ptr)); } - function store(ptr:word, value:uint) -> () { + function store(ptr:word, value:uint) returns (()) { return MemoryType.store(ptr, Typedef.rep(value)); } } -forall a . a:MemoryType => -instance memoryRef(a):Assign(a) { - function assign(l:memoryRef(a), y:a) { +impl Assign, a> where a: MemoryType { + function assign(l:memoryRef, y:a) { MemoryType.store(Typedef.rep(l), y); } } -data MemberAccessProxy(a, field) = MemberAccessProxy(a, Proxy(field)); +enum MemberAccessProxy { MemberAccessProxy(a, Proxy) } -forall a field . -function memberAccessPtr(x:MemberAccessProxy(memory(a), field)) -> word { - match x { - | MemberAccessProxy(y,z) => match y { - | memory(ptr) => return ptr; - } - } +function memberAccessPtr(x:MemberAccessProxy) returns (word) { + match (x ) { + case MemberAccessProxy(y,z) { match (y ) { + case memory(ptr) { return ptr; + } } + } } } -class self:LValueMemberAccess(memberRefType) { - function memberAccess(x:self) -> memberRefType; +trait LValueMemberAccess { + function memberAccess(x:self) returns (memberRefType); } -class self:RValueMemberAccess(memberValueType) { - function memberAccess(x:self) -> memberValueType; +trait RValueMemberAccess { + function memberAccess(x:self) returns (memberValueType); } -instance xunit:MemorySize { - function size(x:Proxy(xunit)) -> word { +impl MemorySize { + function size(x:Proxy) returns (word) { return 0; } } -instance word:MemorySize { - function size(x:Proxy(word)) -> word { +impl MemorySize { + function size(x:Proxy) returns (word) { return 32; } } -instance uint:MemorySize { - function size(x:Proxy(uint)) -> word { +impl MemorySize { + function size(x:Proxy) returns (word) { return 32; } } -data zero = zero; -data suc(a) = suc(a); +enum zero { zero } +enum suc { suc(a) } -forall a b . instance MemberAccessProxy(memory((a, b)), zero) : Typedef (word) {} -forall a b . instance MemberAccessProxy(memory((a,b)), zero):LValueMemberAccess(memoryRef(a)) { - function memberAccess(mptr:MemberAccessProxy(memory((a,b)), zero), f:Proxy(zero)) -> memoryRef(a) { +impl Typedef, word> {} +impl LValueMemberAccess, memoryRef> { + function memberAccess(mptr:MemberAccessProxy<(a, b) memory, zero>, f:Proxy) returns (memoryRef) { let ptr:word = Typedef.rep(mptr); return memoryRef(ptr); } } -forall a b c n. MemberAccessProxy(memory(b), n):LValueMemberAccess(c), a:MemorySize => -instance MemberAccessProxy(memory((a,b)), suc(n)):LValueMemberAccess(c) { - function memberAccess(map:MemberAccessProxy(memory((a,b)), suc(n)), f:Proxy(suc(n))) -> c { +impl LValueMemberAccess>, c> where MemberAccessProxy: LValueMemberAccess, a: MemorySize { + function memberAccess(map:MemberAccessProxy<(a, b) memory, suc>, f:Proxy>) returns (c) { let ptr:word = memberAccessPtr(map); - let sz:word = MemorySize.size(Proxy:Proxy(a)); + let sz:word = MemorySize.size(Proxy as Proxy); assembly { ptr := add(ptr, sz) } - let newPtr:memory(b) = memory(ptr); - return LValueMemberAccess.memberAccess(MemberAccessProxy(newPtr, Proxy:Proxy(n))); + let newPtr:b memory = memory(ptr); + return LValueMemberAccess.memberAccess(MemberAccessProxy(newPtr, Proxy as Proxy)); } } -instance MemberAccessProxy(memory(a), zero) : LValueMemberAccess (word) {} -instance MemberAccessProxy(memory(a), suc(zero)) : LValueMemberAccess (uint) {} -instance MemberAccessProxy(memory(a), suc(suc(zero))) : LValueMemberAccess (word) {} -instance word:Assign(word){} -instance uint:Assign(uint){} +impl LValueMemberAccess, word> {} +impl LValueMemberAccess>, uint> {} +impl LValueMemberAccess>>, word> {} +impl Assign {} +impl Assign {} ////// Testing // struct S { x:word; y:uint; z:word; } -data S = S(word, uint, word); -data x_sel = x_sel; -data y_sel = y_sel; -data z_sel = z_sel; - -instance S:Typedef((word, uint, word)) { - function abs(x:(word, uint, word)) -> S { - match x { - | (a, b, c) => return S(a, b, c); - } +enum S { S(word, uint, word) } +enum x_sel { x_sel } +enum y_sel { y_sel } +enum z_sel { z_sel } + +impl Typedef { + function abs(x:(word, uint, word)) returns (S) { + match (x ) { + case (a, b, c) { return S(a, b, c); + } } } - function rep(x:S) -> (word, uint, word) { - match x { - | S(a, b, c) => return (a, b, c); - } + function rep(x:S) returns ((word, uint, word)) { + match (x ) { + case S(a, b, c) { return (a, b, c); + } } } } // The idea here would be to generate these particularly on the definition of a struct with fields. -forall c rep . S:Typedef(rep), MemberAccessProxy(memory(rep), zero):LValueMemberAccess(word) => -instance MemberAccessProxy(memory(S), x_sel):LValueMemberAccess(word) { - function memberAccess(map:MemberAccessProxy(memory(S), x_sel), f:Proxy(x_sel)) -> word { - return (LValueMemberAccess.memberAccess(MemberAccessProxy(memory(memberAccessPtr(map)):memory(rep), Proxy:Proxy(zero))) : word); +impl LValueMemberAccess, word> where S: Typedef, MemberAccessProxy: LValueMemberAccess { + function memberAccess(map:MemberAccessProxy, f:Proxy) returns (word) { + return (LValueMemberAccess.memberAccess(MemberAccessProxy(memory(memberAccessPtr(map)) as rep memory, Proxy as Proxy)) as word); } } -forall c rep . S:Typedef(rep), MemberAccessProxy(memory(rep), suc(zero)):LValueMemberAccess(uint) => -instance MemberAccessProxy(memory(S), y_sel):LValueMemberAccess(uint) { - function memberAccess(map:MemberAccessProxy(memory(S), y_sel), f:Proxy(y_sel)) -> uint { - return LValueMemberAccess.memberAccess(MemberAccessProxy(memory(memberAccessPtr(map)):memory(rep), Proxy:Proxy(suc(zero)))); +impl LValueMemberAccess, uint> where S: Typedef, MemberAccessProxy>: LValueMemberAccess { + function memberAccess(map:MemberAccessProxy, f:Proxy) returns (uint) { + return LValueMemberAccess.memberAccess(MemberAccessProxy(memory(memberAccessPtr(map)) as rep memory, Proxy as Proxy>)); } } -forall c rep . S:Typedef(rep), MemberAccessProxy(memory(rep), suc(suc(zero))):LValueMemberAccess(word) => -instance MemberAccessProxy(memory(S), z_sel):LValueMemberAccess(word) { - function memberAccess(map:MemberAccessProxy(memory(S), z_sel), f:Proxy(z_sel)) -> word { - return LValueMemberAccess.memberAccess(MemberAccessProxy(memory(memberAccessPtr(map)):memory(rep), Proxy:Proxy(suc(suc(zero))))); +impl LValueMemberAccess, word> where S: Typedef, MemberAccessProxy>>: LValueMemberAccess { + function memberAccess(map:MemberAccessProxy, f:Proxy) returns (word) { + return LValueMemberAccess.memberAccess(MemberAccessProxy(memory(memberAccessPtr(map)) as rep memory, Proxy as Proxy>>)); } } function f() { - let x:memory(word); - let y:memory(word); + let x:word memory; + let y:word memory; x = y; } function g() { - let s:memory(S) = Typedef.abs(0x80); + let s:S memory = Typedef.abs(0x80); let x:word = 42; let y:uint = Typedef.abs(21); let z:word = 7; // s.x = x; - Assign.assign(LValueMemberAccess.memberAccess(MemberAccessProxy(s, Proxy:Proxy(x_sel))), x); + Assign.assign(LValueMemberAccess.memberAccess(MemberAccessProxy(s, Proxy as Proxy)), x); // s.y = y; - Assign.assign(LValueMemberAccess.memberAccess(MemberAccessProxy(s, Proxy:Proxy(y_sel))), y); + Assign.assign(LValueMemberAccess.memberAccess(MemberAccessProxy(s, Proxy as Proxy)), y); // s.z = z; - Assign.assign(LValueMemberAccess.memberAccess(MemberAccessProxy(s, Proxy:Proxy(z_sel))), z); + Assign.assign(LValueMemberAccess.memberAccess(MemberAccessProxy(s, Proxy as Proxy)), z); } contract C { - public function main() { + function main() public { f(); g(); } diff --git a/test/examples/cases/require-annotation-contract-method.solc b/test/examples/cases/require-annotation-contract-method.solc index 55bd20051..3f377a68e 100644 --- a/test/examples/cases/require-annotation-contract-method.solc +++ b/test/examples/cases/require-annotation-contract-method.solc @@ -1,10 +1,10 @@ // Error: contract method missing return type annotation contract Doubler { - public function double(x : word) { + function double(x : word) public { return x; } - public function main() -> word { + function main() public returns (word) { return double(21); } } diff --git a/test/examples/cases/require-annotation-missing-param.solc b/test/examples/cases/require-annotation-missing-param.solc index 5d498984d..115834bdf 100644 --- a/test/examples/cases/require-annotation-missing-param.solc +++ b/test/examples/cases/require-annotation-missing-param.solc @@ -1,5 +1,5 @@ // Error: top-level free function with an unannotated parameter -function add(x, y : word) -> word { +function add(x, y : word) returns (word) { let res : word; assembly { res := add(x, y) } return res; diff --git a/test/examples/cases/require-annotation-mutual.solc b/test/examples/cases/require-annotation-mutual.solc index dc7fd31e6..e53174013 100644 --- a/test/examples/cases/require-annotation-mutual.solc +++ b/test/examples/cases/require-annotation-mutual.solc @@ -3,6 +3,6 @@ function foo(x : word) { return bar(x); } -function bar(x : word) -> word { +function bar(x : word) returns (word) { return foo(x); } diff --git a/test/examples/cases/return-fun-adder.solc b/test/examples/cases/return-fun-adder.solc index 552fda133..752e76e83 100644 --- a/test/examples/cases/return-fun-adder.solc +++ b/test/examples/cases/return-fun-adder.solc @@ -2,8 +2,8 @@ // Validates the single-pass type checker: closure conversion must not hide // that the returned lambda really has type (word) -> word. // Uses an assembly block instead of primAddWord so it lowers end-to-end. -function makeAdder(x : word) -> ((word) -> word) { - return lam (y : word) -> word { +function makeAdder(x : word) returns (function(word) internal returns (word)) { + return lam (y : word) returns (word) { let res : word; assembly { res := add(x, y) @@ -13,7 +13,7 @@ function makeAdder(x : word) -> ((word) -> word) { } contract C { - public function main() -> word { + function main() public returns (word) { let f = makeAdder(10); return f(5); } diff --git a/test/examples/cases/return-fun-bad-arity.solc b/test/examples/cases/return-fun-bad-arity.solc index 4eaf6ae1a..f3424b649 100644 --- a/test/examples/cases/return-fun-bad-arity.solc +++ b/test/examples/cases/return-fun-bad-arity.solc @@ -1,7 +1,7 @@ // INCORRECT: the signature promises a one-argument function (word) -> word, // but the returned lambda takes two arguments. -function makeF(x : word) -> ((word) -> word) { - return lam (y : word, z : word) -> word { +function makeF(x : word) returns (function(word) internal returns (word)) { + return lam (y : word, z : word) returns (word) { let res : word; assembly { res := add(y, z) diff --git a/test/examples/cases/return-fun-bad-param.solc b/test/examples/cases/return-fun-bad-param.solc index b93c35b06..b41f06a55 100644 --- a/test/examples/cases/return-fun-bad-param.solc +++ b/test/examples/cases/return-fun-bad-param.solc @@ -1,8 +1,8 @@ // INCORRECT: the returned lambda's parameter is `bool`, but the signature // promises (word) -> word. Closure conversion would erase the arrow type; // the single-pass checker must still reject this. -function makeAdder(x : word) -> ((word) -> word) { - return lam (y : bool) -> word { +function makeAdder(x : word) returns (function(word) internal returns (word)) { + return lam (y : bool) returns (word) { return x; }; } diff --git a/test/examples/cases/return-fun-bad-return.solc b/test/examples/cases/return-fun-bad-return.solc index a6cb6efa7..409f6b9e2 100644 --- a/test/examples/cases/return-fun-bad-return.solc +++ b/test/examples/cases/return-fun-bad-return.solc @@ -1,7 +1,7 @@ // INCORRECT: the returned lambda's body has type bool, but the signature // promises the result is word. -function makeConst(x : word) -> ((word) -> word) { - return lam (y : word) -> bool { +function makeConst(x : word) returns (function(word) internal returns (word)) { + return lam (y : word) returns (bool) { return true; }; } diff --git a/test/examples/cases/return-fun-bad-sig.solc b/test/examples/cases/return-fun-bad-sig.solc index 21021b255..1687de8a2 100644 --- a/test/examples/cases/return-fun-bad-sig.solc +++ b/test/examples/cases/return-fun-bad-sig.solc @@ -1,7 +1,7 @@ // INCORRECT: signature says the result consumes a bool ((bool) -> word), // but the returned lambda consumes a word. -function makeF(x : word) -> ((bool) -> word) { - return lam (y : word) -> word { +function makeF(x : word) returns (function(bool) internal returns (word)) { + return lam (y : word) returns (word) { return x; }; } diff --git a/test/examples/cases/return-fun-const.solc b/test/examples/cases/return-fun-const.solc index b2709271f..065682656 100644 --- a/test/examples/cases/return-fun-const.solc +++ b/test/examples/cases/return-fun-const.solc @@ -1,7 +1,7 @@ // Returns a constant function that closes over its argument. // Correct annotations: (word) -> word, body returns the captured word. -function constFn(x : word) -> ((word) -> word) { - return lam (y : word) -> word { +function constFn(x : word) returns (function(word) internal returns (word)) { + return lam (y : word) returns (word) { return x; }; } diff --git a/test/examples/cases/return-fun-eq.solc b/test/examples/cases/return-fun-eq.solc index 6f148fc81..21791b34e 100644 --- a/test/examples/cases/return-fun-eq.solc +++ b/test/examples/cases/return-fun-eq.solc @@ -1,7 +1,7 @@ // Returns a function comparing against a captured word, CORRECT annotations. // Uses an assembly `eq` instead of primEqWord so it lowers end-to-end. -function makeEq(x : word) -> ((word) -> word) { - return lam (y : word) -> word { +function makeEq(x : word) returns (function(word) internal returns (word)) { + return lam (y : word) returns (word) { let res : word; assembly { res := eq(x, y) @@ -11,7 +11,7 @@ function makeEq(x : word) -> ((word) -> word) { } contract C { - public function main() -> word { + function main() public returns (word) { let f = makeEq(7); return f(7); } diff --git a/test/examples/cases/return-fun-instance.solc b/test/examples/cases/return-fun-instance.solc index 067da6c63..a6a804ced 100644 --- a/test/examples/cases/return-fun-instance.solc +++ b/test/examples/cases/return-fun-instance.solc @@ -1,12 +1,12 @@ // Instance member returning a function with CORRECT annotations. // The compiled-away validation pass used to check this; the single pass must too. -forall t . class t:CtFun { - function ct(x : t) -> ((t) -> t); +trait CtFun { + function ct(x : t) returns (function(t) internal returns (t)); } -instance word:CtFun { - function ct(x : word) -> ((word) -> word) { - return lam (y : word) -> word { +impl CtFun { + function ct(x : word) returns (function(word) internal returns (word)) { + return lam (y : word) returns (word) { return x; }; } diff --git a/test/examples/cases/return-fun-not-fun.solc b/test/examples/cases/return-fun-not-fun.solc index 686b22361..3726f9a11 100644 --- a/test/examples/cases/return-fun-not-fun.solc +++ b/test/examples/cases/return-fun-not-fun.solc @@ -1,5 +1,5 @@ // INCORRECT: the signature promises a function (word) -> word, but the body // returns a plain word instead of a function. -function makeF(x : word) -> ((word) -> word) { +function makeF(x : word) returns (function(word) internal returns (word)) { return x; } diff --git a/test/examples/cases/same-name-constructor-qualifier.solc b/test/examples/cases/same-name-constructor-qualifier.solc index 6850b084a..18a9aca83 100644 --- a/test/examples/cases/same-name-constructor-qualifier.solc +++ b/test/examples/cases/same-name-constructor-qualifier.solc @@ -1,23 +1,23 @@ // Qualifier access (T.C) must work even when T has a same-name constructor. // Regression test for: `Error.Empty` reporting "Unqualified constructor: Empty". -data Err = Err(word) | Empty | Msg(word); +enum Err { Err(word), Empty, Msg(word) } -function pickEmpty() -> Err { +function pickEmpty() returns (Err) { return Err.Empty; } -function pickMsg(x: word) -> Err { +function pickMsg(x: word) returns (Err) { return Err.Msg(x); } -function pickErr(x: word) -> Err { +function pickErr(x: word) returns (Err) { return Err.Err(x); } -function main() -> word { - match pickEmpty() { - | Err.Empty => return 1; - | Err.Err(_) => return 2; - | Err.Msg(_) => return 3; - } +function main() returns (word) { + match (pickEmpty() ) { + case Err.Empty { return 1; + } case Err.Err(_) { return 2; + } case Err.Msg(_) { return 3; + } } } diff --git a/test/examples/cases/signature.solc b/test/examples/cases/signature.solc index 1be7243be..d192306f8 100644 --- a/test/examples/cases/signature.solc +++ b/test/examples/cases/signature.solc @@ -1,8 +1,8 @@ -class self:Typedef(underlyingType) { - function rep(x:self) -> underlyingType; +trait Typedef { + function rep(x:self) returns (underlyingType); } -forall t:Typedef(word) . function tripleFun(x:t) { +function tripleFun(x:t) where t: Typedef { return Typedef.rep(x); } diff --git a/test/examples/cases/simpleDiscount.solc b/test/examples/cases/simpleDiscount.solc index ebafe1b08..a22964b12 100644 --- a/test/examples/cases/simpleDiscount.solc +++ b/test/examples/cases/simpleDiscount.solc @@ -1,26 +1,22 @@ // test complex match example from the blog post // simplified to use word instead of uint256 -import std.{address, Num, Add, Sub, Div, Bounded, Eq, Ord, Typedef}; +import {address, Num, Add, Sub, Div, Bounded, Eq, Ord, Typedef} from std; -data AuctionState = - NotStarted(word) - | Active(word, address) - | Ended(word, address) - | Cancelled(word, address); +enum AuctionState { NotStarted(word), Active(word, address), Ended(word, address), Cancelled(word, address) } -data Phase = Early | Late; +enum Phase { Early, Late } -function discount(state : AuctionState, phase : Phase) -> word { - match state, phase { - | .Active(bid, _), .Early => return bid / 10; - | .Active(bid, _), .Late => return bid / 20; - | _, _ => return 0; - } +function discount(state : AuctionState, phase : Phase) returns (word) { + match (state, phase ) { + case (.Active(bid, _), .Early ) { return bid / 10; + } case (.Active(bid, _), .Late ) { return bid / 20; + } default { return 0; + } } } contract Discount { - public function main() -> word { - discount(.Active(420,.address(0)), .Early) + function main() public returns (word) { + return discount(.Active(420,.address(0)), .Early); } } diff --git a/test/examples/cases/simpleIfExpr.solc b/test/examples/cases/simpleIfExpr.solc index a6c812a8b..da500036b 100644 --- a/test/examples/cases/simpleIfExpr.solc +++ b/test/examples/cases/simpleIfExpr.solc @@ -1,3 +1,3 @@ contract SimpleIfStmt { - public function main() { return (if (true) then 1 else 0); } + function main() public { return (( (true) ? 1 : 0)); } } diff --git a/test/examples/cases/simpleIfStmt.solc b/test/examples/cases/simpleIfStmt.solc index 80e672f27..c81311f03 100644 --- a/test/examples/cases/simpleIfStmt.solc +++ b/test/examples/cases/simpleIfStmt.solc @@ -1,3 +1,3 @@ contract SimpleIfStmt { - public function main() { if (true) {return 1;} else {return 0;} } + function main() public { if (true) {return 1;} else {return 0;} } } diff --git a/test/examples/cases/simpleid.solc b/test/examples/cases/simpleid.solc index a85da9754..baeefa9f0 100644 --- a/test/examples/cases/simpleid.solc +++ b/test/examples/cases/simpleid.solc @@ -1,3 +1,3 @@ -forall a . function id(x : a) -> a { +function id(x : a) returns (a) { return x; } diff --git a/test/examples/cases/single-lambda.solc b/test/examples/cases/single-lambda.solc index 7c6a17297..168b802ca 100644 --- a/test/examples/cases/single-lambda.solc +++ b/test/examples/cases/single-lambda.solc @@ -1,3 +1,3 @@ -function foo () -> (word) -> bool { - return lam (x:word) -> bool { return true; }; +function foo () returns (function(word) internal returns (bool)) { + return lam (x:word) returns (bool) { return true; }; } diff --git a/test/examples/cases/skolem-let.solc b/test/examples/cases/skolem-let.solc index 2f8ea1c6d..f4a45a688 100644 --- a/test/examples/cases/skolem-let.solc +++ b/test/examples/cases/skolem-let.solc @@ -1,13 +1,13 @@ -forall a. function fromWord(x: word) -> a { +function fromWord(x: word) returns (a) { let result : a; assembly { result := x } return result; } contract Unsafe { - public function main() { - fromWord(7):(); + function main() public { + fromWord(7) as (); return 42; } } diff --git a/test/examples/cases/snds.solc b/test/examples/cases/snds.solc index 44b7bdb1f..2b5d8320a 100644 --- a/test/examples/cases/snds.solc +++ b/test/examples/cases/snds.solc @@ -1,7 +1,7 @@ - function snds (p1 : (word, word), p2 : (word, word)) -> (word, word) { - match p1, p2 { - | (a,b) , (c,d) => return (b,d); - } + function snds (p1 : (word, word), p2 : (word, word)) returns ((word, word)) { + match (p1, p2 ) { + case ((a,b) , (c,d) ) { return (b,d); + } } } diff --git a/test/examples/cases/spec-fail-ungrounded.solc b/test/examples/cases/spec-fail-ungrounded.solc index dde3745a1..87fe56e52 100644 --- a/test/examples/cases/spec-fail-ungrounded.solc +++ b/test/examples/cases/spec-fail-ungrounded.solc @@ -11,19 +11,17 @@ // no constraint, no instance, and no return-type context to fix 'a', so // ensureClosed reports a free type variable and aborts. -forall a. -function abort_(x:word) -> a { +function abort_(x:word) returns (a) { return abort_(x); } -forall b. -function sink_(y:b) -> word { +function sink_(y:b) returns (word) { return 0; } contract C { constructor() {} - public function main() -> word { + function main() public returns (word) { return sink_(abort_(0)); } } diff --git a/test/examples/cases/storage-adt-mapping-field-fail.solc b/test/examples/cases/storage-adt-mapping-field-fail.solc index 15f842a68..da834b7ef 100644 --- a/test/examples/cases/storage-adt-mapping-field-fail.solc +++ b/test/examples/cases/storage-adt-mapping-field-fail.solc @@ -1,7 +1,7 @@ -import std.{*}; -import std.dispatch.{*}; -import std.Generic.{*}; -import std.StorageGeneric.{*}; +import {*} from std; +import {*} from std.dispatch; +import {*} from std.Generic; +import {*} from std.StorageGeneric; // A mapping cannot be a field of a data type. std only provides // `storage(mapping(k,v)) : CanStore(storage(mapping(k,v)))` — the slot handle @@ -12,7 +12,7 @@ import std.StorageGeneric.{*}; // (Even if it did, that instance's store/load are `unimplemented()`: copying a // mapping is not a meaningful storage operation.) -data Wrapper = Wrapper(mapping(uint256, uint256)); +enum Wrapper { Wrapper(mapping(uint256 => uint256)) } contract C { w : Wrapper; diff --git a/test/examples/cases/storage-adt-recursive-fail.solc b/test/examples/cases/storage-adt-recursive-fail.solc index d7739a579..83dc072d0 100644 --- a/test/examples/cases/storage-adt-recursive-fail.solc +++ b/test/examples/cases/storage-adt-recursive-fail.solc @@ -1,7 +1,7 @@ -import std.{*}; -import std.dispatch.{*}; -import std.Generic.{*}; -import std.StorageGeneric.{*}; +import {*} from std; +import {*} from std.dispatch; +import {*} from std.Generic; +import {*} from std.StorageGeneric; // A recursive data type has no bounded slot footprint, so DeriveGeneric // (isRecursiveData) deliberately skips deriving StorageSize and @@ -11,7 +11,7 @@ import std.StorageGeneric.{*}; // The failure surfaces at the use site (the field assignment), not at // derivation time, which is the design stated in DeriveGeneric. -data IntList = Nil | Cons(uint256, IntList); +enum IntList { Nil, Cons(uint256, IntList) } contract C { xs : IntList; diff --git a/test/examples/cases/storage-adt-recursive-ok.solc b/test/examples/cases/storage-adt-recursive-ok.solc index e28974fc2..17db91ea4 100644 --- a/test/examples/cases/storage-adt-recursive-ok.solc +++ b/test/examples/cases/storage-adt-recursive-ok.solc @@ -1,30 +1,30 @@ -import std.{*}; -import std.dispatch.{*}; -import std.Generic.{*}; -import std.StorageGeneric.{*}; +import {*} from std; +import {*} from std.dispatch; +import {*} from std.Generic; +import {*} from std.StorageGeneric; // The counterpart of storage-adt-recursive-fail.solc: skipping storage // derivation for a recursive type is a SKIP, not a hard error. The type still // gets its Generic instance and remains usable everywhere except storage. -data IntList = Nil | Cons(uint256, IntList); +enum IntList { Nil, Cons(uint256, IntList) } -function len(xs : IntList) -> uint256 { - match xs { - | IntList.Nil => return uint256(0); - | IntList.Cons(_, r) => return uint256(1) + len(r); - } +function len(xs : IntList) returns (uint256) { + match (xs ) { + case IntList.Nil { return uint256(0); + } case IntList.Cons(_, r) { return uint256(1) + len(r); + } } } // A non-recursive neighbour in the same module still gets its storage // instances, so the skip is per-type rather than per-module. -data Point = Point(uint256, uint256); +enum Point { Point(uint256, uint256) } contract C { p : Point; constructor() { p = Point(uint256(1), uint256(2)); - assert(StorageSize.size(Proxy : Proxy(Point)) == 2); + assert(StorageSize.size(Proxy as Proxy) == 2); } } diff --git a/test/examples/cases/strange-unbound.solc b/test/examples/cases/strange-unbound.solc index 230f6ae58..de4b470b5 100644 --- a/test/examples/cases/strange-unbound.solc +++ b/test/examples/cases/strange-unbound.solc @@ -1,5 +1,3 @@ -forall b. -class b:IsA { - forall a. - function ais(p : (a,b)) -> a; +trait IsA { + function ais(p : (a, b)) returns (a); } diff --git a/test/examples/cases/string-const.solc b/test/examples/cases/string-const.solc index 735a6d6f9..58e783464 100644 --- a/test/examples/cases/string-const.solc +++ b/test/examples/cases/string-const.solc @@ -1,5 +1,5 @@ contract Answer { - public function main() { + function main() public { return "42"; } } diff --git a/test/examples/cases/subject-index.solc b/test/examples/cases/subject-index.solc index 667f65e50..f1fbdff2a 100644 --- a/test/examples/cases/subject-index.solc +++ b/test/examples/cases/subject-index.solc @@ -1,37 +1,35 @@ -data storage(a) = storage(word); -data storageRef(a) = storageRef(word); -data Proxy(a) = Proxy; +enum storage { storage(word) } +enum storageRef { storageRef(word) } +enum Proxy { Proxy } -data mapping(member, index) = mapping(word, Proxy(member), Proxy(index)); +enum mapping { mapping(word, Proxy, Proxy) } -forall lhs rhs . class lhs:Assign(rhs) { - function assign(l:lhs, r:rhs) -> (); +trait Assign { + function assign(l:lhs, r:rhs) returns (()); } -forall a . instance storageRef(a):Assign(a) { - function assign(l:storageRef(a), y:a) { +impl Assign, a> { + function assign(l:storageRef, y:a) { } } -forall self fieldType offsetType . class self:CStructField(fieldType, offsetType) {} -data StructField(structType, fieldSelector) = StructField(structType); +trait CStructField {} +enum StructField { StructField(structType) } -data MemberAccessProxy(a, field, offset) = MemberAccessProxy(a, field); +enum MemberAccessProxy { MemberAccessProxy(a, field) } -forall self memberRefType . class self:LValueMemberAccess(memberRefType) { - function memberAccess(x:self) -> memberRefType; +trait LValueMemberAccess { + function memberAccess(x:self) returns (memberRefType); } // ------------------------------------------------------------------ // Contract field access // ------------------------------------------------------------------ -forall cxt fieldSelector fieldType offsetType - . StructField(cxt, fieldSelector):CStructField(fieldType, offsetType) - => instance MemberAccessProxy(cxt, fieldSelector, offsetType):LValueMemberAccess(storageRef(fieldType)) { - function memberAccess(x:MemberAccessProxy(cxt, fieldSelector, offsetType)) -> storageRef(fieldType) { +impl LValueMemberAccess, storageRef> where StructField: CStructField { + function memberAccess(x:MemberAccessProxy) returns (storageRef) { return storageRef(0x100); } } @@ -40,19 +38,18 @@ forall cxt fieldSelector fieldType offsetType // Indexed access // ------------------------------------------------------------------ -data mapping(index, member) = mapping(word); -data IndexAccessProxy(map, index, member) = IndexAccessProxy(map, index); +enum mapping { mapping(word) } +enum IndexAccessProxy { IndexAccessProxy(map, index) } -forall map index member. - instance IndexAccessProxy(storageRef(map), index, member):LValueMemberAccess(storageRef(member)) { - function memberAccess(x:IndexAccessProxy(storageRef(map), index, member)) -> storageRef(member) { +impl LValueMemberAccess, index, member>, storageRef> { + function memberAccess(x:IndexAccessProxy, index, member>) returns (storageRef) { return storageRef(0); } } -data MintCtx = MintCtx; -data balances_sel = balances_sel; -instance StructField(MintCtx, balances_sel):CStructField(mapping(word,word), ()) {} +enum MintCtx { MintCtx } +enum balances_sel { balances_sel } +impl CStructField, mapping(word => word), ()> {} function mint(amount:word) { let bal_prx = MemberAccessProxy(MintCtx, balances_sel); @@ -71,7 +68,7 @@ instance StructField(MintCtx, balances_sel):CStructField(mapping(word,word), ()) } contract Map { - public function main () { + function main () public { mint(1000); } } diff --git a/test/examples/cases/subject-reduction.solc b/test/examples/cases/subject-reduction.solc index f1d32e96b..79a79c9db 100644 --- a/test/examples/cases/subject-reduction.solc +++ b/test/examples/cases/subject-reduction.solc @@ -1,37 +1,35 @@ -data storage(a) = storage(word); -data storageRef(a) = storageRef(word); -data Proxy(a) = Proxy; +enum storage { storage(word) } +enum storageRef { storageRef(word) } +enum Proxy { Proxy } -data mapping(member, index) = mapping(word, Proxy(member), Proxy(index)); +enum mapping { mapping(word, Proxy, Proxy) } -forall lhs rhs . class lhs:Assign(rhs) { - function assign(l:lhs, r:rhs) -> (); +trait Assign { + function assign(l:lhs, r:rhs) returns (()); } -forall a . instance storageRef(a):Assign(a) { - function assign(l:storageRef(a), y:a) { +impl Assign, a> { + function assign(l:storageRef, y:a) { } } -forall self fieldType offsetType . class self:CStructField(fieldType, offsetType) {} -data StructField(structType, fieldSelector) = StructField(structType); +trait CStructField {} +enum StructField { StructField(structType) } -data MemberAccessProxy(a, field, offset) = MemberAccessProxy(a, field); +enum MemberAccessProxy { MemberAccessProxy(a, field) } -forall self memberRefType . class self:LValueMemberAccess(memberRefType) { - function memberAccess(x:self) -> memberRefType; +trait LValueMemberAccess { + function memberAccess(x:self) returns (memberRefType); } // ------------------------------------------------------------------ // Contract field access // ------------------------------------------------------------------ -forall cxt fieldSelector fieldType offsetType - . StructField(cxt, fieldSelector):CStructField(fieldType, offsetType) - => instance MemberAccessProxy(cxt, fieldSelector, offsetType):LValueMemberAccess(storageRef(fieldType)) { - function memberAccess(x:MemberAccessProxy(cxt, fieldSelector, offsetType)) -> storageRef(fieldType) { +impl LValueMemberAccess, storageRef> where StructField: CStructField { + function memberAccess(x:MemberAccessProxy) returns (storageRef) { return storageRef(0x100); } } @@ -40,20 +38,19 @@ forall cxt fieldSelector fieldType offsetType // Indexed access // ------------------------------------------------------------------ -data mapping(index, member) = mapping(word); -data IndexAccessProxy(map, index, member) = IndexAccessProxy(map, index); +enum mapping { mapping(word) } +enum IndexAccessProxy { IndexAccessProxy(map, index) } -forall map index member. - instance IndexAccessProxy(storageRef(map), index, member):LValueMemberAccess(storageRef(member)) { - function memberAccess(x:IndexAccessProxy(storageRef(map), index, member)) -> storageRef(member) { +impl LValueMemberAccess, index, member>, storageRef> { + function memberAccess(x:IndexAccessProxy, index, member>) returns (storageRef) { return storageRef(0); } } -data MintCtx = MintCtx; -data balances_sel = balances_sel; -instance StructField(MintCtx, balances_sel):CStructField(mapping(word,word), ()) {} +enum MintCtx { MintCtx } +enum balances_sel { balances_sel } +impl CStructField, mapping(word => word), ()> {} function mint(amount:word) { let bal_prx = MemberAccessProxy(MintCtx, balances_sel); diff --git a/test/examples/cases/subsumption-constraint.solc b/test/examples/cases/subsumption-constraint.solc index bf0cd6b51..285445b84 100644 --- a/test/examples/cases/subsumption-constraint.solc +++ b/test/examples/cases/subsumption-constraint.solc @@ -1,22 +1,22 @@ // This code should FAIL, but PASSES! -data Bool = True | False; +enum Bool { True, False } -forall a . class a : MyCls { - function f(x : a, y : a) -> Bool; +trait MyCls { + function f(x : a, y : a) returns (Bool); } -forall a . function the_bug(x : a, y : a) -> Bool { +function the_bug(x : a, y : a) returns (Bool) { return MyCls.f(x, y); } contract Foo { - public function x() { + function x() public { let b1 = Bool.True; let b2 = Bool.False; the_bug(b1, b2); } - public function main() { + function main() public { x(); } } diff --git a/test/examples/cases/subsumption-test.solc b/test/examples/cases/subsumption-test.solc index 014b0a360..9d3b30acb 100644 --- a/test/examples/cases/subsumption-test.solc +++ b/test/examples/cases/subsumption-test.solc @@ -1,7 +1,7 @@ -function id (x) -> word { +function id (x) returns (word) { return x; } -forall a . function fakeid(x : word) -> a { +function fakeid(x : word) returns (a) { return x ; } diff --git a/test/examples/cases/sum-match-default.solc b/test/examples/cases/sum-match-default.solc index fb90bf701..7a48b3683 100644 --- a/test/examples/cases/sum-match-default.solc +++ b/test/examples/cases/sum-match-default.solc @@ -1,14 +1,14 @@ contract SumMatchDefault { - data Option(a) = None | Some(a); + enum Option { None, Some(a) } - public function g(s : Option(word)) -> Option(word) { - match s { - | Option.None => return Option.None; - | x => return x; - } + function g(s : Option) public returns (Option) { + match (s ) { + case Option.None { return Option.None; + } case x { return x; + } } } - public function main() -> word { + function main() public returns (word) { g(Option.None); return 42; } diff --git a/test/examples/cases/super-class-cycle-fail.solc b/test/examples/cases/super-class-cycle-fail.solc index c6567a6b4..c08e8034d 100644 --- a/test/examples/cases/super-class-cycle-fail.solc +++ b/test/examples/cases/super-class-cycle-fail.solc @@ -1,15 +1,15 @@ -forall a . a:B => class a:A {} -forall a . a:A => class a:B {} -forall a . class a:C {} +trait A where a: B {} +trait B where a: A {} +trait C {} -forall a . a:C => function needsC(x:a) -> () { - return (); +function needsC(x:a) returns (()) where a: C { + return; } -forall a . a:A => function cannotGetC(x:a) -> () { +function cannotGetC(x:a) returns (()) where a: A { return needsC(x); } -function main() -> () { - return (); +function main() returns (()) { + return; } diff --git a/test/examples/cases/super-class-cycle.solc b/test/examples/cases/super-class-cycle.solc index 04a42f71a..1db57b2b7 100644 --- a/test/examples/cases/super-class-cycle.solc +++ b/test/examples/cases/super-class-cycle.solc @@ -1,14 +1,14 @@ -forall a . a:B => class a:A {} -forall a . a:A => class a:B {} +trait A where a: B {} +trait B where a: A {} -forall a . a:B => function needsB(x:a) -> () { - return (); +function needsB(x:a) returns (()) where a: B { + return; } -forall a . a:A => function usesSuperCycle(x:a) -> () { +function usesSuperCycle(x:a) returns (()) where a: A { return needsB(x); } -function main() -> () { - return (); +function main() returns (()) { + return; } diff --git a/test/examples/cases/super-class-num.solc b/test/examples/cases/super-class-num.solc index 920a0b45f..df3eb5655 100644 --- a/test/examples/cases/super-class-num.solc +++ b/test/examples/cases/super-class-num.solc @@ -1,26 +1,25 @@ -data Bool = False | True; +enum Bool { False, True } -function fromBool(b:Bool) -> word { - match b { - | Bool.False => return 0; - | Bool.True => return 1; - } +function fromBool(b:Bool) returns (word) { + match (b ) { + case Bool.False { return 0; + } case Bool.True { return 1; + } } } -function toBool(x: word) -> Bool { - match x { - | 0 => return Bool.False; - | _ => return Bool.True; - } +function toBool(x: word) returns (Bool) { + match (x ) { + case 0 { return Bool.False; + } default { return Bool.True; + } } } -forall a. -class a:Eq { - function eq(x:a, y:a) -> Bool; +trait Eq { + function eq(x:a, y:a) returns (Bool); } -instance word:Eq { - function eq(x:word, y:word) -> Bool { +impl Eq { + function eq(x:word, y:word) returns (Bool) { let res : word; assembly { res := eq(x, y) @@ -29,42 +28,41 @@ instance word:Eq { } } -function not (b : Bool) -> Bool { - match b { - | Bool.True => return Bool.False ; - | Bool.False => return Bool.True ; - } +function not (b : Bool) returns (Bool) { + match (b ) { + case Bool.True { return Bool.False ; + } case Bool.False { return Bool.True ; + } } } -forall a . a:Eq => function ne(x : a, y : a) -> Bool { +function ne(x : a, y : a) returns (Bool) where a: Eq { return not(Eq.eq(x,y)); } -forall a. a:Eq => -class a:Num { - function toWord(x:a) -> word; - function fromWord(x:word) -> a; +trait Num where a: Eq { + function toWord(x:a) returns (word); + function fromWord(x:word) returns (a); } -instance word:Num { - function toWord(x:word) -> word { return x; } - function fromWord(x:word) -> word { return x; } +impl Num { + function toWord(x:word) returns (word) { return x; } + function fromWord(x:word) returns (word) { return x; } } -data uint = uint(word); +enum uint { uint(word) } -instance uint:Eq { - function eq(x:uint, y:uint) -> Bool { return Eq.eq(Num.toWord(x), Num.toWord(y)); } +impl Eq { + function eq(x:uint, y:uint) returns (Bool) { return Eq.eq(Num.toWord(x), Num.toWord(y)); } } -instance uint:Num { - function toWord(x:uint) -> word +impl Num { + function toWord(x:uint) returns (word) { - match x { - | uint(y) => return y; - } + match (x ) { + case uint(y) { return y; + } } } - function fromWord(x:word) -> uint { return uint(x); } + function fromWord(x:word) returns (uint) { return uint(x); } } diff --git a/test/examples/cases/super-class-recursive-arg.solc b/test/examples/cases/super-class-recursive-arg.solc index c6b0c2d98..259f77c5c 100644 --- a/test/examples/cases/super-class-recursive-arg.solc +++ b/test/examples/cases/super-class-recursive-arg.solc @@ -1,17 +1,17 @@ -pragma no-patterson-condition A; +pragma solcore noPattersonCondition A; -data Wrap(a) = Wrap(a); +enum Wrap { Wrap(a) } -forall a . Wrap(a):A => class a:A {} +trait A where Wrap: A {} -forall a . Wrap(a):A => function needsWrappedA(x:a) -> () { - return (); +function needsWrappedA(x:a) returns (()) where Wrap: A { + return; } -forall a . a:A => function shouldUseSuperclass(x:a) -> () { +function shouldUseSuperclass(x:a) returns (()) where a: A { return needsWrappedA(x); } -function main() -> () { - return (); +function main() returns (()) { + return; } diff --git a/test/examples/cases/super-class.solc b/test/examples/cases/super-class.solc index e413219ab..b9aef6335 100644 --- a/test/examples/cases/super-class.solc +++ b/test/examples/cases/super-class.solc @@ -1,38 +1,38 @@ -data List(a) = Nil | Cons(a,List(a)); -data Bool = False | True; +enum List { Nil, Cons(a, List) } +enum Bool { False, True } -function and (x : Bool, y : Bool) -> Bool { - match x,y { - | Bool.False, _ => return Bool.False; - | Bool.True, y => return y; - } +function and (x : Bool, y : Bool) returns (Bool) { + match (x,y ) { + case (Bool.False, _ ) { return Bool.False; + } case (Bool.True, y ) { return y; + } } } -forall a . class a : Eq { - function eq(x : a, y : a) -> Bool; +trait Eq { + function eq(x : a, y : a) returns (Bool); } -instance Bool : Eq { - function eq (x : Bool, y : Bool) -> Bool { - match x, y { - | Bool.False, Bool.False => return Bool.True; - | Bool.True, Bool.True => return Bool.True; - | _, _ => return Bool.False; - } +impl Eq { + function eq (x : Bool, y : Bool) returns (Bool) { + match (x, y ) { + case (Bool.False, Bool.False ) { return Bool.True; + } case (Bool.True, Bool.True ) { return Bool.True; + } default { return Bool.False; + } } } } -forall a . a : Eq => instance (List(a)) : Eq { - function eq (xs : List(a), ys : List(a)) -> Bool { - match xs, ys { - | List.Nil, List.Nil => return Bool.True; - | List.Cons(x,xs), List.Cons(y,ys) => +impl Eq> where a: Eq { + function eq (xs : List, ys : List) returns (Bool) { + match (xs, ys ) { + case (List.Nil, List.Nil ) { return Bool.True; + } case (List.Cons(x,xs), List.Cons(y,ys) ) { return and(Eq.eq(x,y),Eq.eq(xs,ys)); - | _ , _ => return Bool.False; - } + } default { return Bool.False; + } } } } -function foo() -> () { +function foo() returns (()) { let x = Eq.eq(List.Cons(Bool.True,List.Nil), List.Nil); } diff --git a/test/examples/cases/synonym-arity-mismatch.solc b/test/examples/cases/synonym-arity-mismatch.solc index 0486adc21..294d3cfff 100644 --- a/test/examples/cases/synonym-arity-mismatch.solc +++ b/test/examples/cases/synonym-arity-mismatch.solc @@ -1,5 +1,5 @@ -type F(a) = pair(a, word); +type F is pair; -function main() -> F(word, word) { +function main() returns (F) { return pair(42, 0); } diff --git a/test/examples/cases/synonym-basic.solc b/test/examples/cases/synonym-basic.solc index 2f5219806..b5c9f21ca 100644 --- a/test/examples/cases/synonym-basic.solc +++ b/test/examples/cases/synonym-basic.solc @@ -1,21 +1,21 @@ -type Uint = word; -type Point = pair(word, word); +type Uint is word; +type Point is pair; -function useUint(x: Uint) -> word { +function useUint(x: Uint) returns (word) { return x; } -function makePoint(x: word, y: word) -> Point { +function makePoint(x: word, y: word) returns (Point) { return pair(x, y); } -function getX(p: Point) -> word { - match p { - | pair(x, _) => return x; - } +function getX(p: Point) returns (word) { + match (p ) { + case pair(x, _) { return x; + } } } -function main() -> word { +function main() returns (word) { let p: Point = makePoint(10, 20); return getX(p); } \ No newline at end of file diff --git a/test/examples/cases/synonym-in-function.solc b/test/examples/cases/synonym-in-function.solc index a71676b0b..e01493b89 100644 --- a/test/examples/cases/synonym-in-function.solc +++ b/test/examples/cases/synonym-in-function.solc @@ -1,28 +1,28 @@ // Synonyms in function parameter and return types -type Int = word; -type Point = pair(Int, Int); +type Int is word; +type Point is pair; -function add(a: Int, b: Int) -> Int { +function add(a: Int, b: Int) returns (Int) { return a; } -function makePoint(x: Int, y: Int) -> Point { +function makePoint(x: Int, y: Int) returns (Point) { return pair(x, y); } -function getX(p: Point) -> Int { - match p { - | pair(x, _) => return x; - } +function getX(p: Point) returns (Int) { + match (p ) { + case pair(x, _) { return x; + } } } -function getY(p: Point) -> Int { - match p { - | pair(_, y) => return y; - } +function getY(p: Point) returns (Int) { + match (p ) { + case pair(_, y) { return y; + } } } -function main() -> word { +function main() returns (word) { let a: Int = 10; let b: Int = 20; let p: Point = makePoint(a, b); diff --git a/test/examples/cases/synonym-long-cycle.solc b/test/examples/cases/synonym-long-cycle.solc index d06783dce..d34f5d91c 100644 --- a/test/examples/cases/synonym-long-cycle.solc +++ b/test/examples/cases/synonym-long-cycle.solc @@ -1,8 +1,8 @@ // Longer recursive cycle should be rejected -type A = B; -type B = C; -type C = A; +type A is B; +type B is C; +type C is A; -function main() -> word { +function main() returns (word) { return 0; } \ No newline at end of file diff --git a/test/examples/cases/synonym-nested.solc b/test/examples/cases/synonym-nested.solc index 912cc7055..b15f6c0a7 100644 --- a/test/examples/cases/synonym-nested.solc +++ b/test/examples/cases/synonym-nested.solc @@ -1,23 +1,23 @@ // Deeply nested synonyms (synonym of synonym of synonym) -type Word1 = word; -type Word2 = Word1; -type Word3 = Word2; +type Word1 is word; +type Word2 is Word1; +type Word3 is Word2; -type Pair1 = pair(word, word); -type Pair2 = Pair1; -type Pair3 = Pair2; +type Pair1 is pair; +type Pair2 is Pair1; +type Pair3 is Pair2; -function useWord3(x: Word3) -> word { +function useWord3(x: Word3) returns (word) { return x; } -function usePair3(p: Pair3) -> word { - match p { - | pair(x, _) => return x; - } +function usePair3(p: Pair3) returns (word) { + match (p ) { + case pair(x, _) { return x; + } } } -function main() -> word { +function main() returns (word) { let x: Word3 = 42; let p: Pair3 = pair(1, 2); return useWord3(x); diff --git a/test/examples/cases/synonym-param.solc b/test/examples/cases/synonym-param.solc index 1ed3f5660..4cd0eee7f 100644 --- a/test/examples/cases/synonym-param.solc +++ b/test/examples/cases/synonym-param.solc @@ -1,13 +1,13 @@ -type MyPair(a, b) = pair(a, b); -type IntPair = MyPair(word, word); +type MyPair is pair; +type IntPair is MyPair; -function makePair(x: word, y: word) -> MyPair(word, word) { +function makePair(x: word, y: word) returns (MyPair) { return pair(x, y); } -function main() -> word { +function main() returns (word) { let p: IntPair = makePair(42, 100); - match p { - | pair(x, _) => return x; - } + match (p ) { + case pair(x, _) { return x; + } } } diff --git a/test/examples/cases/synonym-recursive.solc b/test/examples/cases/synonym-recursive.solc index 3e34ef4c2..0b0c47b7c 100644 --- a/test/examples/cases/synonym-recursive.solc +++ b/test/examples/cases/synonym-recursive.solc @@ -1,8 +1,8 @@ -type A = B; -type B = A; +type A is B; +type B is A; contract RecursiveTest { - public function main() -> word { + function main() public returns (word) { return 0; } } \ No newline at end of file diff --git a/test/examples/cases/synonym-self-recursive.solc b/test/examples/cases/synonym-self-recursive.solc index 9ecb567d7..94af721f5 100644 --- a/test/examples/cases/synonym-self-recursive.solc +++ b/test/examples/cases/synonym-self-recursive.solc @@ -1,6 +1,6 @@ // Self-recursive synonym should be rejected -type A = A; +type A is A; -function main() -> word { +function main() returns (word) { return 0; } \ No newline at end of file diff --git a/test/examples/cases/tabled-answer-reuse.solc b/test/examples/cases/tabled-answer-reuse.solc index d815c67ee..ca850978a 100644 --- a/test/examples/cases/tabled-answer-reuse.solc +++ b/test/examples/cases/tabled-answer-reuse.solc @@ -1,16 +1,16 @@ -pragma no-patterson-condition Derived; +pragma solcore noPattersonCondition Derived; -forall a . class a:Seed {} -forall a . class a:Derived {} +trait Seed {} +trait Derived {} -instance word:Seed {} +impl Seed {} -forall a . a:Seed => instance a:Derived {} +impl Derived where a: Seed {} -forall a . a:Derived, a:Derived => function needsDerivedTwice(x:a) -> () { - return (); +function needsDerivedTwice(x:a) returns (()) where a: Derived, a: Derived { + return; } -function main() -> () { +function main() returns (()) { return needsDerivedTwice(0); } diff --git a/test/examples/cases/tabled-cycle-fail.solc b/test/examples/cases/tabled-cycle-fail.solc index 3402f7335..315a6bc8c 100644 --- a/test/examples/cases/tabled-cycle-fail.solc +++ b/test/examples/cases/tabled-cycle-fail.solc @@ -1,16 +1,16 @@ -pragma no-patterson-condition A; -pragma no-patterson-condition B; +pragma solcore noPattersonCondition A; +pragma solcore noPattersonCondition B; -forall a . class a:A {} -forall a . class a:B {} +trait A {} +trait B {} -forall a . a:B => instance a:A {} -forall a . a:A => instance a:B {} +impl A where a: B {} +impl B where a: A {} -forall a . a:A => function needsA(x:a) -> () { - return (); +function needsA(x:a) returns (()) where a: A { + return; } -function main() -> () { +function main() returns (()) { return needsA(0); } diff --git a/test/examples/cases/tabled-default-instance.solc b/test/examples/cases/tabled-default-instance.solc index 8bc393713..f998a74a9 100644 --- a/test/examples/cases/tabled-default-instance.solc +++ b/test/examples/cases/tabled-default-instance.solc @@ -1,13 +1,13 @@ -forall a . class a:Fallback { - function tag(x:a) -> word; +trait Fallback { + function tag(x:a) returns (word); } -forall a . default instance a:Fallback { - function tag(x:a) -> word { +default impl Fallback { + function tag(x:a) returns (word) { return 7; } } -function main() -> word { - return Fallback.tag(0:word); +function main() returns (word) { + return Fallback.tag(0 as word); } diff --git a/test/examples/cases/tabled-given-order.solc b/test/examples/cases/tabled-given-order.solc index 689dee146..7286a5218 100644 --- a/test/examples/cases/tabled-given-order.solc +++ b/test/examples/cases/tabled-given-order.solc @@ -1,23 +1,23 @@ -pragma no-patterson-condition C; +pragma solcore noPattersonCondition C; -forall a . class a:A {} -forall a . class a:B {} -forall a . class a:C {} +trait A {} +trait B {} +trait C {} -forall a . a:A, a:B => instance a:C {} +impl C where a: A, a: B {} -forall a . a:C => function needsC(x:a) -> () { - return (); +function needsC(x:a) returns (()) where a: C { + return; } -forall a . a:A, a:B => function fromAB(x:a) -> () { +function fromAB(x:a) returns (()) where a: A, a: B { return needsC(x); } -forall a . a:B, a:A => function fromBA(x:a) -> () { +function fromBA(x:a) returns (()) where a: B, a: A { return needsC(x); } -function main() -> () { - return (); +function main() returns (()) { + return; } diff --git a/test/examples/cases/tabled-left-recursive-fail.solc b/test/examples/cases/tabled-left-recursive-fail.solc index 1784286e7..65902bf8a 100644 --- a/test/examples/cases/tabled-left-recursive-fail.solc +++ b/test/examples/cases/tabled-left-recursive-fail.solc @@ -1,13 +1,13 @@ -pragma no-patterson-condition Loop; +pragma solcore noPattersonCondition Loop; -forall a . class a:Loop {} +trait Loop {} -forall a . a:Loop => instance a:Loop {} +impl Loop where a: Loop {} -forall a . a:Loop => function needsLoop(x:a) -> () { - return (); +function needsLoop(x:a) returns (()) where a: Loop { + return; } -function main() -> () { +function main() returns (()) { return needsLoop(0); } diff --git a/test/examples/cases/tabled-mutual-chain.solc b/test/examples/cases/tabled-mutual-chain.solc index d195a58a4..c03714ea7 100644 --- a/test/examples/cases/tabled-mutual-chain.solc +++ b/test/examples/cases/tabled-mutual-chain.solc @@ -1,18 +1,18 @@ -data WrapA(a) = WrapA(a); -data WrapB(a) = WrapB(a); +enum WrapA { WrapA(a) } +enum WrapB { WrapB(a) } -forall a . class a:A {} -forall a . class a:B {} +trait A {} +trait B {} -instance word:A {} +impl A {} -forall a . a:A => instance WrapB(a):B {} -forall a . a:B => instance WrapA(a):A {} +impl B> where a: A {} +impl A> where a: B {} -forall a . a:A => function needsA(x:a) -> () { - return (); +function needsA(x:a) returns (()) where a: A { + return; } -function main() -> () { +function main() returns (()) { return needsA(WrapA(WrapB(0))); } diff --git a/test/examples/cases/tabled-residual-given.solc b/test/examples/cases/tabled-residual-given.solc index 29daa8861..cba6fdb2d 100644 --- a/test/examples/cases/tabled-residual-given.solc +++ b/test/examples/cases/tabled-residual-given.solc @@ -1,18 +1,18 @@ -pragma no-patterson-condition Wanted; +pragma solcore noPattersonCondition Wanted; -forall a . class a:Known {} -forall a . class a:Wanted {} +trait Known {} +trait Wanted {} -forall a . a:Known => instance a:Wanted {} +impl Wanted where a: Known {} -forall a . a:Wanted => function needsWanted(x:a) -> () { - return (); +function needsWanted(x:a) returns (()) where a: Wanted { + return; } -forall a . a:Known => function passKnown(x:a) -> () { +function passKnown(x:a) returns (()) where a: Known { return needsWanted(x); } -function main() -> () { - return (); +function main() returns (()) { + return; } diff --git a/test/examples/cases/td.solc b/test/examples/cases/td.solc index 8b922c9dc..c663c10ad 100644 --- a/test/examples/cases/td.solc +++ b/test/examples/cases/td.solc @@ -1,19 +1,18 @@ -forall abs rep . class abs:Typedef(rep) { - function abs(x:rep) -> abs; - function rep(x:abs) -> rep; +trait Typedef { + function abs(x:rep) returns (abs); + function rep(x:abs) returns (rep); } -forall t. -/* default */ instance t:Typedef(t) { - function abs(x:t) -> t { return x; } - function rep(x:t) -> t { return x; } +/* default */ +impl Typedef { + function abs(x:t) returns (t) { return x; } + function rep(x:t) returns (t) { return x; } } -forall abs rep res. abs:Typedef(rep) => -function lift1ac(f:(rep) -> res, x:abs) -> res { f(Typedef.rep(x)) } +function lift1ac(f:function(rep) internal returns (res), x:abs) returns (res) where abs: Typedef { return f(Typedef.rep(x)); } -forall a. function id(x:a) -> a {x} +function id(x:a) returns (a) {return x;} contract TD { - public function main() -> word { lift1ac(id, 42) } + function main() public returns (word) { return lift1ac(id, 42); } } diff --git a/test/examples/cases/tiamat.solc b/test/examples/cases/tiamat.solc index f51124d89..661f57385 100644 --- a/test/examples/cases/tiamat.solc +++ b/test/examples/cases/tiamat.solc @@ -1,42 +1,41 @@ -data Proxy (a) = Proxy ; -data dict(member, index) = dict(word, Proxy(member), Proxy(index)) ; -data address = address(word) ; -data storage(a) = storage(word) ; - -forall a. -function saddr(s: storage(a)) -> word { - match s { - | storage(a) => return a; - } +enum Proxy { Proxy } +enum dict { dict(word, Proxy, Proxy) } +enum address { address(word) } +enum storage { storage(word) } + +function saddr(s: a storage) returns (word) { + match (s ) { + case storage(a) { return a; + } } } // Untyped Index (access) Proxy -data UIP (m, idx, member) = UIP(m ,idx); +enum UIP { UIP(m, idx) } // Typed Index (access) Proxy -data TIP (m, idx, member) = TIP(m ,idx, Proxy(member)); +enum TIP { TIP(m, idx, Proxy) } -function setbal(ref: storage(dict(address, word)) , src : address, amt: word) -> () { +function setbal(ref: dict storage , src : address, amt: word) returns (()) { /* Based on inference: ref : storage(dict(address, word)) => ref[src] : storage(word) assuming src is of the right type */ - let tip = TIP(ref, src, Proxy:Proxy(word)); + let tip = TIP(ref, src, Proxy as Proxy); Assign.assign(LVA.acc(tip), amt); } -function setAllowance(ref: storage(dict(address, dict(address, word))), owner : address, spender : address, amt : word) -> () { +function setAllowance(ref: dict> storage, owner : address, spender : address, amt : word) returns (()) { - let tip1 : TIP(storage(dict(address, dict(address, word))), address, dict(address, word)) - = TIP(ref, owner, Proxy:Proxy(dict(address, word) )); - let ref2 : storage(dict(address,word)) = LVA.acc(tip1); - let tip2 : TIP(storage(dict(address, word)), address, word) - = TIP(ref2, spender, Proxy:Proxy(word)); - let ref3 : storage(word) = LVA.acc(tip2); + let tip1 : TIP> storage, address, dict> + = TIP(ref, owner, Proxy as Proxy>); + let ref2 : dict storage = LVA.acc(tip1); + let tip2 : TIP storage, address, word> + = TIP(ref2, spender, Proxy as Proxy); + let ref3 : word storage = LVA.acc(tip2); Assign.assign(ref3, amt); } -function getAllowance(ref: storage(dict(address, dict(address, word))), owner : address, spender : address) -> word { +function getAllowance(ref: dict> storage, owner : address, spender : address) returns (word) { /* let tip : TIP(storage(dict(address, dict(address, word))), address, dict(address, word)) = TIP(ref, owner, Proxy:Proxy(dict(address, word) )); @@ -50,85 +49,77 @@ function getAllowance(ref: storage(dict(address, dict(address, word))), owner : TIP (ref , owner - , Proxy:Proxy(dict(address, word) ) + , Proxy as Proxy> ) /* tip : TIP(storage(dict(address, dict(address, word))), address, dict(address, word)) */ ) /* ref2 : storage(dict(address,word)) */ , spender - , Proxy:Proxy(word) + , Proxy as Proxy ) /* tip2 : TIP(storage(dict(address, word)), address, word) */ ); } -forall self memberRefType. -class self:LVA(memberRefType) { - function acc(x:self) -> memberRefType; +trait LVA { + function acc(x:self) returns (memberRefType); } -forall self member. -class self:RVA(member) { - function acc(x:self) -> member; +trait RVA { + function acc(x:self) returns (member); } -forall index member. - instance TIP(storage(dict(index,member)), index, member):LVA(storage(member)) { - function acc(x:TIP(storage(dict(index,member)), index, member)) -> storage(member) { +impl LVA storage, index, member>, member storage> { + function acc(x:TIP storage, index, member>) returns (member storage) { return storage(42); } } -forall index member. - instance UIP(storage(dict(index,member)), index, member):LVA(storage(member)) { - function acc(x:UIP(storage(dict(index,member)), index, member)) -> storage(member) { +impl LVA storage, index, member>, member storage> { + function acc(x:UIP storage, index, member>) returns (member storage) { return storage(42); } } -forall self. -class self:StorageType { - function sload(ptr:word) -> self; - function store(ptr:word, value:self) -> (); +trait StorageType { + function sload(ptr:word) returns (self); + function store(ptr:word, value:self) returns (()); } -instance word:StorageType { - function sload(ptr:word) -> word { +impl StorageType { + function sload(ptr:word) returns (word) { let r:word; assembly { r := sload(ptr) } return r; } - function store(ptr:word, value:word) -> () { + function store(ptr:word, value:word) returns (()) { assembly { sstore(ptr, value) } } } -forall index member. member:StorageType => - instance TIP(storage(dict(index,member)), index, member):RVA(member) { - function acc(x:TIP(storage(dict(index,member)), index, member)) -> member { +impl RVA storage, index, member>, member> where member: StorageType { + function acc(x:TIP storage, index, member>) returns (member) { let addr = saddr(LVA.acc(x)); return StorageType.sload(addr); } } -forall lhs rhs. -class lhs:Assign(rhs) { - function assign(l:lhs, r:rhs) -> (); +trait Assign { + function assign(l:lhs, r:rhs) returns (()); } -forall a. a:StorageType => -instance storage(a):Assign(a) { - function assign(l:storage(a), r:a) -> () { +impl Assign where a: StorageType { + function assign(l:a storage, r:a) returns (()) { StorageType.store(saddr(l), r); } } contract Tiamat { - public function main() -> word { - let allowances : storage(dict(address, dict(address, word))); + function main() public returns (word) { + let allowances : dict> storage; let src = address(17); setAllowance(allowances, address(1),address(2), 666); return getAllowance(allowances, address(1),address(2)); diff --git a/test/examples/cases/toplevel-fallback.solc b/test/examples/cases/toplevel-fallback.solc index 850ecf869..d19987276 100644 --- a/test/examples/cases/toplevel-fallback.solc +++ b/test/examples/cases/toplevel-fallback.solc @@ -1,3 +1,3 @@ // A `fallback` may only be declared inside a contract. // At the top level this must fail to parse. -fallback() -> () {} +fallback() external {} diff --git a/test/examples/cases/tuple-trick.solc b/test/examples/cases/tuple-trick.solc index 0de688ce9..639d1dc7f 100644 --- a/test/examples/cases/tuple-trick.solc +++ b/test/examples/cases/tuple-trick.solc @@ -1,40 +1,40 @@ -pragma no-coverage-condition Nth; +pragma solcore noCoverageCondition Nth; -data Zero; -data Succ(a); +enum Zero {} +enum Succ {} -data Proxy(a) = Proxy; +enum Proxy { Proxy } -forall a b c . class a : Nth(b,c) { - function nth (x : Proxy(a), y : b) -> c; +trait Nth { + function nth (x : Proxy, y : b) returns (c); } -forall a b . instance Zero : Nth((a,b), a) { - function nth (x : Proxy(Zero), y : (a,b)) -> a { - match y { - | (a, b) => return a ; - } +impl Nth { + function nth (x : Proxy, y : (a, b)) returns (a) { + match (y ) { + case (a, b) { return a ; + } } } } -forall n a b c . n : Nth (b,c) => instance Succ(n) : Nth ((a,b), c) { - function nth (x : Proxy(Succ(n)), y : (a,b)) -> c { - match y { - | (a,b) => return Nth.nth(Proxy : Proxy(n), b); - } +impl Nth, (a, b), c> where n: Nth { + function nth (x : Proxy>, y : (a, b)) returns (c) { + match (y ) { + case (a,b) { return Nth.nth(Proxy as Proxy, b); + } } } } contract C { - public function id (x : word) -> word { + function id (x : word) public returns (word) { return x; } - public function main () -> () { + function main () public returns (()) { let p : (word, word, word, ()); - let x : word = Nth.nth(Proxy : Proxy(Zero), p); - let y : word = Nth.nth(Proxy : Proxy(Succ(Zero)), p); - let z : word = Nth.nth(Proxy : Proxy(Succ(Succ(Zero))), p); + let x : word = Nth.nth(Proxy as Proxy, p); + let y : word = Nth.nth(Proxy as Proxy>, p); + let z : word = Nth.nth(Proxy as Proxy>>, p); id(z); } } diff --git a/test/examples/cases/tuva.solc b/test/examples/cases/tuva.solc index 31bb144bc..5b7c5a477 100644 --- a/test/examples/cases/tuva.solc +++ b/test/examples/cases/tuva.solc @@ -7,35 +7,33 @@ - Assign class */ -import std.{*} hiding {LValueIdxAccess, RValueIdxAccess, readStorage}; -import std.{Typedef, storage, mapping, address, hash2, StorageType, Assign}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +import {*} from std hiding {LValueIdxAccess, RValueIdxAccess, readStorage}; +import {Typedef, storage, mapping, address, hash2, StorageType, Assign} from std; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; -forall col_idx val . class col_idx:RValueIdxAccess(val) { - function lookup(ci : col_idx) -> val; +trait RValueIdxAccess { + function lookup(ci : col_idx) returns (val); } -forall col_idx ref . class col_idx:LValueIdxAccess(ref) { - function lookup(ci : col_idx) -> ref; +trait LValueIdxAccess { + function lookup(ci : col_idx) returns (ref); } -forall i a . i:Typedef(word) => -instance (storage(mapping(i,a)), i): LValueIdxAccess(storage(a)) { - function lookup(xi : (storage(mapping(i,a)), i)) -> storage(a) { +impl LValueIdxAccess<(mapping(i => a) storage, i), a storage> where i: Typedef { + function lookup(xi : (mapping(i => a) storage, i)) returns (a storage) { match(xi) { - | (x, i) => return storage(hash2(Typedef.rep(x), Typedef.rep(i))); - } + case (x, i) { return storage(hash2(Typedef.rep(x), Typedef.rep(i))); + } } // return storage(42); // FIXME: hash2(x,i); } } -forall i a . a:StorageType, i:Typedef(word) => -instance (storage(mapping(i,a)), i): RValueIdxAccess(a) { - function lookup(xi : (storage(mapping(i,a)), i)) -> a { +impl RValueIdxAccess<(mapping(i => a) storage, i), a> where a: StorageType, i: Typedef { + function lookup(xi : (mapping(i => a) storage, i)) returns (a) { /* match(xi) { | (x, i) => return StorageType.load(hash2(Typedef.rep(x), Typedef.rep(i))); @@ -45,26 +43,23 @@ instance (storage(mapping(i,a)), i): RValueIdxAccess(a) { } } -forall a. a:StorageType => -function readStorage(x:storage(a)) -> a { +function readStorage(x:a storage) returns (a) where a: StorageType { return StorageType.load(Typedef.rep(x)); } -forall r a. r: RValueIdxAccess(a) => -function idx_rval(x:r) -> a { +function idx_rval(x:r) returns (a) where r: RValueIdxAccess { return RValueIdxAccess.lookup(x); } -forall r a. r: LValueIdxAccess(a) => -function idx_lval(x:r) -> a { +function idx_lval(x:r) returns (a) where r: LValueIdxAccess { return LValueIdxAccess.lookup(x); } contract TestTuva { - public function main() -> word { - let balances : storage(mapping(address, word)); - let allowances : storage(mapping(address, mapping(address, word) )); - let ref1 : storage(word) = idx_lval( (balances, address(17)) ); + function main() public returns (word) { + let balances : mapping(address => word) storage; + let allowances : mapping(address => mapping(address => word)) storage; + let ref1 : word storage = idx_lval( (balances, address(17)) ); Assign.assign(idx_lval( (balances, address(1)) ), 1337); let ref2a // : storage( mapping(address, word) ) // omitting this type makes instance resolution fail diff --git a/test/examples/cases/tyexp.solc b/test/examples/cases/tyexp.solc index c3fec25ce..ce50bf1d5 100644 --- a/test/examples/cases/tyexp.solc +++ b/test/examples/cases/tyexp.solc @@ -1,4 +1,4 @@ -function main () -> word { - let y = 0 : word ; +function main () returns (word) { + let y = 0 as word ; return y; } diff --git a/test/examples/cases/type-synonym-arg.solc b/test/examples/cases/type-synonym-arg.solc index 876e5bdae..8a5edefc0 100644 --- a/test/examples/cases/type-synonym-arg.solc +++ b/test/examples/cases/type-synonym-arg.solc @@ -1,10 +1,10 @@ -type W = word; +type W is word; -function f(x:W) -> W { x } +function f(x:W) returns (W) { return x; } contract C { - public function main () -> word { + function main () public returns (word) { return f(42); } } diff --git a/test/examples/cases/typedef.solc b/test/examples/cases/typedef.solc index 1421e6910..f16bd89be 100644 --- a/test/examples/cases/typedef.solc +++ b/test/examples/cases/typedef.solc @@ -1,9 +1,8 @@ -forall self underlyingType . class self:Typedef(underlyingType) { - function rep(x:self) -> underlyingType; - function abs(x:underlyingType) -> self; +trait Typedef { + function rep(x:self) returns (underlyingType); + function abs(x:underlyingType) returns (self); } -forall t . t : Typedef((word,(word,word))) => - function tripleFun(x:t) -> (word, (word, word)) { +function tripleFun(x:t) returns ((word, (word, word))) where t: Typedef<(word, (word, word))> { return Typedef.rep(x); } diff --git a/test/examples/cases/ufcs-no-conflict.solc b/test/examples/cases/ufcs-no-conflict.solc index 78990e052..9953ba246 100644 --- a/test/examples/cases/ufcs-no-conflict.solc +++ b/test/examples/cases/ufcs-no-conflict.solc @@ -1,4 +1,4 @@ -import std.{*}; +import {*} from std; // Regression test: the UFCS (receiver-style) method-call rewriting in // NameResolution must coexist with the other uses of dot syntax without @@ -14,18 +14,17 @@ import std.{*}; // type name (`Color.Red`) is handled by the earlier qualified-name cases and // never reaches the UFCS rule. -forall a. -class a : Combiner { - function combine(x : a, y : word) -> word; +trait Combiner { + function combine(x : a, y : word) returns (word); } -instance word : Combiner { - function combine(x : word, y : word) -> word { +impl Combiner { + function combine(x : word, y : word) returns (word) { return y; } } -data Color = Red | Green; +enum Color { Red, Green } contract UfcsNoConflict { val : word; @@ -33,19 +32,19 @@ contract UfcsNoConflict { constructor() {} // UFCS receiver call on a contract field. - public function viaUfcs(z : word) -> word { + function viaUfcs(z : word) public returns (word) { return val.combine(z); } // The explicit qualified class call for the same method: NOT rewritten by // UFCS (receiver is the class name `Combiner`, not a field). - public function viaQualified(z : word) -> word { + function viaQualified(z : word) public returns (word) { return Combiner.combine(val, z); } // A dotted constructor and a bare field read still resolve normally // alongside the UFCS rule. - public function dottedConstructorAndFieldRead() -> word { + function dottedConstructorAndFieldRead() public returns (word) { let c : Color = Color.Red; return val; } diff --git a/test/examples/cases/uintdesugared.solc b/test/examples/cases/uintdesugared.solc index 47365ee2a..7a4a25e1b 100644 --- a/test/examples/cases/uintdesugared.solc +++ b/test/examples/cases/uintdesugared.solc @@ -28,7 +28,7 @@ contract Uint { */ -function addW(x : word, y : word) -> word { +function addW(x : word, y : word) returns (word) { let res: word; assembly { res := add(x, y) @@ -36,7 +36,7 @@ function addW(x : word, y : word) -> word { return res; } -function subW(x : word, y : word) -> word { +function subW(x : word, y : word) returns (word) { let res: word; assembly { res := sub(x, y) @@ -44,7 +44,7 @@ function subW(x : word, y : word) -> word { return res; } -function addU(x : uint, y : uint) -> uint { +function addU(x : uint, y : uint) returns (uint) { let res: word; let xw : word = Num.toWord(x); let yw : word = Num.toWord(y); @@ -54,7 +54,7 @@ function addU(x : uint, y : uint) -> uint { return uint(res); } -function hash1(x: word) -> word { +function hash1(x: word) returns (word) { let result: word = 0; assembly { mstore(0, x) @@ -63,7 +63,7 @@ function hash1(x: word) -> word { return result; } -function hash2(x: word, y: word) -> word { +function hash2(x: word, y: word) returns (word) { let result: word = 0; assembly { mstore(0, x) @@ -73,34 +73,33 @@ function hash2(x: word, y: word) -> word { return result; } -forall a. -class a:Num { - function toWord(x:a) -> word; - function fromWord(x:word) -> a; - function add(x:a, y:a) -> a; - function sub(x:a, y:a) -> a; +trait Num { + function toWord(x:a) returns (word); + function fromWord(x:word) returns (a); + function add(x:a, y:a) returns (a); + function sub(x:a, y:a) returns (a); } -instance word:Num { - function toWord(x:word) -> word { return x; } - function fromWord(x:word) -> word { return x; } - function add(x:word, y:word) -> word { return addW(x, y); } - function sub(x:word, y:word) -> word { return addW(x, y); } +impl Num { + function toWord(x:word) returns (word) { return x; } + function fromWord(x:word) returns (word) { return x; } + function add(x:word, y:word) returns (word) { return addW(x, y); } + function sub(x:word, y:word) returns (word) { return addW(x, y); } } -data uint = uint(word); +enum uint { uint(word) } -instance uint:Num { - function toWord(x:uint) -> word +impl Num { + function toWord(x:uint) returns (word) { - match x { - | uint(y) => return y; - } + match (x ) { + case uint(y) { return y; + } } } - function fromWord(x:word) -> uint { return uint(x); } - function add(x:uint, y:uint) -> uint { return uint(addW(Num.toWord(x), Num.toWord(y))); } - function sub(x:uint, y:uint) -> uint { return uint(subW(Num.toWord(x), Num.toWord(y))); } + function fromWord(x:word) returns (uint) { return uint(x); } + function add(x:uint, y:uint) returns (uint) { return uint(addW(Num.toWord(x), Num.toWord(y))); } + function sub(x:uint, y:uint) returns (uint) { return uint(subW(Num.toWord(x), Num.toWord(y))); } } /* // this breaks the Paterson condition @@ -116,10 +115,9 @@ instance a:Num { /////// Construction -forall abs rep. -class abs:Typedef(rep) { - function rep(x:abs) -> rep; - function abs(x:rep) -> abs; +trait Typedef { + function rep(x:abs) returns (rep); + function abs(x:rep) returns (abs); } @@ -132,97 +130,91 @@ forall a } */ -instance word:Typedef(word) { - function rep(x:word) -> word { return x; } - function abs(x:word) -> word { return x; } +impl Typedef { + function rep(x:word) returns (word) { return x; } + function abs(x:word) returns (word) { return x; } } -instance uint:Typedef(word) { - function rep(x:uint) -> word { - match x { - | uint(y) => return y; - } +impl Typedef { + function rep(x:uint) returns (word) { + match (x ) { + case uint(y) { return y; + } } } - function abs(x:word) -> uint { + function abs(x:word) returns (uint) { return uint(x); } } -data address = address(word); +enum address { address(word) } -instance address:Typedef(word) { - function rep(x:address) -> word { - match x { - | address(y) => return y; - } +impl Typedef { + function rep(x:address) returns (word) { + match (x ) { + case address(y) { return y; + } } } - function abs(x:word) -> address { + function abs(x:word) returns (address) { return address(x); } } -data storage(a) = storage(word); -data ContractStorage(cxt) = ContractStorage(cxt); +enum storage { storage(word) } +enum ContractStorage { ContractStorage(cxt) } -data storageRef(a) = storageRef(word); -data Proxy(a) = Proxy; +enum storageRef { storageRef(word) } +enum Proxy { Proxy } -data mapRef(a) = mapRef(word); //ref to a map elem +enum mapRef { mapRef(word) } //ref to a map elem // data memoryRef(a) = memoryRef(word); -forall a. -instance storage(a):Typedef(word) { - function rep(x:storage(a)) -> word { - match x { - | storage(y) => return y; - } +impl Typedef { + function rep(x:a storage) returns (word) { + match (x ) { + case storage(y) { return y; + } } } - function abs(x:word) -> storage(a) { + function abs(x:word) returns (a storage) { return storage(x); } } -forall a. -instance storageRef(a):Typedef(word) { - function rep(x:storageRef(a)) -> word { - match x { - | storageRef(y) => return y; - } +impl Typedef, word> { + function rep(x:storageRef) returns (word) { + match (x ) { + case storageRef(y) { return y; + } } } - function abs(x:word) -> storageRef(a) { + function abs(x:word) returns (storageRef) { return storageRef(x); } } -forall lhs rhs. -class lhs:Assign(rhs) { - function assign(l:lhs, r:rhs) -> (); +trait Assign { + function assign(l:lhs, r:rhs) returns (()); } -data ref(a) = ref(a); +enum ref { ref(a) } -forall a. -instance ref(a):Assign(a) { - function assign(l:ref(a), r:a) -> () { +impl Assign, a> { + function assign(l:ref, r:a) returns (()) { // builtin "stack store" - return (); + return; } } -forall self. -class self:StorageType { - function sload(ptr:word) -> self; - function store(ptr:word, value:self) -> (); +trait StorageType { + function sload(ptr:word) returns (self); + function store(ptr:word, value:self) returns (()); } -forall self. -class self:StorageSize { - function size(x:Proxy(self)) -> word; +trait StorageSize { + function size(x:Proxy) returns (word); } -function sload_(x:word) -> word { +function sload_(x:word) returns (word) { let res: word; assembly { res := sload(x) @@ -230,80 +222,73 @@ function sload_(x:word) -> word { return res; } -function sstore_(a:word, v:word) -> () { +function sstore_(a:word, v:word) returns (()) { assembly { sstore(a,v) } } -instance word:StorageType { - function sload(ptr:word) -> word { +impl StorageType { + function sload(ptr:word) returns (word) { let r:word; assembly { r := sload(ptr) } return r; } - function store(ptr:word, value:word) -> () { + function store(ptr:word, value:word) returns (()) { assembly { sstore(ptr, value) } } } -instance uint:StorageType { - function sload(ptr:word) -> uint { - return Typedef.abs(sload_(ptr)):uint; // type annotation needed due to a typechecker bug +impl StorageType { + function sload(ptr:word) returns (uint) { + return Typedef.abs(sload_(ptr)) as uint; // type annotation needed due to a typechecker bug } - function store(ptr:word, value:uint) -> () { + function store(ptr:word, value:uint) returns (()) { return sstore_(ptr, Typedef.rep(value)); } } -instance address:StorageType { - function sload(ptr:word) -> address { - return Typedef.abs(sload_(ptr)):address; // type annotation needed due to a typechecker bug +impl StorageType
{ + function sload(ptr:word) returns (address) { + return Typedef.abs(sload_(ptr)) as address; // type annotation needed due to a typechecker bug } - function store(ptr:word, value:address) -> () { + function store(ptr:word, value:address) returns (()) { return sstore_(ptr, Typedef.rep(value)); } } -forall a . a : StorageType => instance storageRef(a):Assign(a) { - function assign(l:storageRef(a), y:a) -> () { +impl Assign, a> where a: StorageType { + function assign(l:storageRef, y:a) returns (()) { StorageType.store(Typedef.rep(l), y); } } -forall self fieldType offsetType. -class self:CStructField(fieldType, offsetType) {} -data StructField(structType, fieldSelector) = StructField(structType); +trait CStructField {} +enum StructField { StructField(structType) } -data MemberAccessProxy(a, field, offset) = MemberAccessProxy(a, field); +enum MemberAccessProxy { MemberAccessProxy(a, field) } -forall a field offset . -function memberAccessD1(x:MemberAccessProxy(a, field, offset)) -> a { - match x { - | MemberAccessProxy(y,z) => return y; - } +function memberAccessD1(x:MemberAccessProxy) returns (a) { + match (x ) { + case MemberAccessProxy(y,z) { return y; + } } } -forall self memberRefType. -class self:LValueMemberAccess(memberRefType) { - function memberAccess(x:self) -> memberRefType; +trait LValueMemberAccess { + function memberAccess(x:self) returns (memberRefType); } -forall self memberValueType . -class self:RValueMemberAccess(memberValueType) { - function memberAccess(x:self) -> memberValueType; +trait RValueMemberAccess { + function memberAccess(x:self) returns (memberValueType); } -forall structType fieldSelector fieldType offsetType - . StructField(structType, fieldSelector):CStructField(fieldType, offsetType) - , offsetType:StorageSize - => instance MemberAccessProxy(storage(structType), fieldSelector, offsetType):LValueMemberAccess(storageRef(fieldType)) { - function memberAccess(x:MemberAccessProxy(storage(structType), fieldSelector, offsetType)) -> storageRef(fieldType) { +impl LValueMemberAccess, storageRef> where StructField: CStructField, offsetType: StorageSize { + function memberAccess(x:MemberAccessProxy) returns (storageRef) { let ptr:word = Typedef.rep(memberAccessD1(x)); - let size:word = StorageSize.size(Proxy:Proxy(offsetType)); + let size:word = StorageSize.size(Proxy as Proxy); assembly { ptr := add(ptr, size) } @@ -311,26 +296,26 @@ forall structType fieldSelector fieldType offsetType } } -instance ():StorageSize { - function size(x:Proxy(())) -> word { +impl StorageSize<()> { + function size(x:Proxy<()>) returns (word) { return 0; } } -instance word:StorageSize { - function size(x:Proxy(word)) -> word { +impl StorageSize { + function size(x:Proxy) returns (word) { return 1; } } -instance uint:StorageSize { - function size(x:Proxy(uint)) -> word { +impl StorageSize { + function size(x:Proxy) returns (word) { return 1; } } -instance address:StorageSize { - function size(x:Proxy(address)) -> word { +impl StorageSize
{ + function size(x:Proxy
) returns (word) { return 1; } } @@ -346,10 +331,10 @@ forall a b . a:Typedef(b), b:StorageSize } */ -forall a b . a:StorageSize, b:StorageSize => instance (a,b):StorageSize { - function size(x:Proxy((a,b))) -> word { - let a_sz:word = StorageSize.size(Proxy:Proxy(a)); - let b_sz:word = StorageSize.size(Proxy:Proxy(b)); +impl StorageSize<(a, b)> where a: StorageSize, b: StorageSize { + function size(x:Proxy<(a, b)>) returns (word) { + let a_sz:word = StorageSize.size(Proxy as Proxy); + let b_sz:word = StorageSize.size(Proxy as Proxy); assembly { a_sz := add(a_sz, b_sz) } @@ -357,20 +342,17 @@ forall a b . a:StorageSize, b:StorageSize => instance (a,b):StorageSize { } } -pragma no-patterson-condition RValueMemberAccess; // this is due to ContractStorage(cxt); probably not needed once we have local instances -pragma no-coverage-condition MemberAccessProxy, LValueMemberAccess, RValueMemberAccess; +pragma solcore noPattersonCondition RValueMemberAccess; // this is due to ContractStorage(cxt); probably not needed once we have local instances +pragma solcore noCoverageCondition MemberAccessProxy, LValueMemberAccess, RValueMemberAccess; // ------------------------------------------------------------------ // Contract field access // ------------------------------------------------------------------ -forall cxt fieldSelector fieldType offsetType - . StructField(ContractStorage(cxt), fieldSelector):CStructField(fieldType, offsetType) - , offsetType:StorageSize - => instance MemberAccessProxy(ContractStorage(cxt), fieldSelector, offsetType):LValueMemberAccess(storageRef(fieldType)) { - function memberAccess(x:MemberAccessProxy(ContractStorage(cxt), fieldSelector, offsetType)) -> storageRef(fieldType) { +impl LValueMemberAccess, fieldSelector, offsetType>, storageRef> where StructField, fieldSelector>: CStructField, offsetType: StorageSize { + function memberAccess(x:MemberAccessProxy, fieldSelector, offsetType>) returns (storageRef) { let ptr:word = 0x100; // forge uses at least 1 storage slot - let offsetSize:word = StorageSize.size(Proxy:Proxy(offsetType)); + let offsetSize:word = StorageSize.size(Proxy as Proxy); assembly { ptr := add(ptr, offsetSize) @@ -379,15 +361,11 @@ forall cxt fieldSelector fieldType offsetType } } -forall cxt fieldSelector fieldType offsetType - . StructField(ContractStorage(cxt), fieldSelector):CStructField(fieldType, offsetType) - , fieldType:StorageType - , offsetType:StorageSize - => instance MemberAccessProxy(ContractStorage(cxt), fieldSelector, offsetType):RValueMemberAccess(fieldType) { - function memberAccess(x:MemberAccessProxy(ContractStorage(cxt), fieldSelector, offsetType)) -> fieldType { +impl RValueMemberAccess, fieldSelector, offsetType>, fieldType> where StructField, fieldSelector>: CStructField, fieldType: StorageType, offsetType: StorageSize { + function memberAccess(x:MemberAccessProxy, fieldSelector, offsetType>) returns (fieldType) { let ptr:word = 0x100; - let offsetSize:word = StorageSize.size(Proxy:Proxy(offsetType)); - return StorageType.sload(addW(ptr, offsetSize)):fieldType; + let offsetSize:word = StorageSize.size(Proxy as Proxy); + return StorageType.sload(addW(ptr, offsetSize)) as fieldType; } } @@ -408,55 +386,52 @@ forall cxt fieldSelector fieldType offsetType // Indexed access // ------------------------------------------------------------------ -data mapping(index, member) = mapping(word); +enum mapping { mapping(word) } -forall member index . instance mapping(index, member):Typedef(word) { - function rep(x:mapping(index, member)) -> word { - match x { - | mapping(y) => return y; - } +impl Typedef member), word> { + function rep(x:mapping(index => member)) returns (word) { + match (x ) { + case mapping(y) { return y; + } } } - function abs(x:word) -> mapping(index,member) { + function abs(x:word) returns (mapping(index => member)) { return mapping(x); } } // cf https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html#mappings-and-dynamic-arrays -forall index member . -instance mapping(index, member):StorageSize { - function size(x:Proxy(mapping(index, member))) -> word { +impl StorageSize member)> { + function size(x:Proxy member)>) returns (word) { return 1; } } -data IndexAccessProxy(map, index, member) = IndexAccessProxy(map, index); +enum IndexAccessProxy { IndexAccessProxy(map, index) } -forall map index member. index:Typedef(word), map:Typedef(word) -=> instance IndexAccessProxy(map, index, member):LValueMemberAccess(storageRef(member)) { - function memberAccess(x:IndexAccessProxy(map, index, member)) -> storageRef(member) { +impl LValueMemberAccess, storageRef> where index: Typedef, map: Typedef { + function memberAccess(x:IndexAccessProxy) returns (storageRef) { return storageRef(indexStorageSlot(x)); } } -forall map index member . index:Typedef(word), member:StorageType, map:Typedef(word) -=> instance IndexAccessProxy(map, index, member):RValueMemberAccess(member) { - function memberAccess(x:IndexAccessProxy(map, index, member)) -> member { +impl RValueMemberAccess, member> where index: Typedef, member: StorageType, map: Typedef { + function memberAccess(x:IndexAccessProxy) returns (member) { let slot:word = indexStorageSlot(x); return StorageType.sload(slot); } } -forall index map member. map:Typedef(word), index:Typedef(word) => function indexStorageSlot(x:IndexAccessProxy(map, index, member)) -> word +function indexStorageSlot(x:IndexAccessProxy) returns (word) //function indexStorageSlot(x) -{ - match x { - | IndexAccessProxy(map, i) => + where map: Typedef, index: Typedef { + match (x ) { + case IndexAccessProxy(map, i) { let mapptr:word = Typedef.rep(map); let rawidx:word = Typedef.rep(i); let loc:word = hash2(mapptr, rawidx); return loc; - } + } } } /* @@ -472,41 +447,40 @@ forall index map member. map:Typedef(word), index:Typedef(word) } */ -forall a b. a:RValueMemberAccess(b) => -function rval(x:a) -> b { +function rval(x:a) returns (b) where a: RValueMemberAccess { return RValueMemberAccess.memberAccess(x); } -data UintCxt = UintCxt ; -data reserved_sel = reserved_sel ; -instance StructField(ContractStorage(UintCxt), reserved_sel) :CStructField(word, ()) { +enum UintCxt { UintCxt } +enum reserved_sel { reserved_sel } +impl CStructField, reserved_sel>, word, ()> { } -data owner_sel = owner_sel ; -instance StructField(ContractStorage(UintCxt), owner_sel) :CStructField(address, (word, ())) { +enum owner_sel { owner_sel } +impl CStructField, owner_sel>, address, (word, ())> { } -data decimals_sel = decimals_sel ; -instance StructField(ContractStorage(UintCxt), decimals_sel) :CStructField(uint, (word, (address, ()))) { +enum decimals_sel { decimals_sel } +impl CStructField, decimals_sel>, uint, (word, (address, ()))> { } -data totalSupply_sel = totalSupply_sel ; -instance StructField(ContractStorage(UintCxt), totalSupply_sel) :CStructField(uint, (word, (address, (uint, ())))) { +enum totalSupply_sel { totalSupply_sel } +impl CStructField, totalSupply_sel>, uint, (word, (address, (uint, ())))> { } -data balances_sel = balances_sel ; -instance StructField(ContractStorage(UintCxt), balances_sel) :CStructField(mapping(address, uint), (word, (address, (uint, (uint, ()))))) { +enum balances_sel { balances_sel } +impl CStructField, balances_sel>, mapping(address => uint), (word, (address, (uint, (uint, ()))))> { } contract Uint { - public function mint (amount : uint) -> () { + function mint (amount : uint) public returns (()) { Assign.assign(LValueMemberAccess.memberAccess(IndexAccessProxy(LValueMemberAccess.memberAccess(MemberAccessProxy(ContractStorage(UintCxt), balances_sel)), rval(MemberAccessProxy(ContractStorage(UintCxt), owner_sel)))), Num.add(rval(IndexAccessProxy(LValueMemberAccess.memberAccess(MemberAccessProxy(ContractStorage(UintCxt), balances_sel)), rval(MemberAccessProxy(ContractStorage(UintCxt), owner_sel)))), amount)); Assign.assign(LValueMemberAccess.memberAccess(MemberAccessProxy(ContractStorage(UintCxt), totalSupply_sel)), Num.add(rval(MemberAccessProxy(ContractStorage(UintCxt), totalSupply_sel)), amount)); } - public function init () -> () { + function init () public returns (()) { Assign.assign(LValueMemberAccess.memberAccess(MemberAccessProxy(ContractStorage(UintCxt), owner_sel)), address(81985529216486895)); Assign.assign(LValueMemberAccess.memberAccess(MemberAccessProxy(ContractStorage(UintCxt), decimals_sel)), Num.fromWord(18)); } - public function main () -> uint { + function main () public returns (uint) { init(); mint(uint(1000)); mint(uint(1000)); - return rval(IndexAccessProxy(LValueMemberAccess.memberAccess(MemberAccessProxy(ContractStorage(UintCxt), balances_sel)), rval(MemberAccessProxy(ContractStorage(UintCxt), owner_sel)))) : uint; + return rval(IndexAccessProxy(LValueMemberAccess.memberAccess(MemberAccessProxy(ContractStorage(UintCxt), balances_sel)), rval(MemberAccessProxy(ContractStorage(UintCxt), owner_sel)))) as uint; } } diff --git a/test/examples/cases/unbound-instance-var.solc b/test/examples/cases/unbound-instance-var.solc index 7b2e1a8bd..532c0762c 100644 --- a/test/examples/cases/unbound-instance-var.solc +++ b/test/examples/cases/unbound-instance-var.solc @@ -1,16 +1,15 @@ -forall self. -class self:C { - function size(x:self) -> word; +trait C { + function size(x:self) returns (word); } -instance ():C { - function size(x:()) -> word { +impl C<()> { + function size(x:()) returns (word) { return 0; } } -instance uint:C { - function size(x:uint) -> word { +impl C { + function size(x:uint) returns (word) { return 1; } } diff --git a/test/examples/cases/unconstrained-instance.solc b/test/examples/cases/unconstrained-instance.solc index 6e838cc5e..f65889bff 100644 --- a/test/examples/cases/unconstrained-instance.solc +++ b/test/examples/cases/unconstrained-instance.solc @@ -1,23 +1,23 @@ -data memory(t) = memory(word); +enum memory { memory(word) } -class t:ValueTy { - function rep(x:t) -> word; +trait ValueTy { + function rep(x:t) returns (word); } -instance memory(t) : ValueTy { - function rep(x: memory(t)) -> word { - match x { - | memory(w) => return w; - }; +impl ValueTy { + function rep(x: t memory) returns (word) { + match (x ) { + case memory(w) { return w; + } } } } -class ref:Ref(deref) { - function store(loc: ref, value: deref) -> (); +trait Ref { + function store(loc: ref, value: deref) returns (()); } -instance memory(t) : Ref(t) { - function store(loc: memory(t), value: t) -> () { +impl Ref { + function store(loc: t memory, value: t) returns (()) { // We don't have a `ValueTy` bound on `t` anywhere, so this should raise a type error... let vw = ValueTy.rep(value); } diff --git a/test/examples/cases/undefined.solc b/test/examples/cases/undefined.solc index 38e05d12e..1f35c4c3d 100644 --- a/test/examples/cases/undefined.solc +++ b/test/examples/cases/undefined.solc @@ -1,13 +1,13 @@ -forall any.function undefined() -> any { +function undefined() returns (any) { assembly { revert(0,0) } } -function useWord(w:word) -> () {} +function useWord(w:word) returns (()) {} contract Magic { - public function main() -> () { + function main() public returns (()) { useWord(undefined()); } } diff --git a/test/examples/cases/unit.solc b/test/examples/cases/unit.solc index 98e93ae72..f065644f2 100644 --- a/test/examples/cases/unit.solc +++ b/test/examples/cases/unit.solc @@ -1,33 +1,33 @@ contract Unit { -public function one (x : ()) -> word { +function one (x : ()) public returns (word) { return 1; } -public function unitVal() -> () { - return (); +function unitVal() public returns (()) { + return; } -public function unitMatch (x : ()) -> word { - match x { - | () => return 1; - } +function unitMatch (x : ()) public returns (word) { + match (x ) { + case () { return 1; + } } } -public function foo (x : word) -> () { - return (); +function foo (x : word) public returns (()) { + return; } -public function main() -> word { +function main() public returns (word) { return unitMatch(foo(one(unitVal()))); } } -forall a . class a : Def { - function def () -> a ; +trait Def { + function def () returns (a) ; } -instance () : Def { - function def() -> () { - return (); +impl Def<()> { + function def() returns (()) { + return; } } diff --git a/test/examples/cases/user-op-lambda.solc b/test/examples/cases/user-op-lambda.solc index ec2d8cd53..6fc5f9d96 100644 --- a/test/examples/cases/user-op-lambda.solc +++ b/test/examples/cases/user-op-lambda.solc @@ -1,20 +1,19 @@ -import std.{*}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +import {*} from std; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; -infixl 70 (^^) => pow; -function pow(b : word, e : word) -> word { +function pow(b : word, e : word) returns (word) { let r : word; assembly { r := exp(b, e) } return r; } contract UserOpLambda { - function main() -> word { - // operator (^^) used inside a lambda body - let f = lam(x : word) -> word { return x ^^ 3; }; + function main() returns (word) { + // helper call used inside a lambda body + let f = lam(x : word) returns (word) { return pow(x, 3); }; return f(2); } } diff --git a/test/examples/cases/vartyped.solc b/test/examples/cases/vartyped.solc index 3b89a402d..a8f13470e 100644 --- a/test/examples/cases/vartyped.solc +++ b/test/examples/cases/vartyped.solc @@ -1,4 +1,4 @@ function foo () { - let f : (word) -> word = lam (x) { return x ; } ; + let f : function(word) internal returns (word) = lam (x) { return x ; } ; return f(1); } diff --git a/test/examples/cases/weirdfoo.solc b/test/examples/cases/weirdfoo.solc index a94677e94..69952ad5a 100644 --- a/test/examples/cases/weirdfoo.solc +++ b/test/examples/cases/weirdfoo.solc @@ -1,5 +1,5 @@ -data W(a) = W(a); -class a: Foo {function foo(); } -instance ((word, a) : Foo) => (word, W(a)) : Foo { +enum W { W(a) } +trait Foo {function foo(); } +impl Foo<(word, W)> where (word, a): Foo { function foo() {} } diff --git a/test/examples/cases/word-match-default.solc b/test/examples/cases/word-match-default.solc index c1b17b956..a1e3e3eb0 100644 --- a/test/examples/cases/word-match-default.solc +++ b/test/examples/cases/word-match-default.solc @@ -1,14 +1,14 @@ contract WordMatchDefault { - public function f(n : word) -> word { + function f(n : word) public returns (word) { let result : word; - match n { - | 0 => assembly { result := 100 } - | x => assembly { result := x } - } + match (n ) { + case 0 { assembly { result := 100 } + } case x { assembly { result := x } + } } return result; } - public function main() -> word { + function main() public returns (word) { return f(42); } } diff --git a/test/examples/cases/word-match.solc b/test/examples/cases/word-match.solc index 07c20ad04..3a96ea2f5 100644 --- a/test/examples/cases/word-match.solc +++ b/test/examples/cases/word-match.solc @@ -1,11 +1,10 @@ -forall a . class a:IsWord { function toWord(x : a) -> word; } +trait IsWord { function toWord(x : a) returns (word); } -function kw(a:word, b:word) -> word {return a;} +function kw(a:word, b:word) returns (word) {return a;} -forall a b . a:IsWord, b:IsWord -=> function bar(x:(a,b)) -> word { - match x { - | (t,u) => return kw(IsWord.toWord(t), IsWord.toWord(u)); - } +function bar(x:(a, b)) returns (word) where a: IsWord, b: IsWord { + match (x ) { + case (t,u) { return kw(IsWord.toWord(t), IsWord.toWord(u)); + } } } diff --git a/test/examples/cases/xref.solc b/test/examples/cases/xref.solc index d3cc27d2f..326e39bae 100644 --- a/test/examples/cases/xref.solc +++ b/test/examples/cases/xref.solc @@ -6,7 +6,7 @@ function add_(x:word, y:word) { // _add is not a legal identifier :( return res; } -function mload_(x:word) -> word { +function mload_(x:word) returns (word) { let res: word; assembly { res := mload(x) @@ -18,102 +18,102 @@ function mstore_(a:word, v:word) { assembly { mstore(a,v) } } -forall r d . class r:Ref(d) { function load(x:r) -> d; function store(x:r, v:d) -> ();} +trait Ref { function load(x:r) returns (d); function store(x:r, v:d) returns (());} -forall self underlyingType . class self:Typedef(underlyingType) { - function rep(x:self) -> underlyingType; // abbr: x.rep = Typedef.rep(x) - function abs(x:underlyingType) -> self; // abbr: x.abs +trait Typedef { + function rep(x:self) returns (underlyingType); // abbr: x.rep = Typedef.rep(x) + function abs(x:underlyingType) returns (self); // abbr: x.abs } -data Proxy(a) = Proxy; +enum Proxy { Proxy } -data M(a) = M(word); +enum M { M(word) } -forall a . instance M(a) : Typedef(word) { - function rep(m : M(a)) -> word { match m { | M(w) => return w; }} - function abs(w : word) -> M(a) { return M(w); } +impl Typedef, word> { + function rep(m : M) returns (word) { match (m ) { case M(w) { return w; } }} + function abs(w : word) returns (M) { return M(w); } } -forall Self . class Self:MemoryType { - function memorySize(p:Proxy(Self)) -> word; +trait MemoryType { + function memorySize(p:Proxy) returns (word); /* inline function sizeof(Self) -> word { // an abbreviation to avoid writing Proxy; wasteful unless inlined return memorySize(Proxy:Proxy(self)); } */ - function memoryStep(word, self:Self) -> word; - function mload(r:word) -> Self; - function mstore(r:word, v:Self) -> (); + function memoryStep(word, self:Self) returns (word); + function mload(r:word) returns (Self); + function mstore(r:word, v:Self) returns (()); } -forall Self . Self:MemoryType => function sizeof(self:Self) -> word { - return MemoryType.memorySize(Proxy:Proxy(Self)); +function sizeof(self:Self) returns (word) where Self: MemoryType { + return MemoryType.memorySize(Proxy as Proxy); } -forall a d . class a:MemoryRef(d) { function addr(r:a) -> word; } -forall a . instance M(a):MemoryRef(a) { function addr(r:M(a)) -> word {return Typedef.rep(r);} } +trait MemoryRef { function addr(r:a) returns (word); } +impl MemoryRef, a> { function addr(r:M) returns (word) {return Typedef.rep(r);} } -forall a . function xaddr(r:M(a)) -> word { return MemoryRef.addr(r); } -forall a b . function asMemRefTo(r:M(a), p:Proxy(b)) -> M(b) { return Typedef.abs(xaddr(r)); } +function xaddr(r:M) returns (word) { return MemoryRef.addr(r); } +function asMemRefTo(r:M, p:Proxy) returns (M) { return Typedef.abs(xaddr(r)); } -forall a . a:MemoryType => function stepStore(aa: word, va: a) -> word { +function stepStore(aa: word, va: a) returns (word) where a: MemoryType { MemoryType.mstore(aa, va); - return add_(aa, MemoryType.memorySize(Proxy:Proxy(a))); + return add_(aa, MemoryType.memorySize(Proxy as Proxy)); } -forall Self r . Self:MemoryType, r:MemoryRef(Self) => instance r : Ref(Self) { - function load(r:M(Self)) -> Self { return MemoryType.mload(xaddr(r)); } - function store(r:M(Self), v:Self) -> () { MemoryType.mstore(xaddr(r), v); } +impl Ref where Self: MemoryType, r: MemoryRef { + function load(r:M) returns (Self) { return MemoryType.mload(xaddr(r)); } + function store(r:M, v:Self) returns (()) { MemoryType.mstore(xaddr(r), v); } } -instance word:MemoryType { - function memorySize(p:Proxy(word)) -> word { return 32; } - function memoryStep(a:word, self:word) -> word { return add_(a,32); } - function mload(a: word) -> word { return mload_(a); } - function mstore(a: word, v:word) -> () { mstore_(a, v); } +impl MemoryType { + function memorySize(p:Proxy) returns (word) { return 32; } + function memoryStep(a:word, self:word) returns (word) { return add_(a,32); } + function mload(a: word) returns (word) { return mload_(a); } + function mstore(a: word, v:word) returns (()) { mstore_(a, v); } } -forall a b . a:MemoryType, b:MemoryType => instance (a,b) : MemoryType { - function memorySize(p:Proxy((a,b))) -> word { - return add_(MemoryType.memorySize(Proxy:Proxy(a)), MemoryType.memorySize(Proxy:Proxy(a)) ); +impl MemoryType<(a, b)> where a: MemoryType, b: MemoryType { + function memorySize(p:Proxy<(a, b)>) returns (word) { + return add_(MemoryType.memorySize(Proxy as Proxy), MemoryType.memorySize(Proxy as Proxy) ); } - function mload(aa:word) -> (a,b) { + function mload(aa:word) returns ((a, b)) { let va = MemoryType.mload(aa); let ab = add_(aa, sizeof(va)); let vb = MemoryType.mload(ab); return (va,vb); } - function mstore(aa:word, v: (a,b)) -> () { - match v { | pair(va, vb) => mstore2(aa, va, vb); } // match-compiler cannot compile mopre than 1 stmt in a branch :( + function mstore(aa:word, v: (a, b)) returns (()) { + match (v ) { case pair(va, vb) { mstore2(aa, va, vb); } } // match-compiler cannot compile mopre than 1 stmt in a branch :( } } -forall a b . a: MemoryType, b: MemoryType => function mstore2(aa:word, va:a, vb: b) { //needed because of bug in match-compiler +function mstore2(aa:word, va:a, vb: b) where a: MemoryType, b: MemoryType { //needed because of bug in match-compiler let ab = stepStore(aa, va); MemoryType.mstore(ab, vb); } -data XRef(st, field, fieldType) = XRef(st, field); -data PairFst = PairFst; -data PairSnd = PairSnd; +enum XRef { XRef(st, field) } +enum PairFst { PairFst } +enum PairSnd { PairSnd } -forall a b r . r:MemoryRef ( (a,b)), a:MemoryType, b:MemoryType => instance XRef(r, PairFst, a) : MemoryRef(a) { - function addr(xr : XRef(r, PairFst, a)) -> word { - match xr { | XRef(r, _) => return MemoryRef.addr(r); } +impl MemoryRef, a> where r: MemoryRef<(a, b)>, a: MemoryType, b: MemoryType { + function addr(xr : XRef) returns (word) { + match (xr ) { case XRef(r, _) { return MemoryRef.addr(r); } } } } -forall a b r . r:MemoryRef ((a,b)), a:MemoryType, b:MemoryType => instance XRef(r, PairSnd, b) : MemoryRef(b) { - function addr(xr : XRef (r, PairSnd, b)) -> word { - match xr { - | XRef(r, _) => return add_(MemoryRef.addr(r), MemoryType.memorySize(Proxy : Proxy(b))); - } +impl MemoryRef, b> where r: MemoryRef<(a, b)>, a: MemoryType, b: MemoryType { + function addr(xr : XRef) returns (word) { + match (xr ) { + case XRef(r, _) { return add_(MemoryRef.addr(r), MemoryType.memorySize(Proxy as Proxy)); + } } } } contract Ref219 { - public function main() { - let mp:M((word, word, word)) = M(96); // no alloc yet + function main() public { + let mp:M<(word, word, word)> = M(96); // no alloc yet let p = (1,16,25); Ref.store(mp, p); diff --git a/test/examples/cases/yul-asm-for-body.solc b/test/examples/cases/yul-asm-for-body.solc index 806a0d117..999ed5c0f 100644 --- a/test/examples/cases/yul-asm-for-body.solc +++ b/test/examples/cases/yul-asm-for-body.solc @@ -1,6 +1,6 @@ -import std.{*}; +import {*} from std; -function yul_asm_for_body() -> () { +function yul_asm_for_body() returns (()) { let result : word = 0; assembly { for { let i := 0 } lt(i, 3) { i := add(i, 1) } { @@ -10,7 +10,7 @@ function yul_asm_for_body() -> () { } contract Foo { - public function main() -> () { - yul_asm_for_body() + function main() public returns (()) { + return yul_asm_for_body(); } } diff --git a/test/examples/cases/yul-asm-switch-body.solc b/test/examples/cases/yul-asm-switch-body.solc index 76e914ff3..c576fe428 100644 --- a/test/examples/cases/yul-asm-switch-body.solc +++ b/test/examples/cases/yul-asm-switch-body.solc @@ -1,6 +1,6 @@ -import std.{*}; +import {*} from std; -function yul_asm_switch_body() -> () { +function yul_asm_switch_body() returns (()) { let result : word = 0; let flag : word = 1; assembly { @@ -11,7 +11,7 @@ function yul_asm_switch_body() -> () { } contract Foo { - public function main() -> () { - yul_asm_switch_body() + function main() public returns (()) { + return yul_asm_switch_body(); } } diff --git a/test/examples/cases/yul-deposit-example.solc b/test/examples/cases/yul-deposit-example.solc index 53992aa80..afbbc7b9b 100644 --- a/test/examples/cases/yul-deposit-example.solc +++ b/test/examples/cases/yul-deposit-example.solc @@ -1,6 +1,6 @@ -import std.{*}; +import {*} from std; -function deposit(pubkey: memory(string), withdrawal_credentials: memory(string), signature: memory(string), deposit_data_root: uint256) -> () { +function deposit(pubkey: string memory, withdrawal_credentials: string memory, signature: string memory, deposit_data_root: uint256) returns (()) { let msg_value : word = 0; assembly { msg_value := callvalue() @@ -8,7 +8,7 @@ function deposit(pubkey: memory(string), withdrawal_credentials: memory(string), } contract Foo { - public function main () -> () { + function main () public returns (()) { deposit(memory(0), memory(0), memory(0), uint256(2)); } } diff --git a/test/examples/cases/yul-for.solc b/test/examples/cases/yul-for.solc index f0a1497d2..020aa5b9c 100644 --- a/test/examples/cases/yul-for.solc +++ b/test/examples/cases/yul-for.solc @@ -1,5 +1,5 @@ contract YulFor { - public function main() -> word { + function main() public returns (word) { let loopStart : word = 128; let loopEnd : word = 256; let res : word; diff --git a/test/examples/cases/yul-function-typing.solc b/test/examples/cases/yul-function-typing.solc index 32812c249..2c6b784ee 100644 --- a/test/examples/cases/yul-function-typing.solc +++ b/test/examples/cases/yul-function-typing.solc @@ -1,4 +1,4 @@ -function foo(length:word, pos:word) -> word { +function foo(length:word, pos:word) returns (word) { let ret: word; assembly { // ret := add(pos, mul(0x20, iszero(iszero(length)))) diff --git a/test/examples/cases/yul-multi-return-arity-fail.solc b/test/examples/cases/yul-multi-return-arity-fail.solc index de58b9456..1d6a5f43d 100644 --- a/test/examples/cases/yul-multi-return-arity-fail.solc +++ b/test/examples/cases/yul-multi-return-arity-fail.solc @@ -2,7 +2,7 @@ // values but 3 names are being assigned, so this Yul is invalid and the type // checker must report the arity error. contract YulMultiRetBad { - public function main() -> word { + function main() public returns (word) { let x : word; let y : word; let z : word; diff --git a/test/examples/cases/yul-multi-return.solc b/test/examples/cases/yul-multi-return.solc index 4c2666ed8..3b6a6d10b 100644 --- a/test/examples/cases/yul-multi-return.solc +++ b/test/examples/cases/yul-multi-return.solc @@ -3,7 +3,7 @@ // so the type checker must accept it (regression for the arity check that used // to collapse every non-empty return list to a single 'word'). contract YulMultiRet { - public function main() -> word { + function main() public returns (word) { let x : word; let y : word; assembly { diff --git a/test/examples/cases/yul-return.solc b/test/examples/cases/yul-return.solc index 0dc00a802..0875f3e58 100644 --- a/test/examples/cases/yul-return.solc +++ b/test/examples/cases/yul-return.solc @@ -1,5 +1,5 @@ contract C { - public function main() -> () { + function main() public returns (()) { assembly { return(0,0) } diff --git a/test/examples/comptime/CondExpr.solc b/test/examples/comptime/CondExpr.solc index a2fac7f53..2f63b4d34 100644 --- a/test/examples/comptime/CondExpr.solc +++ b/test/examples/comptime/CondExpr.solc @@ -1,12 +1,12 @@ -import std.{*}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +import {*} from std; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; -function notAnswer(n : word) -> word { if(n == 42) then 0 else 42 } +function notAnswer(n : word) returns (word) { return ((n == 42) ? 0 : 42 );} -function answer(n:word) -> word { notAnswer(notAnswer(42)) } +function answer(n:word) returns (word) { return notAnswer(notAnswer(42)); } contract Fib { - public function main() -> word { answer(42) } + function main() public returns (word) { return answer(42); } } diff --git a/test/examples/comptime/CondStmt.solc b/test/examples/comptime/CondStmt.solc index c0bb72f08..a4866f8d5 100644 --- a/test/examples/comptime/CondStmt.solc +++ b/test/examples/comptime/CondStmt.solc @@ -1,17 +1,17 @@ -import std.{*}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +import {*} from std; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; -function notAnswer(n : word) -> word { +function notAnswer(n : word) returns (word) { if(n == 42) { return 0; } else {return 42; } } -function answer(n:word) -> word { +function answer(n:word) returns (word) { return notAnswer(notAnswer(42)); } contract Fib { -public function main() -> word { +function main() public returns (word) { return answer(42); } } diff --git a/test/examples/comptime/OneOne.solc b/test/examples/comptime/OneOne.solc index 0c74f7bad..ac3eb4f19 100644 --- a/test/examples/comptime/OneOne.solc +++ b/test/examples/comptime/OneOne.solc @@ -1,14 +1,14 @@ -function addWord(l: word, r: word) -> word { +function addWord(l: word, r: word) returns (word) { let rw : word; assembly { - rw := add(l,r); + rw := add(l,r) } return rw; } -function zero () { 0 } -function one() { addWord(1, zero()) } +function zero () { return 0; } +function one() { return addWord(1, zero()); } contract OneOne { - function main() -> word { addWord(one(), one()) } + function main() returns (word) { return addWord(one(), one()); } } \ No newline at end of file diff --git a/test/examples/comptime/OneTwo.solc b/test/examples/comptime/OneTwo.solc index 9a16738d1..30ff2b54b 100644 --- a/test/examples/comptime/OneTwo.solc +++ b/test/examples/comptime/OneTwo.solc @@ -1,5 +1,5 @@ // This function should be in stdlib -function addWord(l: word, r: word) -> word { +function addWord(l: word, r: word) returns (word) { let rw : word; assembly { rw := add(l,r) @@ -7,15 +7,15 @@ function addWord(l: word, r: word) -> word { return rw; } - function zero () -> word { + function zero () returns (word) { return 0; } -function one() -> word { +function one() returns (word) { return addWord(1, zero()) ; } -function two () -> word { +function two () returns (word) { let x = zero(); x = addWord(x, one()); x = addWord(x,x); @@ -23,6 +23,6 @@ function two () -> word { } contract OneTwo { - public function main() -> word { return two(); } + function main() public returns (word) { return two(); } } diff --git a/test/examples/comptime/Plus.solc b/test/examples/comptime/Plus.solc index 2d34d846f..794aa2084 100644 --- a/test/examples/comptime/Plus.solc +++ b/test/examples/comptime/Plus.solc @@ -1,16 +1,16 @@ -import std.{*}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; - function zero () -> word { +import {*} from std; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; + function zero () returns (word) { return 0; } -function one() -> word { +function one() returns (word) { return 1 + zero() ; } -function two () -> word { +function two () returns (word) { let x = zero(); x = x + one(); x = x + x ; @@ -18,5 +18,5 @@ function two () -> word { } contract Plus { - public function main() -> word { return two() + two(); } + function main() public returns (word) { return two() + two(); } } diff --git a/test/examples/comptime/Size.solc b/test/examples/comptime/Size.solc index 292b99e7d..33bb8c395 100644 --- a/test/examples/comptime/Size.solc +++ b/test/examples/comptime/Size.solc @@ -1,6 +1,6 @@ -data Proxy(t) = Proxy; +enum Proxy { Proxy } -function addWord(l: word, r: word) -> word { +function addWord(l: word, r: word) returns (word) { let rw : word; assembly { rw := add(l,r) @@ -8,42 +8,40 @@ function addWord(l: word, r: word) -> word { return rw; } -forall self. -class self:StorageSize { - function size(x:Proxy(self)) -> word; +trait StorageSize { + function size(x:Proxy) returns (word); } -forall self. -default instance self:StorageSize { - function size(x:Proxy(self)) -> word { +default impl StorageSize { + function size(x:Proxy) returns (word) { return 1; } } -instance ():StorageSize { - function size(x:Proxy(())) -> word { +impl StorageSize<()> { + function size(x:Proxy<()>) returns (word) { return 0; } } -instance word:StorageSize { - function size(x:Proxy(word)) -> word { +impl StorageSize { + function size(x:Proxy) returns (word) { return 1; } } -forall a b. a:StorageSize, b:StorageSize => instance (a,b):StorageSize { - function size(x:Proxy((a,b))) -> word { - let a_sz:word = StorageSize.size(Proxy:Proxy(a)); - let b_sz:word = StorageSize.size(Proxy:Proxy(b)); +impl StorageSize<(a, b)> where a: StorageSize, b: StorageSize { + function size(x:Proxy<(a, b)>) returns (word) { + let a_sz:word = StorageSize.size(Proxy as Proxy); + let b_sz:word = StorageSize.size(Proxy as Proxy); return addWord(a_sz, b_sz); } } contract Size { - public function main() -> word { + function main() public returns (word) { return - StorageSize.size(Proxy:Proxy( (word, (word, ())))); } + StorageSize.size(Proxy as Proxy<(word, (word, ()))>); } } diff --git a/test/examples/comptime/StdSize.solc b/test/examples/comptime/StdSize.solc index 17123de68..b2f9d57cb 100644 --- a/test/examples/comptime/StdSize.solc +++ b/test/examples/comptime/StdSize.solc @@ -1,6 +1,6 @@ -data Proxy(t) = Proxy; +enum Proxy { Proxy } -function addWord(l: word, r: word) -> word { +function addWord(l: word, r: word) returns (word) { let rw: word; assembly { rw := add(l, r) @@ -8,41 +8,38 @@ function addWord(l: word, r: word) -> word { return rw; } -forall self. -class self:StorageSize { - function size(x: Proxy(self)) -> word; +trait StorageSize { + function size(x: Proxy) returns (word); } -forall self. -default instance self:StorageSize { - function size(x: Proxy(self)) -> word { +default impl StorageSize { + function size(x: Proxy) returns (word) { return 1; } } -instance ():StorageSize { - function size(x: Proxy(())) -> word { +impl StorageSize<()> { + function size(x: Proxy<()>) returns (word) { return 0; } } -instance word:StorageSize { - function size(x: Proxy(word)) -> word { +impl StorageSize { + function size(x: Proxy) returns (word) { return 1; } } -forall a b. a:StorageSize, b:StorageSize => -instance (a, b):StorageSize { - function size(x: Proxy((a, b))) -> word { - let a_sz: word = StorageSize.size(Proxy:Proxy(a)); - let b_sz: word = StorageSize.size(Proxy:Proxy(b)); +impl StorageSize<(a, b)> where a: StorageSize, b: StorageSize { + function size(x: Proxy<(a, b)>) returns (word) { + let a_sz: word = StorageSize.size(Proxy as Proxy); + let b_sz: word = StorageSize.size(Proxy as Proxy); return addWord(a_sz, b_sz); } } contract Size { - public function main() -> word { - return StorageSize.size(Proxy:Proxy((word, (word, ())))); + function main() public returns (word) { + return StorageSize.size(Proxy as Proxy<(word, (word, ()))>); } } diff --git a/test/examples/comptime/comptime_syntax.solc b/test/examples/comptime/comptime_syntax.solc index 50c9ef373..6a8f08f66 100644 --- a/test/examples/comptime/comptime_syntax.solc +++ b/test/examples/comptime/comptime_syntax.solc @@ -1,15 +1,15 @@ contract ComptimeSyntax { - function f(comptime x : word) -> comptime word { + function f(comptime x : word) returns (comptime word) { return x; } - function g() -> word { - let y : comptime word = f(42); + function g() returns (word) { + let comptime y : word = f(42); return y; } - function main() -> word { + function main() returns (word) { return g(); } } diff --git a/test/examples/comptime/counter.solc b/test/examples/comptime/counter.solc index 8c93f43e7..0de98a106 100644 --- a/test/examples/comptime/counter.solc +++ b/test/examples/comptime/counter.solc @@ -1,9 +1,9 @@ -import std.{*}; -import std.{uint256, address}; -import std.dispatch.{*}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +import {*} from std; +import {uint256, address} from std; +import {*} from std.dispatch; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; contract Counter { // some dummy fields to test offset calculation fld0 : word; @@ -18,7 +18,7 @@ contract Counter { fld0 = 7; } - public function main() -> word { + function main() public returns (word) { counter = counter + 1; return counter; } diff --git a/test/examples/comptime/ct_asm_mem.solc b/test/examples/comptime/ct_asm_mem.solc index 94a8d86ad..214241791 100644 --- a/test/examples/comptime/ct_asm_mem.solc +++ b/test/examples/comptime/ct_asm_mem.solc @@ -2,7 +2,7 @@ when its argument is known at compile time. The evaluator runs in comptime mode for the RHS of `let x : comptime`. */ -function storeLoad(x : word) -> word { +function storeLoad(x : word) returns (word) { let r : word; assembly { mstore(0, x) @@ -12,8 +12,8 @@ function storeLoad(x : word) -> word { } contract ComptimeAsmMem { - function main() -> word { - let res : comptime word = storeLoad(42); + function main() returns (word) { + let comptime res : word = storeLoad(42); return res; } } diff --git a/test/examples/comptime/ct_asm_ret.solc b/test/examples/comptime/ct_asm_ret.solc index b0d3893bf..3fa78bd30 100644 --- a/test/examples/comptime/ct_asm_ret.solc +++ b/test/examples/comptime/ct_asm_ret.solc @@ -4,14 +4,14 @@ */ contract ComptimeAsmRet { - function loadFromStorage() -> comptime word { + function loadFromStorage() returns (comptime word) { let v : word; assembly { v := sload(0) } return v; } - function main() -> word { + function main() returns (word) { return loadFromStorage(); } } diff --git a/test/examples/comptime/ct_chain_ok.solc b/test/examples/comptime/ct_chain_ok.solc index a35f9f41c..0e7c4bddf 100644 --- a/test/examples/comptime/ct_chain_ok.solc +++ b/test/examples/comptime/ct_chain_ok.solc @@ -4,13 +4,13 @@ import std; contract ComptimeChainOk { - function increment(comptime x : word) -> comptime word { + function increment(comptime x : word) returns (comptime word) { return x + 1; } - function double(comptime x : word) -> comptime word { + function double(comptime x : word) returns (comptime word) { return x + x; } - function main() -> word { + function main() returns (word) { return double(increment(20)); } } diff --git a/test/examples/comptime/ct_let_ok.solc b/test/examples/comptime/ct_let_ok.solc index 4f3739a90..51d3377ee 100644 --- a/test/examples/comptime/ct_let_ok.solc +++ b/test/examples/comptime/ct_let_ok.solc @@ -2,11 +2,11 @@ import std; contract ComptimeLetOk { - function double(comptime x : word) -> comptime word { + function double(comptime x : word) returns (comptime word) { return x + x; } - function main() -> word { - let y : comptime word = double(21); + function main() returns (word) { + let comptime y : word = double(21); return y; } } diff --git a/test/examples/comptime/ct_let_runtime.solc b/test/examples/comptime/ct_let_runtime.solc index 2db7a7d63..8cabfe79a 100644 --- a/test/examples/comptime/ct_let_runtime.solc +++ b/test/examples/comptime/ct_let_runtime.solc @@ -5,7 +5,7 @@ */ import std; -function sloadWord() -> word { +function sloadWord() returns (word) { let v : word; assembly { v := sload(0) @@ -14,8 +14,8 @@ function sloadWord() -> word { } contract ComptimeLetRuntime { - function main() -> word { - let y : comptime word = sloadWord(); + function main() returns (word) { + let comptime y : word = sloadWord(); return y; } } diff --git a/test/examples/comptime/ct_overloaded_bad.solc b/test/examples/comptime/ct_overloaded_bad.solc index 68042e0a8..f086d4b67 100644 --- a/test/examples/comptime/ct_overloaded_bad.solc +++ b/test/examples/comptime/ct_overloaded_bad.solc @@ -5,12 +5,12 @@ */ import std; -forall a. class a : Scale { - function scale(comptime factor : word, comptime x : a) -> comptime a; +trait Scale { + function scale(comptime factor : word, comptime x : a) returns (comptime a); } -instance word : Scale { - function scale(comptime factor : word, comptime x : word) -> comptime word { +impl Scale { + function scale(comptime factor : word, comptime x : word) returns (comptime word) { let base : word; assembly { base := sload(0) @@ -20,8 +20,8 @@ instance word : Scale { } contract ComptimeOverloadedBad { - function main() -> word { - let a : comptime word = Scale.scale(3, 10); + function main() returns (word) { + let comptime a : word = Scale.scale(3, 10); return a; } } diff --git a/test/examples/comptime/ct_overloaded_ok.solc b/test/examples/comptime/ct_overloaded_ok.solc index f62524908..86830d43b 100644 --- a/test/examples/comptime/ct_overloaded_ok.solc +++ b/test/examples/comptime/ct_overloaded_ok.solc @@ -4,14 +4,14 @@ mulWord is builtinPure, so multiplication of comptime values is comptime. The verifier must follow specialization and accept this. */ -import std.{*}; +import {*} from std; -forall a. class a : Scale { - function scale(comptime factor : word, comptime x : a) -> comptime a; +trait Scale { + function scale(comptime factor : word, comptime x : a) returns (comptime a); } -instance word : Scale { - function scale(comptime factor : word, comptime x : word) -> comptime word { +impl Scale { + function scale(comptime factor : word, comptime x : word) returns (comptime word) { if (factor == 1) { return x; } else { @@ -21,9 +21,9 @@ instance word : Scale { } contract ComptimeOverloadedOk { - function main() -> word { - let a : comptime word = Scale.scale(1, 32); - let b : comptime word = Scale.scale(3, 10); + function main() returns (word) { + let comptime a : word = Scale.scale(1, 32); + let comptime b : word = Scale.scale(3, 10); return a + b; } } diff --git a/test/examples/comptime/ct_param_ok.solc b/test/examples/comptime/ct_param_ok.solc index 68bf72473..63ed195fb 100644 --- a/test/examples/comptime/ct_param_ok.solc +++ b/test/examples/comptime/ct_param_ok.solc @@ -5,10 +5,10 @@ import std; contract ComptimeParamOk { - function double(comptime x : word) -> comptime word { + function double(comptime x : word) returns (comptime word) { return x + x; } - function main() -> word { + function main() returns (word) { return double(21); } } diff --git a/test/examples/comptime/ct_param_poly_runtime.solc b/test/examples/comptime/ct_param_poly_runtime.solc index e67a24c1f..62d15bfae 100644 --- a/test/examples/comptime/ct_param_poly_runtime.solc +++ b/test/examples/comptime/ct_param_poly_runtime.solc @@ -6,22 +6,22 @@ */ import std; -forall t. class t : Wrap { - function unwrap(comptime x : t) -> comptime word; +trait Wrap { + function unwrap(comptime x : t) returns (comptime word); } -instance word : Wrap { - function unwrap(comptime x : word) -> comptime word { +impl Wrap { + function unwrap(comptime x : word) returns (comptime word) { return x; } } -forall t. t:Wrap => function process(z : t) -> word { +function process(z : t) returns (word) where t: Wrap { return Wrap.unwrap(z); } contract ComptimeParamPolyRuntime { - function main() -> word { + function main() returns (word) { return process(42); } } diff --git a/test/examples/comptime/ct_param_runtime.solc b/test/examples/comptime/ct_param_runtime.solc index 496cb2a7d..cae2375cb 100644 --- a/test/examples/comptime/ct_param_runtime.solc +++ b/test/examples/comptime/ct_param_runtime.solc @@ -7,13 +7,13 @@ import std; contract ComptimeParamRuntime { - function double(comptime x : word) -> comptime word { + function double(comptime x : word) returns (comptime word) { return x + x; } - function process(value : word) -> word { + function process(value : word) returns (word) { return double(value); } - function main() -> word { + function main() returns (word) { return process(21); } } diff --git a/test/examples/comptime/ct_runtime_arg.solc b/test/examples/comptime/ct_runtime_arg.solc index ed9e0132b..0575f184b 100644 --- a/test/examples/comptime/ct_runtime_arg.solc +++ b/test/examples/comptime/ct_runtime_arg.solc @@ -4,7 +4,7 @@ */ import std; -function sloadWord() -> word { +function sloadWord() returns (word) { let v : word; assembly { v := sload(0) @@ -13,10 +13,10 @@ function sloadWord() -> word { } contract ComptimeRuntimeArg { - function double(comptime x : word) -> comptime word { + function double(comptime x : word) returns (comptime word) { return x + x; } - function main() -> word { + function main() returns (word) { return double(sloadWord()); } } diff --git a/test/examples/comptime/fib.solc b/test/examples/comptime/fib.solc index 46498180f..40610b015 100644 --- a/test/examples/comptime/fib.solc +++ b/test/examples/comptime/fib.solc @@ -1,14 +1,14 @@ -import std.{*}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +import {*} from std; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; -function fib(n : word) -> word { +function fib(n : word) returns (word) { if(n < 2) { return n; } else {return fib(n-1) + fib(n-2); } } contract Fib { -public function main() -> word { +function main() public returns (word) { return fib(10); } } diff --git a/test/examples/comptime/fib2.solc b/test/examples/comptime/fib2.solc index 5cd5c869f..db54dcefd 100644 --- a/test/examples/comptime/fib2.solc +++ b/test/examples/comptime/fib2.solc @@ -1,12 +1,12 @@ -import std.{*}; +import {*} from std; -function fib2(n : word) -> comptime word { +function fib2(n : word) returns (comptime word) { if(n < 2) { return n; } else {return fib2(n-1) + fib2(n-2); } } contract Fib { - function main() -> word { - let res : comptime word = fib2(10); + function main() returns (word) { + let comptime res : word = fib2(10); return res; } } diff --git a/test/examples/comptime/fib3.solc b/test/examples/comptime/fib3.solc index 62d396e89..500140314 100644 --- a/test/examples/comptime/fib3.solc +++ b/test/examples/comptime/fib3.solc @@ -1,12 +1,12 @@ -import std.{*}; +import {*} from std; -function fib3(n : word) -> word { +function fib3(n : word) returns (word) { if(n < 2) { return n; } else {return fib3(n-1) + fib3(n-2); } } contract Fib { - function main() -> word { - let res : comptime word = fib3(10); + function main() returns (word) { + let comptime res : word = fib3(10); return res; } } diff --git a/test/examples/comptime/fromInt.solc b/test/examples/comptime/fromInt.solc index f1525446a..50a5a8a3e 100644 --- a/test/examples/comptime/fromInt.solc +++ b/test/examples/comptime/fromInt.solc @@ -8,44 +8,41 @@ Here we use a bit less ambitious approach: literals of type word and `fromWord` import std; -type uint = uint256; // misleads instance solver +type uint is uint256; // misleads instance solver -forall i. -class i : Int { - function fromWord(x:word) -> comptime i; // meaning result is comptime whenever arg is +trait Int { + function fromWord(x:word) returns (comptime i); // meaning result is comptime whenever arg is - function toWord(x:i) -> comptime word; + function toWord(x:i) returns (comptime word); } -instance word : Int { - function fromWord(x:word) -> comptime word { x } - function toWord(x:word) -> comptime word { x } +impl Int { + function fromWord(x:word) returns (comptime word) { return x; } + function toWord(x:word) returns (comptime word) { return x; } } -instance uint : Int { - function fromWord(x:word) -> comptime uint { uint256(x) } - function toWord(x:uint) -> comptime word { Typedef.rep(x) } +impl Int { + function fromWord(x:word) returns (comptime uint) { return uint256(x); } + function toWord(x:uint) returns (comptime word) { return Typedef.rep(x); } } // specialised for numbers -forall a b. a:Int, b:Int => function fromInt(x:a) -> b { Int.fromWord(Int.toWord(x)) } -forall a b. a:Int, b:Int => function staticInt(comptime x:a) -> comptime b { Int.fromWord(Int.toWord(x)) } +function fromInt(x:a) returns (b) where a: Int, b: Int { return Int.fromWord(Int.toWord(x)); } +function staticInt(comptime x:a) returns (comptime b) where a: Int, b: Int { return Int.fromWord(Int.toWord(x)); } // limited usability -forall a b r. a:Typedef(r), b:Typedef(r) => function dynamic_cast(x:a) -> b { Typedef.abs(Typedef.rep(x):r) } -forall a b r. a:Typedef(r), b:Typedef(r) => function static_cast(comptime x:a) -> comptime b { Typedef.abs(Typedef.rep(x):r) } +function dynamic_cast(x:a) returns (b) where a: Typedef, b: Typedef { return Typedef.abs(Typedef.rep(x) as r); } +function static_cast(comptime x:a) returns (comptime b) where a: Typedef, b: Typedef { return Typedef.abs(Typedef.rep(x) as r); } // wider usability -forall a b r. a:Typedef(r), b:Typedef(r) => -function dynamic_cast_via(p:@r, x:a) -> b { Typedef.abs(Typedef.rep(x):r) } +function dynamic_cast_via(p:Proxy, x:a) returns (b) where a: Typedef, b: Typedef { return Typedef.abs(Typedef.rep(x) as r); } -forall a b r. a:Typedef(r), b:Typedef(r) => -function static_cast_via(comptime p:@r, comptime x:a) -> comptime b { Typedef.abs(Typedef.rep(x):r) } +function static_cast_via(comptime p:Proxy, comptime x:a) returns (comptime b) where a: Typedef, b: Typedef { return Typedef.abs(Typedef.rep(x) as r); } // maybe: `comptime function static_cast_via` as equivalent notation -function notcomptime(x:word) -> word { +function notcomptime(x:word) returns (word) { let res : word; assembly { res := mload(0) @@ -53,27 +50,27 @@ function notcomptime(x:word) -> word { return res; } -forall a. function id(x:a) -> comptime a { x } -function id_uint(x:uint) -> comptime uint { x } +function id(x:a) returns (comptime a) { return x; } +function id_uint(x:uint) returns (comptime uint) { return x; } contract FromWord { constructor() {} - function f1(x : word) -> comptime word { x } - function f2(x : uint) -> comptime uint { x } - function g() -> uint { - let y1 : comptime uint256 = static_cast( // cast on top level of comptime let + function f1(x : word) returns (comptime word) { return x; } + function f2(x : uint) returns (comptime uint) { return x; } + function g() returns (uint) { + let comptime y1 : uint256 = static_cast( // cast on top level of comptime let f1( static_cast(42) //cast a literal - could be fromWord/staticInt )); - let y2 : comptime uint256 = staticInt( id_uint(staticInt(42)) ); // cast at literal, cast at let + let comptime y2 : uint256 = staticInt( id_uint(staticInt(42)) ); // cast at literal, cast at let let z = notcomptime(Typedef.rep(y1)); // no cast - not comptime let t = dynamic_cast(y1); // just testing return t; } - function h() -> comptime uint256 { - let y2 : comptime uint256 = staticInt( ( staticInt(42) ):uint256); // error w/o type annotation + function h() returns (comptime uint256) { + let comptime y2 : uint256 = staticInt( ( staticInt(42) ) as uint256); // error w/o type annotation return y2; } function main() { diff --git a/test/examples/comptime/fromInt2.solc b/test/examples/comptime/fromInt2.solc index c2714fab1..472a2625a 100644 --- a/test/examples/comptime/fromInt2.solc +++ b/test/examples/comptime/fromInt2.solc @@ -1,45 +1,43 @@ // import std.{Num,Add,Sub,Eq,Ord,Bounded,Typedef,le}; import std; -forall i. -class i : Int { - function fromWord(x:word) -> comptime i; // meaning result is comptime whenever arg is +trait Int { + function fromWord(x:word) returns (comptime i); // meaning result is comptime whenever arg is - function toWord(x:i) -> comptime word; + function toWord(x:i) returns (comptime word); } -instance uint256 : Int { - function fromWord(x:word) -> uint256 { Typedef.abs(x) } - function toWord(y:uint256) -> word { Typedef.rep(y) } +impl Int { + function fromWord(x:word) returns (uint256) { return Typedef.abs(x); } + function toWord(y:uint256) returns (word) { return Typedef.rep(y); } } -instance uint256 : Mul { - function mul(x: uint256, y: uint256) -> uint256 { - Int.fromWord(Mul.mul(Int.toWord(x), Int.toWord(y))) +impl Mul { + function mul(x: uint256, y: uint256) returns (uint256) { + return Int.fromWord(Mul.mul(Int.toWord(x), Int.toWord(y))); } } -instance word : Int { - function fromWord(x:word) -> word { x } - function toWord(y:word) -> word { y } +impl Int { + function fromWord(x:word) returns (word) { return x; } + function toWord(y:word) returns (word) { return y; } } -function bitAnd(x:word, y:word) -> comptime word { +function bitAnd(x:word, y:word) returns (comptime word) { let res : word; assembly { res := and(x,y) } return res; } -forall a. a: Num => -function fromLit(x:word) -> a { Num.fromWord(x) } +function fromLit(x:word) returns (a) where a: Num { return Num.fromWord(x); } contract FromInt { - function main() -> uint256 { + function main() returns (uint256) { let a : uint256 = fromLit(1); - let b : comptime uint256 = fromLit((2 + 2)); // CTE + let comptime b : uint256 = fromLit((2 + 2)); // CTE let c : uint256 = fromLit(3) + fromLit(3); // RTE // let d : comptime word = fromLit(bitAnd(0xff,keccakLit("foo"+"bar"))); // CTE - let d : comptime word = fromLit(bitAnd(0xff,keccakLit("foo"+"bar"))); // CTE + let comptime d : word = fromLit(bitAnd(0xff,keccakLit("foo"+"bar"))); // CTE let k = fromLit(40); return k+2; diff --git a/test/examples/comptime/fromInt3.solc b/test/examples/comptime/fromInt3.solc index 88d4cacf4..7346af59c 100644 --- a/test/examples/comptime/fromInt3.solc +++ b/test/examples/comptime/fromInt3.solc @@ -1,40 +1,38 @@ // import std.{Num,Add,Sub,Eq,Ord,Bounded,Typedef,le}; import std; -forall i. -class i : Int { - function fromWord(x:word) -> comptime i; // meaning result is comptime whenever arg is +trait Int { + function fromWord(x:word) returns (comptime i); // meaning result is comptime whenever arg is - function toWord(x:i) -> comptime word; + function toWord(x:i) returns (comptime word); } -instance uint256 : Int { - function fromWord(x:word) -> uint256 { Typedef.abs(x) } - function toWord(y:uint256) -> word { Typedef.rep(y) } +impl Int { + function fromWord(x:word) returns (uint256) { return Typedef.abs(x); } + function toWord(y:uint256) returns (word) { return Typedef.rep(y); } } -instance uint256 : Mul { - function mul(x: uint256, y: uint256) -> uint256 { - Int.fromWord(Mul.mul(Int.toWord(x), Int.toWord(y))) +impl Mul { + function mul(x: uint256, y: uint256) returns (uint256) { + return Int.fromWord(Mul.mul(Int.toWord(x), Int.toWord(y))); } } -instance word : Int { - function fromWord(x:word) -> word { x } - function toWord(y:word) -> word { y } +impl Int { + function fromWord(x:word) returns (word) { return x; } + function toWord(y:word) returns (word) { return y; } } -function bitAnd(x:word, y:word) -> comptime word { +function bitAnd(x:word, y:word) returns (comptime word) { let res : word; assembly { res := and(x,y) } return res; } -forall a. a: Num => -function fromLit(x:word) -> a { Num.fromWord(x) } +function fromLit(x:word) returns (a) where a: Num { return Num.fromWord(x); } contract FromInt { - function main() -> uint256 { + function main() returns (uint256) { let k = fromLit(40); return k+2; } diff --git a/test/examples/comptime/fromLit.solc b/test/examples/comptime/fromLit.solc index 52b6d15a0..6e22f8345 100644 --- a/test/examples/comptime/fromLit.solc +++ b/test/examples/comptime/fromLit.solc @@ -1,18 +1,17 @@ import std; -forall a b. class a:FromLit(b) { - function fromLit(l:b) -> a; +trait FromLit { + function fromLit(l:b) returns (a); } -forall a b. a:FromLit(b) => -function fromLit(l:b) -> a { FromLit.fromLit(l) } +function fromLit(l:b) returns (a) where a: FromLit { return FromLit.fromLit(l); } -instance word:FromLit(word) { - function fromLit(l:word) -> word { l } +impl FromLit { + function fromLit(l:word) returns (word) { return l; } } -instance uint256:FromLit(word) { - function fromLit(l:word) -> uint256 { uint256(l) } +impl FromLit { + function fromLit(l:word) returns (uint256) { return uint256(l); } } /* @@ -22,15 +21,15 @@ default instance a:FromLit(a) { function fromLit(l:a) -> a { l } } */ -instance uint256:Mul { - function mul(a:uint256, b:uint256) -> uint256 { uint256(Mul.mul(Typedef.rep(a),Typedef.rep(b))) } +impl Mul { + function mul(a:uint256, b:uint256) returns (uint256) { return uint256(Mul.mul(Typedef.rep(a),Typedef.rep(b))); } } -function main() -> uint256 { +function main() returns (uint256) { let a : uint256 = fromLit(1); - let b : comptime uint256 = fromLit(2 + 2); // CTE + let comptime b : uint256 = fromLit(2 + 2); // CTE let c : uint256 = fromLit(3) + fromLit(3); // RTE - let d : comptime word = fromLit(keccakLit("foo"+"bar")); // CTE + let comptime d : word = fromLit(keccakLit("foo"+"bar")); // CTE return b*b - fromLit(4)*a*c + fromLit(d); // RTE in RTC } \ No newline at end of file diff --git a/test/examples/comptime/int-untyped-let.solc b/test/examples/comptime/int-untyped-let.solc index ffad1f7d7..45a7460bb 100644 --- a/test/examples/comptime/int-untyped-let.solc +++ b/test/examples/comptime/int-untyped-let.solc @@ -1,8 +1,8 @@ // Bare integer literals with integer class instances from std. -import std.{Eq,Ord,lt,Add,Sub}; +import {Eq,Ord,lt,Add,Sub} from std; -function fib(comptime n : integer) -> comptime integer { +function fib(comptime n : integer) returns (comptime integer) { if (n < 2) { return n; } else { @@ -12,9 +12,9 @@ function fib(comptime n : integer) -> comptime integer { } contract IntegerLit { - function main() -> word { + function main() returns (word) { let x = 20; - let res : comptime word = Int.fromInteger(fib(x)); + let comptime res : word = Int.fromInteger(fib(x)); return res; } } diff --git a/test/examples/comptime/integer-basic.solc b/test/examples/comptime/integer-basic.solc index c58ee6ef3..a7072fc1f 100644 --- a/test/examples/comptime/integer-basic.solc +++ b/test/examples/comptime/integer-basic.solc @@ -3,7 +3,7 @@ // Expected: main() folds to word literal 100. contract IntegerBasic { - function main() -> word { + function main() returns (word) { let x = 42; let y = integerAdd(x, 8); return wordFromInteger(integerMul(y, 2)); diff --git a/test/examples/comptime/integer-fib.solc b/test/examples/comptime/integer-fib.solc index 9726d8e84..b5c215455 100644 --- a/test/examples/comptime/integer-fib.solc +++ b/test/examples/comptime/integer-fib.solc @@ -2,7 +2,7 @@ // No import std needed: uses only compiler builtins. // Expected: main() folds to word literal 55 (fib(10)). -function fib(comptime n : integer) -> comptime integer { +function fib(comptime n : integer) returns (comptime integer) { if (integerLt(n, 2)) { return n; } else { @@ -14,7 +14,7 @@ function fib(comptime n : integer) -> comptime integer { } contract FibInteger { - function main() -> word { + function main() returns (word) { return wordFromInteger(fib(10)); } } diff --git a/test/examples/comptime/integer-from-integer.solc b/test/examples/comptime/integer-from-integer.solc index e156e2d4b..74f2f4e47 100644 --- a/test/examples/comptime/integer-from-integer.solc +++ b/test/examples/comptime/integer-from-integer.solc @@ -1,9 +1,9 @@ -import std.{*}; +import {*} from std; // Tests Num.fromInteger for word (Typedef.abs = identity) and uint256 (wraps in uint256(...)). // Also tests the full design-doc pattern: comptime integer fib result converted via Num.fromInteger. -function fib(comptime n : integer) -> comptime integer { +function fib(comptime n : integer) returns (comptime integer) { if (integerLt(n, wordToInteger(2))) { return n; } else { @@ -20,9 +20,9 @@ function fib(comptime n : integer) -> comptime integer { // Returns Typedef.rep(u) = 55, demonstrating the uint256 round-trip. // Expected: main() folds to word literal 55. contract IntegerFromInteger { - function main() -> word { - let w : comptime word = Num.fromInteger(wordToInteger(42)); - let u : comptime uint256 = Num.fromInteger(fib(wordToInteger(10))); + function main() returns (word) { + let comptime w : word = Num.fromInteger(wordToInteger(42)); + let comptime u : uint256 = Num.fromInteger(fib(wordToInteger(10))); return Typedef.rep(u); } } diff --git a/test/examples/comptime/integer-lit-class.solc b/test/examples/comptime/integer-lit-class.solc index 636381a76..72b278394 100644 --- a/test/examples/comptime/integer-lit-class.solc +++ b/test/examples/comptime/integer-lit-class.solc @@ -2,9 +2,9 @@ // The type checker infers the literal type from context: the integer:Ord/Add/Sub // instances constrain unresolved literals to `integer`. -import std.{Eq,Ord,lt,Add,Sub}; +import {Eq,Ord,lt,Add,Sub} from std; -function fib(comptime n : integer) -> comptime integer { +function fib(comptime n : integer) returns (comptime integer) { if (n < 2) { return n; } else { @@ -14,9 +14,9 @@ function fib(comptime n : integer) -> comptime integer { } contract IntegerLit { - function main() -> word { - let x : comptime integer = 20; - let res : comptime word = wordFromInteger(fib(x)); + function main() returns (word) { + let comptime x : integer = 20; + let comptime res : word = wordFromInteger(fib(x)); return res; } } diff --git a/test/examples/comptime/integer-lit-cond.solc b/test/examples/comptime/integer-lit-cond.solc index f6dcd8c54..cdfb00790 100644 --- a/test/examples/comptime/integer-lit-cond.solc +++ b/test/examples/comptime/integer-lit-cond.solc @@ -3,9 +3,9 @@ // in branches infer the correct type. contract CondLit { - function main() -> word { + function main() returns (word) { // Both literal branches should infer type word from the return annotation. - let x : word = if (true) then 1 else 2; + let x : word = ( (true) ? 1 : 2); return x; } } diff --git a/test/examples/comptime/integer-lit-pat.solc b/test/examples/comptime/integer-lit-pat.solc index f5ec90076..7fd30565c 100644 --- a/test/examples/comptime/integer-lit-pat.solc +++ b/test/examples/comptime/integer-lit-pat.solc @@ -1,27 +1,27 @@ // Integer literal patterns against word and integer scrutinees. -import std.{Add}; +import {Add} from std; -function classify_word(comptime n : word) -> comptime word { - match n { - | 0 => return 10; - | 1 => return 20; - | _ => return 0; - } +function classify_word(comptime n : word) returns (comptime word) { + match (n ) { + case 0 { return 10; + } case 1 { return 20; + } default { return 0; + } } } -function classify_integer(comptime n : integer) -> comptime integer { - match n { - | 0 => return integerAdd(n, 10); - | 1 => return integerAdd(n, 20); - | _ => return n; - } +function classify_integer(comptime n : integer) returns (comptime integer) { + match (n ) { + case 0 { return integerAdd(n, 10); + } case 1 { return integerAdd(n, 20); + } default { return n; + } } } contract PatternLit { - function main() -> word { - let a : comptime word = classify_word(1); - let b : comptime integer = classify_integer(0); + function main() returns (word) { + let comptime a : word = classify_word(1); + let comptime b : integer = classify_integer(0); return Add.add(a, wordFromInteger(b)); } } diff --git a/test/examples/comptime/integer-lit-poly.solc b/test/examples/comptime/integer-lit-poly.solc index d67ab32ca..17fd000b0 100644 --- a/test/examples/comptime/integer-lit-poly.solc +++ b/test/examples/comptime/integer-lit-poly.solc @@ -4,10 +4,10 @@ // Add.add(s, 1) with s:word => 1 infers as word (Add a => a->a->a, a=word) // integerAdd(n, 1) with n:integer => 1 infers as integer (param type is integer) -import std.{Add}; +import {Add} from std; contract PolyLit { - function main() -> word { + function main() returns (word) { let s : word = 0; // 1 inferred as word via Add.add constraint let s2 : word = Add.add(s, 1); diff --git a/test/examples/comptime/integer-lit-safe.solc b/test/examples/comptime/integer-lit-safe.solc index 4eef8d7be..b9b25a7e6 100644 --- a/test/examples/comptime/integer-lit-safe.solc +++ b/test/examples/comptime/integer-lit-safe.solc @@ -1,4 +1,4 @@ -import std.{*}; +import {*} from std; // Safety: verify literals pick up the correct type from context, no spurious coercions. // @@ -9,16 +9,16 @@ import std.{*}; // let z : word = 5 — explicit word annotation, wordFromInteger coercion inserted contract IntegerLitSafe { - function main() -> word { + function main() returns (word) { // word arithmetic: 1 and 2 must stay as word literals let a : word = addWord(1, 2); // already-explicit coercions: no double-wrapping of the inner 42 - let ok : comptime bool = integerEq(wordToInteger(42), wordToInteger(42)); + let comptime ok : bool = integerEq(wordToInteger(42), wordToInteger(42)); // wordFromInteger param is integer, but wordToInteger(10) is a Call not a // literal, so no double-wrap; b folds to 10 - let b : comptime word = wordFromInteger(wordToInteger(10)); + let comptime b : word = wordFromInteger(wordToInteger(10)); // word-annotated let: annotation is word, not integer -> no coercion let z : word = 5; diff --git a/test/examples/comptime/integer-lit-word-site.solc b/test/examples/comptime/integer-lit-word-site.solc index 8b52fbc9a..379704675 100644 --- a/test/examples/comptime/integer-lit-word-site.solc +++ b/test/examples/comptime/integer-lit-word-site.solc @@ -5,7 +5,7 @@ // passing literal to word parameter contract WordSite { - function main() -> word { + function main() returns (word) { let a : word = 42; let b : word = 0; return a; diff --git a/test/examples/comptime/integer-lit.solc b/test/examples/comptime/integer-lit.solc index db376d788..7cbdd1e0d 100644 --- a/test/examples/comptime/integer-lit.solc +++ b/test/examples/comptime/integer-lit.solc @@ -6,7 +6,7 @@ // // Expected: main() folds to word literal 55 (fib(10)). -function fib(comptime n : integer) -> comptime integer { +function fib(comptime n : integer) returns (comptime integer) { if (integerLt(n, 2)) { return n; } else { @@ -18,9 +18,9 @@ function fib(comptime n : integer) -> comptime integer { } contract IntegerLit { - function main() -> word { - let x : comptime integer = 10; - let res : comptime word = wordFromInteger(fib(x)); + function main() returns (word) { + let comptime x : integer = 10; + let comptime res : word = wordFromInteger(fib(x)); return res; } } diff --git a/test/examples/comptime/match_labels.solc b/test/examples/comptime/match_labels.solc index f88edeaa2..1fe7ac7bb 100644 --- a/test/examples/comptime/match_labels.solc +++ b/test/examples/comptime/match_labels.solc @@ -3,21 +3,21 @@ Covers: keccakLit of a literal, keccakLit of a concatenation, wildcard. */ -import std.{*}; +import {*} from std; contract MatchLabels { - function dispatch(selector : word) -> word { - match selector { - | comptime keccakLit("transfer(address,uint256)") => return 1; - | comptime keccakLit("balanceOf" + "(" + "address" + ")") => return 2; - | _ => return 0; - } + function dispatch(selector : word) returns (word) { + match (selector ) { + case comptime keccakLit("transfer(address,uint256)") { return 1; + } case comptime keccakLit("balanceOf" + "(" + "address" + ")") { return 2; + } default { return 0; + } } } - function main() -> word { - let t : comptime word = keccakLit("transfer(address,uint256)"); - let b : comptime word = keccakLit("balanceOf(address)"); + function main() returns (word) { + let comptime t : word = keccakLit("transfer(address,uint256)"); + let comptime b : word = keccakLit("balanceOf(address)"); return dispatch(t) + dispatch(b) + dispatch(0); } } diff --git a/test/examples/comptime/string-lit-keccak.solc b/test/examples/comptime/string-lit-keccak.solc index 5d950e41e..8ef6d915b 100644 --- a/test/examples/comptime/string-lit-keccak.solc +++ b/test/examples/comptime/string-lit-keccak.solc @@ -1,10 +1,10 @@ import std; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; contract StringLitKeccak { - public function main() -> word { + function main() public returns (word) { // keccakLit folds to a 256-bit word (EVM/Yul semantics) return std.keccakLit("abc"); } diff --git a/test/examples/comptime/string-lit-len.solc b/test/examples/comptime/string-lit-len.solc index 06a1a8e12..062de49b0 100644 --- a/test/examples/comptime/string-lit-len.solc +++ b/test/examples/comptime/string-lit-len.solc @@ -1,10 +1,10 @@ import std; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; contract StringLitLen { - public function main() -> word { + function main() public returns (word) { // strlenLit folds to a word return std.strlenLit("hello"); } diff --git a/test/examples/comptime/string-lit-ops.solc b/test/examples/comptime/string-lit-ops.solc index 95dad6681..dd09813b5 100644 --- a/test/examples/comptime/string-lit-ops.solc +++ b/test/examples/comptime/string-lit-ops.solc @@ -1,15 +1,15 @@ import std; -import std.{*}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +import {*} from std; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; // These functions are intended to be folded by MastEval at compile time. contract StringLitOps { - public function main() -> () { + function main() public returns (()) { // concatLit folds to a string literal, enabling revertLit("...") lowering - let s : comptime string = concatLit("ab", "cd"); + let comptime s : string = concatLit("ab", "cd"); std.revertLit(s); } } diff --git a/test/examples/comptime/uint256-lit.solc b/test/examples/comptime/uint256-lit.solc index 6eed609f3..dc7edfc24 100644 --- a/test/examples/comptime/uint256-lit.solc +++ b/test/examples/comptime/uint256-lit.solc @@ -1,10 +1,10 @@ // Bare integer literals at uint256-typed sites use `instance uint256 : Int`. // The instance's fromInteger wraps `wordFromInteger`, so an out-of-range // literal is truncated mod 2^256, matching the `word` site behaviour. -import std.{*}; +import {*} from std; contract Uint256Lit { - function main() -> word { + function main() returns (word) { let a : uint256 = 3; // 2^256 + 5 must truncate to 5. let b : uint256 = 0x10000000000000000000000000000000000000000000000000000000000000005; diff --git a/test/examples/dispatch/Revert.solc b/test/examples/dispatch/Revert.solc index 88e8229da..25b21657b 100644 --- a/test/examples/dispatch/Revert.solc +++ b/test/examples/dispatch/Revert.solc @@ -1,7 +1,7 @@ -import std.{*}; -import std.dispatch.{*}; +import {*} from std; +import {*} from std.dispatch; -function my_revert() -> word { +function my_revert() returns (word) { revertLit("regression"); return 0; } @@ -9,11 +9,11 @@ function my_revert() -> word { contract Foo { constructor() {} - public function noAnswer() -> uint256 { + function noAnswer() public returns (uint256) { return uint256(my_revert()); } - public function answer() -> uint256 { + function answer() public returns (uint256) { return uint256(42); } } diff --git a/test/examples/dispatch/array_copy.solc b/test/examples/dispatch/array_copy.solc index be64ac092..0e96581b4 100644 --- a/test/examples/dispatch/array_copy.solc +++ b/test/examples/dispatch/array_copy.solc @@ -1,46 +1,46 @@ -import std.{*}; -import std.dispatch.{*}; +import {*} from std; +import {*} from std.dispatch; // Whole-array assignment `a = b` follows Solidity: it is a deep copy, not an // alias; assigning an array to itself is a no-op; and a copy that shrinks the // destination clears the slots it abandons, so regrowing yields zeros. contract ArrayCopy { - a : array(uint256); - b : array(uint256); + a : uint256[]; + b : uint256[]; constructor() {} - public function pushA(v : uint256) -> () { + function pushA(v : uint256) public returns (()) { ArrayPush.push(a, v); } - public function pushB(v : uint256) -> () { + function pushB(v : uint256) public returns (()) { ArrayPush.push(b, v); } // a = b - public function copyBintoA() -> () { + function copyBintoA() public returns (()) { a = b; } // a = a (must be a no-op, not a self-clobbering copy) - public function copyAintoA() -> () { + function copyAintoA() public returns (()) { a = a; } - public function setB(i : uint256, v : uint256) -> () { + function setB(i : uint256, v : uint256) public returns (()) { b[i] = v; } - public function growA(n : uint256) -> () { + function growA(n : uint256) public returns (()) { Array.setLength(a, n); } - public function lenA() -> uint256 { + function lenA() public returns (uint256) { return Array.length(a); } - public function getA(i : uint256) -> uint256 { + function getA(i : uint256) public returns (uint256) { return a[i]; } } diff --git a/test/examples/dispatch/array_nested.solc b/test/examples/dispatch/array_nested.solc index 1d84d868d..7cd6c0a41 100644 --- a/test/examples/dispatch/array_nested.solc +++ b/test/examples/dispatch/array_nested.solc @@ -1,5 +1,5 @@ -import std.{*}; -import std.dispatch.{*}; +import {*} from std; +import {*} from std.dispatch; // Nested storage arrays and aliasing, on the EVM. // @@ -10,48 +10,48 @@ import std.dispatch.{*}; // Binding an array field to a local is an alias (Solidity's `T[] storage p`), not // a copy: mutating through the local must be visible through the field. contract NestedArray { - grid : array(array(uint256)); - flat : array(uint256); + grid : uint256[][]; + flat : uint256[]; constructor() {} - public function growOuter(n : uint256) -> () { + function growOuter(n : uint256) public returns (()) { Array.setLength(grid, n); } // grid[i].push(v) -- the inner handle comes straight out of the index - public function pushInner(i : uint256, v : uint256) -> () { + function pushInner(i : uint256, v : uint256) public returns (()) { ArrayPush.push(grid[i], v); } - public function innerLen(i : uint256) -> uint256 { + function innerLen(i : uint256) public returns (uint256) { return Array.length(grid[i]); } - public function get2(i : uint256, j : uint256) -> uint256 { + function get2(i : uint256, j : uint256) public returns (uint256) { return grid[i][j]; } - public function set2(i : uint256, j : uint256, v : uint256) -> () { + function set2(i : uint256, j : uint256, v : uint256) public returns (()) { grid[i][j] = v; } // Mutate `flat` through a local alias; the field must observe it. - public function aliasPush(v : uint256) -> () { - let p : storage(array(uint256)) = flat; + function aliasPush(v : uint256) public returns (()) { + let p : uint256[] storage = flat; ArrayPush.push(p, v); } - public function aliasSet(i : uint256, v : uint256) -> () { - let p : storage(array(uint256)) = flat; + function aliasSet(i : uint256, v : uint256) public returns (()) { + let p : uint256[] storage = flat; p[i] = v; } - public function flatLen() -> uint256 { + function flatLen() public returns (uint256) { return Array.length(flat); } - public function getFlat(i : uint256) -> uint256 { + function getFlat(i : uint256) public returns (uint256) { return flat[i]; } } diff --git a/test/examples/dispatch/array_ops.solc b/test/examples/dispatch/array_ops.solc index 4e42c35a6..ac820cc73 100644 --- a/test/examples/dispatch/array_ops.solc +++ b/test/examples/dispatch/array_ops.solc @@ -1,33 +1,33 @@ -import std.{*}; -import std.dispatch.{*}; +import {*} from std; +import {*} from std.dispatch; // Storage-array primitives end to end: push / pop / length / indexed read, // the two revert paths (index out of range, pop on empty), and the guarantee // that abandoned slots are zeroed -- so regrowing an array never resurrects the // values that `pop` or a shrinking `setLength` dropped. contract ArrayOps { - xs : array(uint256); + xs : uint256[]; constructor() {} // NOTE: not named `add` -- that collides with the Yul builtin of the same name. - public function pushVal(v : uint256) -> () { + function pushVal(v : uint256) public returns (()) { ArrayPush.push(xs, v); } - public function popArr() -> () { + function popArr() public returns (()) { Array.pop(xs); } - public function len() -> uint256 { + function len() public returns (uint256) { return Array.length(xs); } - public function get(i : uint256) -> uint256 { + function get(i : uint256) public returns (uint256) { return xs[i]; } - public function grow(n : uint256) -> () { + function grow(n : uint256) public returns (()) { Array.setLength(xs, n); } } diff --git a/test/examples/dispatch/array_string.solc b/test/examples/dispatch/array_string.solc index 1e285877d..498075b35 100644 --- a/test/examples/dispatch/array_string.solc +++ b/test/examples/dispatch/array_string.solc @@ -1,5 +1,5 @@ -import std.{*}; -import std.dispatch.{*}; +import {*} from std; +import {*} from std.dispatch; // Storage arrays of a *dynamic* element type. `push` stores a `memory(string)` // through `storage(string):CanStore(memory(string))`, `arr[i]` reads one back, @@ -8,37 +8,37 @@ import std.dispatch.{*}; // Both the short (<32 bytes, inline) and long (>=32 bytes, keccak tail) string // encodings are exercised. contract ArrayString { - names : array(string); - backup : array(string); + names : string[]; + backup : string[]; constructor() {} - public function pushName(s : memory(string)) -> () { + function pushName(s : string memory) public returns (()) { ArrayPush.push(names, s); } - public function setName(i : uint256, s : memory(string)) -> () { + function setName(i : uint256, s : string memory) public returns (()) { names[i] = s; } - public function getName(i : uint256) -> memory(string) { + function getName(i : uint256) public returns (string memory) { return names[i]; } - public function len() -> uint256 { + function len() public returns (uint256) { return Array.length(names); } // backup = names - public function saveBackup() -> () { + function saveBackup() public returns (()) { backup = names; } - public function getBackup(i : uint256) -> memory(string) { + function getBackup(i : uint256) public returns (string memory) { return backup[i]; } - public function lenBackup() -> uint256 { + function lenBackup() public returns (uint256) { return Array.length(backup); } } diff --git a/test/examples/dispatch/assembly.solc b/test/examples/dispatch/assembly.solc index a39e7cdde..a6f1d596a 100644 --- a/test/examples/dispatch/assembly.solc +++ b/test/examples/dispatch/assembly.solc @@ -1,5 +1,5 @@ -import std.{*}; -import std.dispatch.{*}; +import {*} from std; +import {*} from std.dispatch; contract C { constructor() {} @@ -7,7 +7,7 @@ contract C { // Exercises a Yul block that declares an uninitialized `let y`, assigns the // boolean literal `true` to it, and writes it back to the surrounding // `word` local `x`. `true` is the word `1`, so this returns uint256(1). - public function asmBool() -> uint256 { + function asmBool() public returns (uint256) { let x : word; assembly { let y diff --git a/test/examples/dispatch/basic.solc b/test/examples/dispatch/basic.solc index 37fd4c3a7..a30d72816 100644 --- a/test/examples/dispatch/basic.solc +++ b/test/examples/dispatch/basic.solc @@ -1,99 +1,99 @@ -import std.{*}; -import std.dispatch.{*}; -import std.opcodes.{address as address_}; +import {*} from std; +import {*} from std.dispatch; +import {address as address_} from std.opcodes; -function self() -> address { +function self() returns (address) { return address(address_()); } contract C { constructor() {} - public function nothing() -> () {} + function nothing() public returns (()) {} // Re-enters this very contract via raw_call(address(this), ...). The payload // is the 4-byte selector of an existing entry point (something(), 0xa7a0d537), // built by left-aligning it in a bytes32 and truncating to 4 bytes. The inner // call succeeds, so raw_call reports ok == true and returns its returndata // (the abi-encoded uint256(1)). - public function callSelf() -> (bool, memory(bytes)) { + function callSelf() public returns ((bool, bytes memory)) { let sel: bytes32 = bytes32(0xa7a0d53700000000000000000000000000000000000000000000000000000000); let payload = truncate(to_bytes(sel), 4); - match raw_call(self(), uint256(0), payload) { - | (ok, ret) => return (ok, ret); - } + match (raw_call(self(), uint256(0), payload) ) { + case (ok, ret) { return (ok, ret); + } } } // Same shape, but the selector (0xdeadc0de) matches no entry point, so dispatch // reverts (there is no fallback). raw_call swallows the inner revert and reports // ok == false; this outer call itself still succeeds and returns the revert // returndata (the 4-byte NoFallback error selector). - public function callSelfInvalid() -> (bool, memory(bytes)) { + function callSelfInvalid() public returns ((bool, bytes memory)) { let sel: bytes32 = bytes32(0xdeadc0de00000000000000000000000000000000000000000000000000000000); let payload = truncate(to_bytes(sel), 4); - match raw_call(self(), uint256(0), payload) { - | (ok, ret) => return (ok, ret); - } + match (raw_call(self(), uint256(0), payload) ) { + case (ok, ret) { return (ok, ret); + } } } - public function something() -> (uint256) { + function something() public returns (uint256) { return uint256(1); } - public function add2(x : uint256, y : uint256) -> uint256 { + function add2(x : uint256, y : uint256) public returns (uint256) { return Add.add(x,y); } - public function add3(x : uint256, y : uint256, z : uint256) -> uint256 { + function add3(x : uint256, y : uint256, z : uint256) public returns (uint256) { return Add.add(z, Add.add(x,y)); } - public function addmod3(x : uint256, y : uint256, k : uint256) -> uint256 { + function addmod3(x : uint256, y : uint256, k : uint256) public returns (uint256) { return addmod(x, y, k); } - public function mulmod3(x : uint256, y : uint256, k : uint256) -> uint256 { + function mulmod3(x : uint256, y : uint256, k : uint256) public returns (uint256) { return mulmod(x, y, k); } // Bitwise / modulo via the syntactic sugar only (no explicit class calls): // `^` -> BitXor.bxor, `|` -> BitOr.bor, `&` -> BitAnd.band, `%` -> Mod.mod. - public function bxor2(x : uint256, y : uint256) -> uint256 { + function bxor2(x : uint256, y : uint256) public returns (uint256) { return x ^ y; } - public function bor2(x : uint256, y : uint256) -> uint256 { + function bor2(x : uint256, y : uint256) public returns (uint256) { return x | y; } - public function band2(x : uint256, y : uint256) -> uint256 { + function band2(x : uint256, y : uint256) public returns (uint256) { return x & y; } - public function mod2(x : uint256, y : uint256) -> uint256 { + function mod2(x : uint256, y : uint256) public returns (uint256) { return x % y; } - public function id_bytes(b: memory(bytes)) -> memory(bytes) { + function id_bytes(b: bytes memory) public returns (bytes memory) { return b; } - public function id_string(b: memory(string)) -> memory(string) { + function id_string(b: string memory) public returns (string memory) { return b; } - public function id_bytes32(b: bytes32) -> bytes32 { + function id_bytes32(b: bytes32) public returns (bytes32) { return b; } - public function id_address(a: address) -> address { + function id_address(a: address) public returns (address) { return a; } - public function id_pair() -> (uint256, uint256) { + function id_pair() public returns ((uint256, uint256)) { return (uint256(7), uint256(11)); } - function hidden() -> (uint256) { + function hidden() returns (uint256) { return uint256(42); } } diff --git a/test/examples/dispatch/concat.solc b/test/examples/dispatch/concat.solc index 4d0b59bfe..c9d0e6c78 100644 --- a/test/examples/dispatch/concat.solc +++ b/test/examples/dispatch/concat.solc @@ -1,42 +1,42 @@ -import std.{*}; -import std.dispatch.{*}; +import {*} from std; +import {*} from std.dispatch; contract C { constructor() {} - public function concat_b32_b32(a: bytes32, b: bytes32) -> memory(bytes) { + function concat_b32_b32(a: bytes32, b: bytes32) public returns (bytes memory) { return concat(a, b); } - public function concat_b32_bytes(a: bytes32, b: memory(bytes)) -> memory(bytes) { + function concat_b32_bytes(a: bytes32, b: bytes memory) public returns (bytes memory) { return concat(a, b); } - public function concat_bytes_bytes(a: memory(bytes), b: memory(bytes)) -> memory(bytes) { + function concat_bytes_bytes(a: bytes memory, b: bytes memory) public returns (bytes memory) { return concat(a, b); } - public function to_bytes_b32(a: bytes32) -> memory(bytes) { + function to_bytes_b32(a: bytes32) public returns (bytes memory) { return to_bytes(a); } - public function to_bytes_bytes(a: memory(bytes)) -> memory(bytes) { + function to_bytes_bytes(a: bytes memory) public returns (bytes memory) { return to_bytes(a); } - public function empty_area(n: uint256) -> memory(bytes) { + function empty_area(n: uint256) public returns (bytes memory) { return to_bytes(empty(Typedef.rep(n))); } - public function concat_b32_empty(a: bytes32, n: uint256) -> memory(bytes) { + function concat_b32_empty(a: bytes32, n: uint256) public returns (bytes memory) { return concat(a, empty(Typedef.rep(n))); } - public function concat_nested_b32(a: bytes32, b: bytes32, c: bytes32) -> memory(bytes) { + function concat_nested_b32(a: bytes32, b: bytes32, c: bytes32) public returns (bytes memory) { return concat(a, concat(b, c)); } - public function concat_nested_empty(a: bytes32, n: uint256, c: bytes32) -> memory(bytes) { + function concat_nested_empty(a: bytes32, n: uint256, c: bytes32) public returns (bytes memory) { return concat(a, concat(empty(Typedef.rep(n)), c)); } } diff --git a/test/examples/dispatch/counter.solc b/test/examples/dispatch/counter.solc index 5b7956998..be3b0fc8d 100644 --- a/test/examples/dispatch/counter.solc +++ b/test/examples/dispatch/counter.solc @@ -1,5 +1,5 @@ -import std.{*}; -import std.dispatch.{*}; +import {*} from std; +import {*} from std.dispatch; contract Counter { counter : uint256; @@ -7,7 +7,7 @@ contract Counter { counter = 41; } - public function test() -> uint256 { + function test() public returns (uint256) { counter = counter + 1; return counter; } diff --git a/test/examples/dispatch/ecrecover.solc b/test/examples/dispatch/ecrecover.solc index 9cb8c7906..d30aed28f 100644 --- a/test/examples/dispatch/ecrecover.solc +++ b/test/examples/dispatch/ecrecover.solc @@ -1,8 +1,8 @@ -import std.{*}; -import std.dispatch.{*}; +import {*} from std; +import {*} from std.dispatch; contract EcrecoverTest { - public function recover() -> address { + function recover() public returns (address) { let h: bytes32 = bytes32(0xaabbccddeeff00112233445566778899aabbccddeeff00112233445566778899); let v: uint256 = uint256(27); let r: bytes32 = bytes32(0xb3ba6dd3757d18f28736e84b1296af85362b7bdf4548710733c6325abf95311d); @@ -14,7 +14,7 @@ contract EcrecoverTest { // but recovers nothing, so it returns empty output and `res` stays 0. This // exercises the `ECRecoverFailed()` (0x4fbfae63) revert path. `v` and `s` // are kept well-formed so neither the malleability nor call-failed guards fire. - public function recoverFail() -> address { + function recoverFail() public returns (address) { let h: bytes32 = bytes32(0xaabbccddeeff00112233445566778899aabbccddeeff00112233445566778899); let v: uint256 = uint256(27); let r: bytes32 = bytes32(0x0); diff --git a/test/examples/dispatch/empty.solc b/test/examples/dispatch/empty.solc index 87b82bbf1..7f9a2c346 100644 --- a/test/examples/dispatch/empty.solc +++ b/test/examples/dispatch/empty.solc @@ -1,5 +1,5 @@ -import std.{*}; -import std.dispatch.{*}; +import {*} from std; +import {*} from std.dispatch; contract C { constructor() {} diff --git a/test/examples/dispatch/empty_no_constructor.solc b/test/examples/dispatch/empty_no_constructor.solc index 66a42685c..f9e6a5adc 100644 --- a/test/examples/dispatch/empty_no_constructor.solc +++ b/test/examples/dispatch/empty_no_constructor.solc @@ -1,5 +1,5 @@ -import std.{*}; -import std.dispatch.{*}; +import {*} from std; +import {*} from std.dispatch; contract C { } diff --git a/test/examples/dispatch/fallback.solc b/test/examples/dispatch/fallback.solc index 9bf224527..bfa81d29b 100644 --- a/test/examples/dispatch/fallback.solc +++ b/test/examples/dispatch/fallback.solc @@ -1,14 +1,14 @@ -import std.{*}; -import std.dispatch.{*}; +import {*} from std; +import {*} from std.dispatch; contract WithFallback { constructor() {} - public function answer() -> uint256 { + function answer() public returns (uint256) { return uint256(42); } - fallback() -> () { + fallback() external { revertLit("fallback-was-called"); } } diff --git a/test/examples/dispatch/fib.solc b/test/examples/dispatch/fib.solc index 3c01cd4bb..c9406f9e3 100644 --- a/test/examples/dispatch/fib.solc +++ b/test/examples/dispatch/fib.solc @@ -1,12 +1,12 @@ -import std.dispatch.{*}; +import {*} from std.dispatch; -function fib(n : word) -> word { +function fib(n : word) returns (word) { if(n < 2) { return n; } else {return fib(n-1) + fib(n-2); } } contract Fib { constructor() {} - public function test() -> uint256 { + function test() public returns (uint256) { return uint256(fib(10)); } } diff --git a/test/examples/dispatch/forloops.solc b/test/examples/dispatch/forloops.solc index f2086ae45..4f56160cc 100644 --- a/test/examples/dispatch/forloops.solc +++ b/test/examples/dispatch/forloops.solc @@ -1,5 +1,5 @@ -import std.{*}; -import std.dispatch.{*}; +import {*} from std; +import {*} from std.dispatch; contract C { counter : uint256; @@ -8,17 +8,17 @@ contract C { counter = uint256(0); } - function bump() -> uint256 { + function bump() returns (uint256) { counter = counter + uint256(1); return counter; } - public function getCounter() -> uint256 { + function getCounter() public returns (uint256) { return counter; } // Sum of 0..4 with early `break` at i == 5. - public function break_sum() -> uint256 { + function break_sum() public returns (uint256) { let s : uint256 = uint256(0); for (let i : uint256 = uint256(0); i < uint256(10); i = i + uint256(1)) { if (i == uint256(5)) { @@ -32,7 +32,7 @@ contract C { // Sum of 5..9 using `continue` to skip the iterations where i < 5. // The post-statement (i = i + 1) must still run on `continue`, otherwise // the loop would never terminate. - public function continue_sum() -> uint256 { + function continue_sum() public returns (uint256) { let s : uint256 = uint256(0); for (let i : uint256 = uint256(0); i < uint256(10); i = i + uint256(1)) { if (i < uint256(5)) { @@ -44,7 +44,7 @@ contract C { } // Empty initializer: `i` is declared/initialised outside the loop. - public function empty_init() -> uint256 { + function empty_init() public returns (uint256) { let i : uint256 = uint256(3); let s : uint256 = uint256(0); for (; i < uint256(7); i = i + uint256(1)) { @@ -54,7 +54,7 @@ contract C { } // Empty post-body: the increment is done in the loop body. - public function empty_post() -> uint256 { + function empty_post() public returns (uint256) { let s : uint256 = uint256(0); for (let i : uint256 = uint256(0); i < uint256(4); ) { s = s + i; @@ -66,7 +66,7 @@ contract C { // Side effect in the condition: `bump()` increments storage on every // probe (including the failing one), so observing `counter` afterwards // proves the condition ran the expected number of times. - public function cond_side_effect() -> uint256 { + function cond_side_effect() public returns (uint256) { counter = uint256(0); for (let i : uint256 = uint256(0); bump() < uint256(5); i = i + uint256(1)) {} return counter; @@ -74,7 +74,7 @@ contract C { // Side effect in the post-body: `bump()` runs once per completed // iteration, so `counter` ends equal to the iteration count. - public function post_side_effect() -> uint256 { + function post_side_effect() public returns (uint256) { counter = uint256(0); for (let i : uint256 = uint256(0); i < uint256(3); bump()) { i = i + uint256(1); @@ -83,7 +83,7 @@ contract C { } // Nested `for` -- sum of i*j for i,j in 1..3. - public function double_loop() -> uint256 { + function double_loop() public returns (uint256) { let s : uint256 = uint256(0); for (let i : uint256 = uint256(1); i < uint256(4); i = i + uint256(1)) { for (let j : uint256 = uint256(1); j < uint256(4); j = j + uint256(1)) { diff --git a/test/examples/dispatch/generic_product.solc b/test/examples/dispatch/generic_product.solc index 5a2ce10fe..84b14b49b 100644 --- a/test/examples/dispatch/generic_product.solc +++ b/test/examples/dispatch/generic_product.solc @@ -1,21 +1,21 @@ -import std.{*}; -import std.dispatch.{*}; -import std.opcodes.{mload, mstore}; -import std.Generic.{*}; -import std.ABIGeneric.{*}; +import {*} from std; +import {*} from std.dispatch; +import {mload, mstore} from std.opcodes; +import {*} from std.Generic; +import {*} from std.ABIGeneric; -pragma no-generic-instance-for Point; +pragma solcore noGenericInstanceFor Point; -data Point = Point(uint256, uint256); +enum Point { Point(uint256, uint256) } // Only requirement: Generic instance using the primitive pair type. // rep = (uint256, uint256) — primitive Solcore pair -instance Point : Generic((uint256, uint256)) { - function from(p : Point) -> (uint256, uint256) { - match p { | Point(x, y) => return (x, y); } +impl Generic { + function from(p : Point) returns ((uint256, uint256)) { + match (p ) { case Point(x, y) { return (x, y); } } } - function to(t : (uint256, uint256)) -> Point { - match t { | (x, y) => return Point(x, y); } + function to(t : (uint256, uint256)) returns (Point) { + match (t ) { case (x, y) { return Point(x, y); } } } } @@ -23,7 +23,7 @@ contract GenericProduct { constructor() {} // Calls encode; returns word at offset 0 (the x field). - public function encodeX(a : uint256, b : uint256) -> uint256 { + function encodeX(a : uint256, b : uint256) public returns (uint256) { let p : Point = Point(a, b); let buf = allocate_zeroed_memory(64); encode(p, buf, 0, 64); @@ -31,7 +31,7 @@ contract GenericProduct { } // Calls encode; returns word at offset 32 (the y field). - public function encodeY(a : uint256, b : uint256) -> uint256 { + function encodeY(a : uint256, b : uint256) public returns (uint256) { let p : Point = Point(a, b); let buf = allocate_zeroed_memory(64); encode(p, buf, 0, 64); @@ -39,13 +39,13 @@ contract GenericProduct { } // Writes [a][b] into memory, calls decode, returns the x field. - public function decodeX(a : uint256, b : uint256) -> uint256 { + function decodeX(a : uint256, b : uint256) public returns (uint256) { let buf = allocate_zeroed_memory(64); mstore(buf, Typedef.rep(a)); mstore(buf + 32, Typedef.rep(b)); let rdr : MemoryWordReader = MemoryWordReader(buf); - let dec : ABIDecoder(Point, MemoryWordReader) = ABIDecoder(rdr); + let dec : ABIDecoder = ABIDecoder(rdr); let p : Point = decode(dec, 0); - match p { | Point(x, _) => return x; } + match (p ) { case Point(x, _) { return x; } } } } diff --git a/test/examples/dispatch/generic_sum.solc b/test/examples/dispatch/generic_sum.solc index 164f7bc74..f4b2739ef 100644 --- a/test/examples/dispatch/generic_sum.solc +++ b/test/examples/dispatch/generic_sum.solc @@ -1,27 +1,27 @@ -import std.{*}; -import std.dispatch.{*}; -import std.opcodes.{mload, mstore}; -import std.Generic.{*}; -import std.ABIGeneric.{*}; +import {*} from std; +import {*} from std.dispatch; +import {mload, mstore} from std.opcodes; +import {*} from std.Generic; +import {*} from std.ABIGeneric; -pragma no-generic-instance-for Option; +pragma solcore noGenericInstanceFor Option; -data Option(a) = None | Some(a); +enum Option { None, Some(a) } // Only requirement: Generic instance using the primitive sum type. // rep = sum((), uint256): inl(()) = None, inr(v) = Some(v) -instance Option(uint256) : Generic(sum((), uint256)) { - function from(x : Option(uint256)) -> sum((), uint256) { - match x { - | Option.None => return inl(()); - | Option.Some(v) => return inr(v); - } +impl Generic, sum<(), uint256>> { + function from(x : Option) returns (sum<(), uint256>) { + match (x ) { + case Option.None { return inl(()); + } case Option.Some(v) { return inr(v); + } } } - function to(r : sum((), uint256)) -> Option(uint256) { - match r { - | inl(_) => return Option.None; - | inr(v) => return Option.Some(v); - } + function to(r : sum<(), uint256>) returns (Option) { + match (r ) { + case inl(_) { return Option.None; + } case inr(v) { return Option.Some(v); + } } } } @@ -30,8 +30,8 @@ contract GenericSum { // Calls encode; returns the tag word (first 32 bytes). // None → 0 - public function encodeNone() -> uint256 { - let x : Option(uint256) = Option.None; + function encodeNone() public returns (uint256) { + let x : Option = Option.None; let buf = allocate_zeroed_memory(64); encode(x, buf, 0, 64); return Typedef.abs(mload(buf)); @@ -39,32 +39,32 @@ contract GenericSum { // Calls encode; returns the tag word (first 32 bytes). // Some(n) → 1 - public function encodeSomeTag(n : uint256) -> uint256 { - let x : Option(uint256) = Option.Some(n); + function encodeSomeTag(n : uint256) public returns (uint256) { + let x : Option = Option.Some(n); let buf = allocate_zeroed_memory(64); encode(x, buf, 0, 64); return Typedef.abs(mload(buf)); } // Calls encode; returns the payload word (bytes 32-63). - public function encodePayload(n : uint256) -> uint256 { - let x : Option(uint256) = Option.Some(n); + function encodePayload(n : uint256) public returns (uint256) { + let x : Option = Option.Some(n); let buf = allocate_zeroed_memory(64); encode(x, buf, 0, 64); return Typedef.abs(mload(buf + 32)); } // Writes [tag][value] into memory, calls decode, returns the value or 0. - public function decodeAndGet(tag : uint256, value : uint256) -> uint256 { + function decodeAndGet(tag : uint256, value : uint256) public returns (uint256) { let buf = allocate_zeroed_memory(64); mstore(buf, Typedef.rep(tag)); mstore(buf + 32, Typedef.rep(value)); let rdr : MemoryWordReader = MemoryWordReader(buf); - let dec : ABIDecoder(Option(uint256), MemoryWordReader) = ABIDecoder(rdr); - let opt : Option(uint256) = decode(dec, 0); - match opt { - | Option.None => return uint256(0); - | Option.Some(v) => return v; - } + let dec : ABIDecoder, MemoryWordReader> = ABIDecoder(rdr); + let opt : Option = decode(dec, 0); + match (opt ) { + case Option.None { return uint256(0); + } case Option.Some(v) { return v; + } } } } diff --git a/test/examples/dispatch/hashes.solc b/test/examples/dispatch/hashes.solc index c7ddf111b..c9a32e51b 100644 --- a/test/examples/dispatch/hashes.solc +++ b/test/examples/dispatch/hashes.solc @@ -1,9 +1,9 @@ -import std.{*}; -import std.dispatch.{*}; -import std.opcodes.{mstore}; +import {*} from std; +import {*} from std.dispatch; +import {mstore} from std.opcodes; // Build a memory(bytes) holding the three-byte string "abc". -function abcBytes() -> memory(bytes) { +function abcBytes() returns (bytes memory) { let p = allocate_memory(64); mstore(p, 3); mstore(p + 32, 0x6162630000000000000000000000000000000000000000000000000000000000); @@ -13,19 +13,19 @@ function abcBytes() -> memory(bytes) { contract C { constructor() {} - public function keccak() -> bytes32 { + function keccak() public returns (bytes32) { return keccak256_(abcBytes()); } - public function sha() -> bytes32 { + function sha() public returns (bytes32) { return sha256(abcBytes()); } - public function ripemd() -> bytes32 { + function ripemd() public returns (bytes32) { return ripemd160(abcBytes()); } - public function erc7201_(id: memory(bytes)) -> bytes32 { + function erc7201_(id: bytes memory) public returns (bytes32) { return erc7201(id); } } diff --git a/test/examples/dispatch/memory.solc b/test/examples/dispatch/memory.solc index eec43817b..b9ded759b 100644 --- a/test/examples/dispatch/memory.solc +++ b/test/examples/dispatch/memory.solc @@ -1,16 +1,16 @@ -import std.{*}; -import std.dispatch.{*}; -import std.opcodes.{mstore}; +import {*} from std; +import {*} from std.dispatch; +import {mstore} from std.opcodes; contract C { - public function dirty_allocate() -> memory(bytes) { + function dirty_allocate() public returns (bytes memory) { mstore(get_free_memory() + 32, 0xdeadc0de); let ptr = allocate_memory(32 + 32); mstore(ptr, 32); return memory(ptr); } - public function clear_allocate() -> memory(bytes) { + function clear_allocate() public returns (bytes memory) { mstore(get_free_memory() + 32, 0xdeadc0de); let ptr = allocate_zeroed_memory(32 + 32); mstore(ptr, 32); diff --git a/test/examples/dispatch/miniERC20.solc b/test/examples/dispatch/miniERC20.solc index 9391bb670..26f41570c 100644 --- a/test/examples/dispatch/miniERC20.solc +++ b/test/examples/dispatch/miniERC20.solc @@ -1,7 +1,7 @@ -import std.{*}; -import std.dispatch.{*}; +import {*} from std; +import {*} from std.dispatch; -function caller() -> address { +function caller() returns (address) { let res: word; assembly { res := caller() @@ -15,10 +15,10 @@ contract MiniERC20 { owner : address; decimals : uint256; // should be uint8 when we get to it totalSupply : uint256; - balances : mapping(address,uint256); - allowance : mapping(address, mapping(address, uint256)); + balances : mapping(address => uint256); + allowance : mapping(address => mapping(address => uint256)); - constructor(name_ : memory(string), symbol_ : memory(string), totalSupply_:uint256) { + constructor(name_ : string memory, symbol_ : string memory, totalSupply_:uint256) { name = name_; symbol = symbol_; owner = caller(); @@ -26,45 +26,45 @@ contract MiniERC20 { mint(totalSupply_); } - public function name() -> memory(string) { + function name() public returns (string memory) { return name; } - public function symbol() -> memory(string) { + function symbol() public returns (string memory) { return symbol; } - public function decimals() -> uint256 { + function decimals() public returns (uint256) { return decimals; } - public function allowance(owner_ : address, spender: address) -> uint256 { + function allowance(owner_ : address, spender: address) public returns (uint256) { return allowance[owner_][spender]; // don't use "owner" here } - public function balanceOf(account : address) -> uint256 { + function balanceOf(account : address) public returns (uint256) { return balances[account]; } - public function totalSupply() -> uint256 { + function totalSupply() public returns (uint256) { return totalSupply; } // Note that this is not access guarded — the minting always goes to the owner - public function mint(amount:uint256) -> () { + function mint(amount:uint256) public returns (()) { balances[owner] = Num.add(balances[owner], amount); totalSupply = Num.add(totalSupply, amount); } - public function transfer(dst : address, amt : uint256) -> bool { + function transfer(dst : address, amt : uint256) public returns (bool) { return transferFrom(caller(), dst, amt); } - public function transferFrom(src:address, dst:address, amt:uint256) -> bool { + function transferFrom(src:address, dst:address, amt:uint256) public returns (bool) { let msg_sender = caller(); require(balances[src] >= amt, Error(0xf4d678b8)); // InsufficientBalance() - if (src != msg_sender && allowance[src][msg_sender] != (Num.maxVal():uint256)) { + if (src != msg_sender && allowance[src][msg_sender] != (Num.maxVal() as uint256)) { require(allowance[src][msg_sender] >= amt, Error(0x13be252b)); // InsufficientAllowance() allowance[src][msg_sender] -= amt; @@ -75,7 +75,7 @@ contract MiniERC20 { return true; } - public function approve(usr: address, amt: uint256) -> bool { + function approve(usr: address, amt: uint256) public returns (bool) { let msg_sender = caller(); allowance[msg_sender][usr] = amt; // emit Approval(msg.sender, usr, amt); @@ -84,11 +84,11 @@ contract MiniERC20 { // testing - public function getMyBalance() -> uint256 { + function getMyBalance() public returns (uint256) { return balances[caller()]; } - public function test() -> uint256 { + function test() public returns (uint256) { approve(address(0), 10); transferFrom(caller(), address(0), 958); return getMyBalance(); diff --git a/test/examples/dispatch/neg.solc b/test/examples/dispatch/neg.solc index b04d0a8a9..6c2e4b1e0 100644 --- a/test/examples/dispatch/neg.solc +++ b/test/examples/dispatch/neg.solc @@ -1,40 +1,38 @@ -import std.{*}; -import std.dispatch.{*}; +import {*} from std; +import {*} from std.dispatch; -forall a. -class a : Neg { - function neg(x:a) -> a; +trait Neg { + function neg(x:a) returns (a); } -data B = F | T; -data Pair(a,b) = Pair(a,b); +enum B { F, T } +enum Pair { Pair(a, b) } -instance B : Neg { - function neg (x : B) -> B { - match x { - | B.F => return B.T; - | B.T => return B.F; - } +impl Neg { + function neg (x : B) returns (B) { + match (x ) { + case B.F { return B.T; + } case B.T { return B.F; + } } } } -forall a b . function pairfst (p : Pair(a,b)) -> a { - match p { - | Pair(x,y) => return x; - } +function pairfst (p : Pair) returns (a) { + match (p ) { + case Pair(x,y) { return x; + } } } -forall a b . function pairsnd(p : Pair(a,b)) -> b { - match p { - | Pair(x,y) => return y; - } +function pairsnd(p : Pair) returns (b) { + match (p ) { + case Pair(x,y) { return y; + } } } -forall a b. -a:Neg,b:Neg => instance Pair(a,b):Neg { - function neg(p:Pair(a,b)) -> Pair(a,b) { +impl Neg> where a: Neg, b: Neg { + function neg(p:Pair) returns (Pair) { return Pair(Neg.neg (pairfst(p)), Neg.neg(pairsnd(p))); } } @@ -49,21 +47,21 @@ instance (a:Neg,b:Neg) => Pair(a,b):Neg { } */ - function bnot(x:B) -> B { - match x { - | B.T => return B.F; - | B.F => return B.T; - } + function bnot(x:B) returns (B) { + match (x ) { + case B.T { return B.F; + } case B.F { return B.T; + } } } - function fromB(b:B) -> word { - match b { - | B.F => return 0; - | B.T => return 1; - } + function fromB(b:B) returns (word) { + match (b ) { + case B.F { return 0; + } case B.T { return 1; + } } } contract NegPair { constructor() {} - public function negPair() -> uint256 { return uint256(fromB(pairfst(Neg.neg(Pair(B.F,B.T))))); } + function negPair() public returns (uint256) { return uint256(fromB(pairfst(Neg.neg(Pair(B.F,B.T))))); } } diff --git a/test/examples/dispatch/nonpayable_ctor.solc b/test/examples/dispatch/nonpayable_ctor.solc index c19c104bc..d37d107c6 100644 --- a/test/examples/dispatch/nonpayable_ctor.solc +++ b/test/examples/dispatch/nonpayable_ctor.solc @@ -1,5 +1,5 @@ -import std.{*}; -import std.dispatch.{*}; +import {*} from std; +import {*} from std.dispatch; // A contract whose constructor is NOT marked `payable`. Deploying it with an // incoming value transfer must revert with the NonPayableReceivedValue error @@ -7,7 +7,7 @@ import std.dispatch.{*}; contract NonPayableCtor { constructor() {} - public function balance() -> uint256 { + function balance() public returns (uint256) { let value; assembly { value := selfbalance() diff --git a/test/examples/dispatch/ownable.solc b/test/examples/dispatch/ownable.solc index b20be59b9..532aae607 100644 --- a/test/examples/dispatch/ownable.solc +++ b/test/examples/dispatch/ownable.solc @@ -1,10 +1,10 @@ -import std.{*}; -import std.dispatch.{*}; +import {*} from std; +import {*} from std.dispatch; // caller() is not in the std library yet, // so every contract must define its own -function caller() -> address { +function caller() returns (address) { let res: word; assembly { res := caller() @@ -20,11 +20,11 @@ contract Ownable { } // named getOwner() instead of owner() to avoid collision with the field name - public function getOwner() -> address { + function getOwner() public returns (address) { return owner; } - public function changeOwner(newOwner : address) -> () { + function changeOwner(newOwner : address) public returns (()) { require(caller() == owner, Error(0x12b0c500)); // OwnableUnauthorizedAccount() owner = newOwner; } diff --git a/test/examples/dispatch/payable.solc b/test/examples/dispatch/payable.solc index 553275b1d..f7f561400 100644 --- a/test/examples/dispatch/payable.solc +++ b/test/examples/dispatch/payable.solc @@ -1,10 +1,10 @@ -import std.{*}; -import std.dispatch.{*}; +import {*} from std; +import {*} from std.dispatch; contract PayableTest { constructor() {} - public payable function deposit() -> uint256 { + function deposit() public payable returns (uint256) { let value; assembly { value := callvalue() @@ -12,7 +12,7 @@ contract PayableTest { return uint256(value); } - public function balance() -> uint256 { + function balance() public returns (uint256) { let value; assembly { value := selfbalance() @@ -20,7 +20,7 @@ contract PayableTest { return uint256(value); } - payable fallback() -> () { + fallback() external payable { let value; assembly { value := callvalue() diff --git a/test/examples/dispatch/payable_ctor.solc b/test/examples/dispatch/payable_ctor.solc index ce2d3ce17..6a99b2fe0 100644 --- a/test/examples/dispatch/payable_ctor.solc +++ b/test/examples/dispatch/payable_ctor.solc @@ -1,13 +1,13 @@ -import std.{*}; -import std.dispatch.{*}; +import {*} from std; +import {*} from std.dispatch; // A contract whose constructor is explicitly marked `payable`. // Deploying it with an incoming value transfer must succeed and the // transferred value is retained by the newly created contract. contract PayableCtor { - payable constructor() {} + constructor() payable {} - public function balance() -> uint256 { + function balance() public returns (uint256) { let value; assembly { value := selfbalance() diff --git a/test/examples/dispatch/slices.solc b/test/examples/dispatch/slices.solc index a44d6b386..75a5974e9 100644 --- a/test/examples/dispatch/slices.solc +++ b/test/examples/dispatch/slices.solc @@ -1,5 +1,5 @@ -import std.{*}; -import std.dispatch.{*}; +import {*} from std; +import {*} from std.dispatch; // Exercises slice_/truncate (memory_slice) composed with concat, to_bytes, // and the hashing precompiles (keccak256_, sha256). memory_slice implements @@ -8,58 +8,58 @@ import std.dispatch.{*}; contract C { // --- slice_/truncate on a memory(bytes), materialized with to_bytes --- - public function slice_bytes(a: memory(bytes), start: uint256) -> memory(bytes) { + function slice_bytes(a: bytes memory, start: uint256) public returns (bytes memory) { return to_bytes(slice_(a, Typedef.rep(start))); } - public function truncate_bytes(a: memory(bytes), end: uint256) -> memory(bytes) { + function truncate_bytes(a: bytes memory, end: uint256) public returns (bytes memory) { return to_bytes(truncate(a, Typedef.rep(end))); } // --- slice_/truncate over the result of a concat --- - public function slice_of_concat(a: bytes32, b: bytes32, start: uint256) -> memory(bytes) { + function slice_of_concat(a: bytes32, b: bytes32, start: uint256) public returns (bytes memory) { return to_bytes(slice_(concat(a, b), Typedef.rep(start))); } - public function truncate_of_concat(a: bytes32, b: bytes32, end: uint256) -> memory(bytes) { + function truncate_of_concat(a: bytes32, b: bytes32, end: uint256) public returns (bytes memory) { return to_bytes(truncate(concat(a, b), Typedef.rep(end))); } // to_bytes(truncate(slice_(concat(a, b), start), end)) -- the headline nesting: // drop `start` bytes, then keep `end` of what remains (re-slicing a memory_slice). - public function window_of_concat(a: bytes32, b: bytes32, start: uint256, end: uint256) -> memory(bytes) { + function window_of_concat(a: bytes32, b: bytes32, start: uint256, end: uint256) public returns (bytes memory) { return to_bytes(truncate(slice_(concat(a, b), Typedef.rep(start)), Typedef.rep(end))); } // --- a slice used as a concat operand --- - public function concat_slice_b32(a: memory(bytes), start: uint256, c: bytes32) -> memory(bytes) { + function concat_slice_b32(a: bytes memory, start: uint256, c: bytes32) public returns (bytes memory) { return concat(slice_(a, Typedef.rep(start)), c); } - public function concat_two_slices(a: memory(bytes), sa: uint256, b: memory(bytes), eb: uint256) -> memory(bytes) { + function concat_two_slices(a: bytes memory, sa: uint256, b: bytes memory, eb: uint256) public returns (bytes memory) { return concat(slice_(a, Typedef.rep(sa)), truncate(b, Typedef.rep(eb))); } // --- re-slicing a memory_slice --- - public function slice_of_slice(a: memory(bytes), s1: uint256, s2: uint256) -> memory(bytes) { + function slice_of_slice(a: bytes memory, s1: uint256, s2: uint256) public returns (bytes memory) { return to_bytes(slice_(slice_(a, Typedef.rep(s1)), Typedef.rep(s2))); } // --- hashing a slice directly (no intermediate copy) --- - public function keccak_slice(a: memory(bytes), start: uint256) -> bytes32 { + function keccak_slice(a: bytes memory, start: uint256) public returns (bytes32) { return keccak256_(slice_(a, Typedef.rep(start))); } - public function sha_truncate(a: memory(bytes), end: uint256) -> bytes32 { + function sha_truncate(a: bytes memory, end: uint256) public returns (bytes32) { return sha256(truncate(a, Typedef.rep(end))); } // keccak256_(truncate(slice_(concat(a, b), start), end)) -- nested chain, hash endpoint. - public function keccak_window_concat(a: bytes32, b: bytes32, start: uint256, end: uint256) -> bytes32 { + function keccak_window_concat(a: bytes32, b: bytes32, start: uint256, end: uint256) public returns (bytes32) { return keccak256_(truncate(slice_(concat(a, b), Typedef.rep(start)), Typedef.rep(end))); } } diff --git a/test/examples/dispatch/specialise_sum_of_product.solc b/test/examples/dispatch/specialise_sum_of_product.solc index 22d600642..7e25070fe 100644 --- a/test/examples/dispatch/specialise_sum_of_product.solc +++ b/test/examples/dispatch/specialise_sum_of_product.solc @@ -20,50 +20,49 @@ // class and its instances are defined locally and exercised directly, so the // program must now lower end-to-end and return the expected value. -import std.{*}; -import std.dispatch.{*}; +import {*} from std; +import {*} from std.dispatch; -pragma no-patterson-condition; -pragma no-bounded-variable-condition; +pragma solcore noPattersonCondition; +pragma solcore noBoundVariableCondition; // total(x, y) sums every leaf word of both arguments. -forall a. -class a : Total { - function total(x : a, y : a) -> word; +trait Total { + function total(x : a, y : a) returns (word); } -instance word : Total { - function total(x : word, y : word) -> word { +impl Total { + function total(x : word, y : word) returns (word) { return x + y; } } // product: recurse into both components (this is the shape inl carries). -forall f g . f : Total, g : Total => instance (f, g) : Total { - function total(x : (f, g), y : (f, g)) -> word { - match x { - | (xa, xb) => match y { - | (ya, yb) => return Total.total(xa, ya) + Total.total(xb, yb); - } - } +impl Total<(f, g)> where f: Total, g: Total { + function total(x : (f, g), y : (f, g)) returns (word) { + match (x ) { + case (xa, xb) { match (y ) { + case (ya, yb) { return Total.total(xa, ya) + Total.total(xb, yb); + } } + } } } } // sum: the buggy shape. The inl branch recurses at f (a product here), the inr // branch recurses at g (a word here); specializing one must not pollute the // other's nested `match y`. -forall f g . f : Total, g : Total => instance sum(f, g) : Total { - function total(x : sum(f, g), y : sum(f, g)) -> word { - match x { - | inl(xa) => match y { - | inl(ya) => return Total.total(xa, ya); - | inr(yb) => return 0; - } - | inr(xb) => match y { - | inl(ya) => return 0; - | inr(yb) => return Total.total(xb, yb); - } - } +impl Total> where f: Total, g: Total { + function total(x : sum, y : sum) returns (word) { + match (x ) { + case inl(xa) { match (y ) { + case inl(ya) { return Total.total(xa, ya); + } case inr(yb) { return 0; + } } + } case inr(xb) { match (y ) { + case inl(ya) { return 0; + } case inr(yb) { return Total.total(xb, yb); + } } + } } } } @@ -73,9 +72,9 @@ contract SpecialiseSumOfProduct { // inl carries a product (word, word); the two sum sides differ in shape // (pair vs word), which is what the specializer mishandled. // total(inl((1,2)), inl((1,2))) = total((1,2),(1,2)) = (1+1)+(2+2) = 6. - public function probe() -> uint256 { - let x : sum((word, word), word) = inl((1, 2)); - let y : sum((word, word), word) = inl((1, 2)); + function probe() public returns (uint256) { + let x : sum<(word, word), word> = inl((1, 2)); + let y : sum<(word, word), word> = inl((1, 2)); return uint256(Total.total(x, y)); } } diff --git a/test/examples/dispatch/storage.solc b/test/examples/dispatch/storage.solc index a1a537814..9f8261c00 100644 --- a/test/examples/dispatch/storage.solc +++ b/test/examples/dispatch/storage.solc @@ -1,5 +1,5 @@ -import std.{*}; -import std.dispatch.{*}; +import {*} from std; +import {*} from std.dispatch; // Storage support for a `memory(bytes)` contract field: assigning to the // field copies the byte array into storage, reading it back loads it into @@ -7,11 +7,11 @@ import std.dispatch.{*}; contract C { content: bytes; - public function set(value: memory(bytes)) -> () { + function set(value: bytes memory) public returns (()) { content = value; } - public function get() -> memory(bytes) { + function get() public returns (bytes memory) { return content; } } diff --git a/test/examples/dispatch/storage_adt_abi.solc b/test/examples/dispatch/storage_adt_abi.solc index 2dcdc0ba8..e163398b4 100644 --- a/test/examples/dispatch/storage_adt_abi.solc +++ b/test/examples/dispatch/storage_adt_abi.solc @@ -1,8 +1,8 @@ -import std.{*}; -import std.dispatch.{*}; -import std.Generic.{*}; -import std.ABIGeneric.{*}; -import std.StorageGeneric.{*}; +import {*} from std; +import {*} from std.dispatch; +import {*} from std.Generic; +import {*} from std.ABIGeneric; +import {*} from std.StorageGeneric; // An ADT crossing the ABI boundary AND living in storage at the same time. // @@ -18,42 +18,42 @@ import std.StorageGeneric.{*}; // at +32. So `Some(42)` is 0x...01 followed by 0x...2a, and `None` is 0x...00 // followed by a don't-care word. -data Option(a) = None | Some(a); +enum Option { None, Some(a) } contract C { - stored : Option(uint256); + stored : Option; constructor() { stored = Option.None; - assert(StorageSize.size(Proxy : Proxy(Option(uint256))) == 2); + assert(StorageSize.size(Proxy as Proxy>) == 2); } // ADT as a parameter: decoded from calldata, then written to storage. - public function setOpt(o : Option(uint256)) -> () { + function setOpt(o : Option) public returns (()) { stored = o; } // ADT as a return value: loaded from storage, then encoded into returndata. - public function getOpt() -> Option(uint256) { + function getOpt() public returns (Option) { return stored; } // Round-trip in one call, without touching storage. - public function echo(o : Option(uint256)) -> Option(uint256) { + function echo(o : Option) public returns (Option) { return o; } - public function isSome() -> bool { - match stored { - | Option.None => return false; - | Option.Some(_) => return true; - } + function isSome() public returns (bool) { + match (stored ) { + case Option.None { return false; + } case Option.Some(_) { return true; + } } } - public function unwrapOr(d : uint256) -> uint256 { - match stored { - | Option.None => return d; - | Option.Some(v) => return v; - } + function unwrapOr(d : uint256) public returns (uint256) { + match (stored ) { + case Option.None { return d; + } case Option.Some(v) { return v; + } } } } diff --git a/test/examples/dispatch/storage_adt_bool.solc b/test/examples/dispatch/storage_adt_bool.solc index 5d1a80b6e..804030277 100644 --- a/test/examples/dispatch/storage_adt_bool.solc +++ b/test/examples/dispatch/storage_adt_bool.solc @@ -1,7 +1,7 @@ -import std.{*}; -import std.dispatch.{*}; -import std.Generic.{*}; -import std.StorageGeneric.{*}; +import {*} from std; +import {*} from std.dispatch; +import {*} from std.Generic; +import {*} from std.StorageGeneric; // `bool` in storage, bare and inside an ADT. // @@ -16,10 +16,10 @@ import std.StorageGeneric.{*}; // instance, so it cannot appear in a public parameter position. It can appear // in a return position, which is what the getters below exercise. -data Flags = Flags(bool, bool); -data Toggle = Off | On(bool); +enum Flags { Flags(bool, bool) } +enum Toggle { Off, On(bool) } -function toBool(v : uint256) -> bool { +function toBool(v : uint256) returns (bool) { return v != uint256(0); } @@ -32,58 +32,58 @@ contract C { bare = false; flags = Flags(false, false); toggle = Toggle.Off; - assert(StorageSize.size(Proxy : Proxy(bool)) == 1); + assert(StorageSize.size(Proxy as Proxy) == 1); // product of two bools - assert(StorageSize.size(Proxy : Proxy(Flags)) == 2); + assert(StorageSize.size(Proxy as Proxy) == 2); // 1 tag + max(size (), size bool) - assert(StorageSize.size(Proxy : Proxy(Toggle)) == 2); + assert(StorageSize.size(Proxy as Proxy) == 2); } - public function setBare(v : uint256) -> () { + function setBare(v : uint256) public returns (()) { bare = toBool(v); } - public function getBare() -> bool { + function getBare() public returns (bool) { return bare; } - public function setFlags(a : uint256, b : uint256) -> () { + function setFlags(a : uint256, b : uint256) public returns (()) { flags = Flags(toBool(a), toBool(b)); } - public function firstFlag() -> bool { - match flags { - | Flags(a, _) => return a; - } + function firstFlag() public returns (bool) { + match (flags ) { + case Flags(a, _) { return a; + } } } - public function secondFlag() -> bool { - match flags { - | Flags(_, b) => return b; - } + function secondFlag() public returns (bool) { + match (flags ) { + case Flags(_, b) { return b; + } } } - public function turnOn(v : uint256) -> () { + function turnOn(v : uint256) public returns (()) { toggle = Toggle.On(toBool(v)); } - public function turnOff() -> () { + function turnOff() public returns (()) { toggle = Toggle.Off; } // Distinguishes Off from On(false): both leave a zero payload slot, so only // the tag can tell them apart. - public function isOn() -> bool { - match toggle { - | Toggle.Off => return false; - | Toggle.On(_) => return true; - } + function isOn() public returns (bool) { + match (toggle ) { + case Toggle.Off { return false; + } case Toggle.On(_) { return true; + } } } - public function toggleValue() -> bool { - match toggle { - | Toggle.Off => revertEmpty(); return false; - | Toggle.On(b) => return b; - } + function toggleValue() public returns (bool) { + match (toggle ) { + case Toggle.Off { revertEmpty(); return false; + } case Toggle.On(b) { return b; + } } } } diff --git a/test/examples/dispatch/storage_adt_enum.solc b/test/examples/dispatch/storage_adt_enum.solc index 732d60363..2bfc36f1c 100644 --- a/test/examples/dispatch/storage_adt_enum.solc +++ b/test/examples/dispatch/storage_adt_enum.solc @@ -1,7 +1,7 @@ -import std.{*}; -import std.dispatch.{*}; -import std.Generic.{*}; -import std.StorageGeneric.{*}; +import {*} from std; +import {*} from std.dispatch; +import {*} from std.Generic; +import {*} from std.StorageGeneric; // An enumeration with more than two constructors. // @@ -17,15 +17,12 @@ import std.StorageGeneric.{*}; // // `Green` is the only constructor that exercises the `inr(inl(...))` path, // which is exactly the sum nesting that CanStore.load has to reconstruct. -data Color = Red | Green | Blue; +enum Color { Red, Green, Blue } // A three-constructor sum whose branches carry payloads of different widths. // rep = sum(uint256, sum((uint256, uint256), ())), so // size = 1 + max(1, 1 + max(2, 0)) = 4. -data Shape = - Dot(uint256) - | Seg(uint256, uint256) - | Nothing; +enum Shape { Dot(uint256), Seg(uint256, uint256), Nothing } contract C { color : Color; @@ -35,58 +32,58 @@ contract C { color = Color.Red; shape = Shape.Nothing; // 1 tag + max(size (), 1 tag + max(size (), size ())) = 1 + 1 + 0 = 2 - assert(StorageSize.size(Proxy : Proxy(Color)) == 2); + assert(StorageSize.size(Proxy as Proxy) == 2); // 1 tag + max(size uint256, 1 tag + max(size (uint256,uint256), size ())) = 1 + 1 + 2 = 4 - assert(StorageSize.size(Proxy : Proxy(Shape)) == 4); + assert(StorageSize.size(Proxy as Proxy) == 4); } - public function setRed() -> () { + function setRed() public returns (()) { color = Color.Red; } // inr(inl(())) — the nested-tag branch. - public function setGreen() -> () { + function setGreen() public returns (()) { color = Color.Green; } - public function setBlue() -> () { + function setBlue() public returns (()) { color = Color.Blue; } - public function tag() -> uint256 { - match color { - | Color.Red => return uint256(0); - | Color.Green => return uint256(1); - | Color.Blue => return uint256(2); - } + function tag() public returns (uint256) { + match (color ) { + case Color.Red { return uint256(0); + } case Color.Green { return uint256(1); + } case Color.Blue { return uint256(2); + } } } - public function setDot(a : uint256) -> () { + function setDot(a : uint256) public returns (()) { shape = Shape.Dot(a); } // inr(inl(...)) again, this time with a product payload. - public function setSeg(a : uint256, b : uint256) -> () { + function setSeg(a : uint256, b : uint256) public returns (()) { shape = Shape.Seg(a, b); } - public function setNothing() -> () { + function setNothing() public returns (()) { shape = Shape.Nothing; } - public function shapeSum() -> uint256 { - match shape { - | Shape.Dot(a) => return a; - | Shape.Seg(a, b) => return a + b; - | Shape.Nothing => return uint256(0); - } + function shapeSum() public returns (uint256) { + match (shape ) { + case Shape.Dot(a) { return a; + } case Shape.Seg(a, b) { return a + b; + } case Shape.Nothing { return uint256(0); + } } } - public function shapeTag() -> uint256 { - match shape { - | Shape.Dot(_) => return uint256(0); - | Shape.Seg(_, _) => return uint256(1); - | Shape.Nothing => return uint256(2); - } + function shapeTag() public returns (uint256) { + match (shape ) { + case Shape.Dot(_) { return uint256(0); + } case Shape.Seg(_, _) { return uint256(1); + } case Shape.Nothing { return uint256(2); + } } } } diff --git a/test/examples/dispatch/storage_adt_field.solc b/test/examples/dispatch/storage_adt_field.solc index d1b68cb29..603b671c8 100644 --- a/test/examples/dispatch/storage_adt_field.solc +++ b/test/examples/dispatch/storage_adt_field.solc @@ -1,7 +1,7 @@ -import std.{*}; -import std.dispatch.{*}; -import std.Generic.{*}; -import std.StorageGeneric.{*}; +import {*} from std; +import {*} from std.dispatch; +import {*} from std.Generic; +import {*} from std.StorageGeneric; // Algebraic data types used directly as contract storage fields, including a // nested ADT (Option(Triple)). @@ -10,78 +10,78 @@ import std.StorageGeneric.{*}; // - triple : Triple (product, rep (uint256,(uint256,uint256)) -> 3 slots) // - someTriple : Option(Triple) (sum of product, rep sum((), Triple) -> 4 slots) -data Option(a) = None | Some(a); -data Triple = Triple(uint256, uint256, uint256); +enum Option { None, Some(a) } +enum Triple { Triple(uint256, uint256, uint256) } contract C { - someValue : Option(uint256); + someValue : Option; triple : Triple; - someTriple : Option(Triple); + someTriple : Option; constructor() { // sum: 1 tag + max(size (), size uint256) = 1 + 1 = 2 - assert(StorageSize.size(Proxy : Proxy(Option(uint256))) == 2); + assert(StorageSize.size(Proxy as Proxy>) == 2); // product: size uint256 * 3 = 3 - assert(StorageSize.size(Proxy : Proxy(Triple)) == 3); + assert(StorageSize.size(Proxy as Proxy) == 3); // sum of product: 1 tag + max(size (), size Triple) = 1 + 3 = 4 - assert(StorageSize.size(Proxy : Proxy(Option(Triple))) == 4); + assert(StorageSize.size(Proxy as Proxy>) == 4); } - public function setValue(v : uint256) -> () { + function setValue(v : uint256) public returns (()) { someValue = Option.Some(v); } - public function clearValue() -> () { + function clearValue() public returns (()) { someValue = Option.None; } - public function getValue() -> uint256 { - match someValue { - | Option.None => revertEmpty(); return uint256(0); - | Option.Some(v) => return v; - } + function getValue() public returns (uint256) { + match (someValue ) { + case Option.None { revertEmpty(); return uint256(0); + } case Option.Some(v) { return v; + } } } - public function isSome() -> bool { - match someValue { - | Option.None => return false; - | Option.Some(_) => return true; - } + function isSome() public returns (bool) { + match (someValue ) { + case Option.None { return false; + } case Option.Some(_) { return true; + } } } - public function setTriple(a : uint256, b : uint256, c : uint256) -> () { + function setTriple(a : uint256, b : uint256, c : uint256) public returns (()) { triple = Triple(a, b, c); } - public function tripleSum() -> uint256 { - match triple { - | Triple(a, b, c) => return a + b + c; - } + function tripleSum() public returns (uint256) { + match (triple ) { + case Triple(a, b, c) { return a + b + c; + } } } // Nested ADT: Option(Triple). - public function setSomeTriple(a : uint256, b : uint256, c : uint256) -> () { + function setSomeTriple(a : uint256, b : uint256, c : uint256) public returns (()) { someTriple = Option.Some(Triple(a, b, c)); } - public function clearSomeTriple() -> () { + function clearSomeTriple() public returns (()) { someTriple = Option.None; } - public function someTripleSum() -> uint256 { - match someTriple { - | Option.None => revertEmpty(); return uint256(0); - | Option.Some(t) => - match t { - | Triple(a, b, c) => return a + b + c; - } - } + function someTripleSum() public returns (uint256) { + match (someTriple ) { + case Option.None { revertEmpty(); return uint256(0); + } case Option.Some(t) { + match (t ) { + case Triple(a, b, c) { return a + b + c; + } } + } } } - public function hasSomeTriple() -> bool { - match someTriple { - | Option.None => return false; - | Option.Some(_) => return true; - } + function hasSomeTriple() public returns (bool) { + match (someTriple ) { + case Option.None { return false; + } case Option.Some(_) { return true; + } } } } diff --git a/test/examples/dispatch/storage_adt_mapping.solc b/test/examples/dispatch/storage_adt_mapping.solc index ad2f4df8c..221f0a72b 100644 --- a/test/examples/dispatch/storage_adt_mapping.solc +++ b/test/examples/dispatch/storage_adt_mapping.solc @@ -1,7 +1,7 @@ -import std.{*}; -import std.dispatch.{*}; -import std.Generic.{*}; -import std.StorageGeneric.{*}; +import {*} from std; +import {*} from std.dispatch; +import {*} from std.Generic; +import {*} from std.StorageGeneric; // An ADT used as the VALUE of a storage mapping. // @@ -12,71 +12,71 @@ import std.StorageGeneric.{*}; // A multi-slot value in a mapping occupies hash2(slot, key) .. + size(v) - 1, // exactly as Solidity lays out a struct behind a mapping. -data Option(a) = None | Some(a); -data Pair = Pair(uint256, uint256); +enum Option { None, Some(a) } +enum Pair { Pair(uint256, uint256) } contract C { // 2 slots per entry: tag + payload - opts : mapping(uint256, Option(uint256)); + opts : mapping(uint256 => Option); // 2 slots per entry: no tag, two words - pairs : mapping(uint256, Pair); + pairs : mapping(uint256 => Pair); // 3 slots per entry: tag + max(0, 2) - optPairs : mapping(uint256, Option(Pair)); + optPairs : mapping(uint256 => Option); constructor() { - assert(StorageSize.size(Proxy : Proxy(Option(uint256))) == 2); - assert(StorageSize.size(Proxy : Proxy(Pair)) == 2); - assert(StorageSize.size(Proxy : Proxy(Option(Pair))) == 3); + assert(StorageSize.size(Proxy as Proxy>) == 2); + assert(StorageSize.size(Proxy as Proxy) == 2); + assert(StorageSize.size(Proxy as Proxy>) == 3); } - public function putOpt(k : uint256, v : uint256) -> () { + function putOpt(k : uint256, v : uint256) public returns (()) { opts[k] = Option.Some(v); } - public function clearOpt(k : uint256) -> () { + function clearOpt(k : uint256) public returns (()) { opts[k] = Option.None; } // Unset keys read back as the zero slot pattern, i.e. tag 0 = None. - public function hasOpt(k : uint256) -> bool { - match opts[k] { - | Option.None => return false; - | Option.Some(_) => return true; - } + function hasOpt(k : uint256) public returns (bool) { + match (opts[k] ) { + case Option.None { return false; + } case Option.Some(_) { return true; + } } } - public function getOpt(k : uint256) -> uint256 { - match opts[k] { - | Option.None => revertEmpty(); return uint256(0); - | Option.Some(v) => return v; - } + function getOpt(k : uint256) public returns (uint256) { + match (opts[k] ) { + case Option.None { revertEmpty(); return uint256(0); + } case Option.Some(v) { return v; + } } } - public function putPair(k : uint256, a : uint256, b : uint256) -> () { + function putPair(k : uint256, a : uint256, b : uint256) public returns (()) { pairs[k] = Pair(a, b); } - public function pairSum(k : uint256) -> uint256 { - match pairs[k] { - | Pair(a, b) => return a + b; - } + function pairSum(k : uint256) public returns (uint256) { + match (pairs[k] ) { + case Pair(a, b) { return a + b; + } } } - public function putOptPair(k : uint256, a : uint256, b : uint256) -> () { + function putOptPair(k : uint256, a : uint256, b : uint256) public returns (()) { optPairs[k] = Option.Some(Pair(a, b)); } - public function clearOptPair(k : uint256) -> () { + function clearOptPair(k : uint256) public returns (()) { optPairs[k] = Option.None; } - public function optPairSum(k : uint256) -> uint256 { - match optPairs[k] { - | Option.None => revertEmpty(); return uint256(0); - | Option.Some(p) => - match p { - | Pair(a, b) => return a + b; - } - } + function optPairSum(k : uint256) public returns (uint256) { + match (optPairs[k] ) { + case Option.None { revertEmpty(); return uint256(0); + } case Option.Some(p) { + match (p ) { + case Pair(a, b) { return a + b; + } } + } } } } diff --git a/test/examples/dispatch/storage_array.solc b/test/examples/dispatch/storage_array.solc index d97e2be37..45329e3eb 100644 --- a/test/examples/dispatch/storage_array.solc +++ b/test/examples/dispatch/storage_array.solc @@ -1,18 +1,18 @@ -import std.{*}; -import std.dispatch.{*}; -import std.opcodes.{mload, mstore}; +import {*} from std; +import {*} from std.dispatch; +import {mload, mstore} from std.opcodes; contract MemberRegistry { - members : array(address); + members : address[]; constructor() {} - public function addMember(addr : address) -> () { + function addMember(addr : address) public returns (()) { ArrayPush.push(members, addr); } // MemberNotFound() selector - public function removeMember(addr : address) -> () { + function removeMember(addr : address) public returns (()) { // foundIdx == length() acts as the "not found" sentinel. let foundIdx : uint256 = Array.length(members); let i : uint256; @@ -32,11 +32,11 @@ contract MemberRegistry { Array.pop(members); } - public function numberOfMembers() -> uint256 { + function numberOfMembers() public returns (uint256) { return Array.length(members); } - public function getMembers() -> memory(DynArray(address)) { + function getMembers() public returns (DynArray
memory) { let count : word = Typedef.rep(Array.length(members)); let totalBytes : word = (count + 1) * 32; let ptr : word = allocate_memory(totalBytes); @@ -47,6 +47,6 @@ contract MemberRegistry { let addr : address = members[uint256(i)]; mstore(ptr + 32 + i * 32, Typedef.rep(addr)); } - return Typedef.abs(ptr) : memory(DynArray(address)); + return Typedef.abs(ptr) as DynArray
memory; } } diff --git a/test/examples/dispatch/storage_dynamic_field.solc b/test/examples/dispatch/storage_dynamic_field.solc index fa8634521..735a04688 100644 --- a/test/examples/dispatch/storage_dynamic_field.solc +++ b/test/examples/dispatch/storage_dynamic_field.solc @@ -1,11 +1,9 @@ -import std.{*}; -import std.dispatch.{*}; -import std.Generic.{*}; -import std.StorageGeneric.{*}; +import {*} from std; +import {*} from std.dispatch; +import {*} from std.Generic; +import {*} from std.StorageGeneric; -data Blob = - NoBlob - | SomeBytes(memory(bytes)); +enum Blob { NoBlob, SomeBytes(bytes memory) } contract C { blob : Blob; @@ -13,31 +11,31 @@ contract C { constructor() { blob = Blob.NoBlob; // A dynamic field occupies one slot, so the sum is 1 (tag) + max(0, 1). - assert(StorageSize.size(Proxy : Proxy(Blob)) == 2); + assert(StorageSize.size(Proxy as Proxy) == 2); } - public function clear() -> () { + function clear() public returns (()) { blob = Blob.NoBlob; } // Stores the memory(bytes) payload into the ADT field (round-trips the // dynamic leaf through storage(bytes)). - public function setBytes(b: memory(bytes)) -> () { + function setBytes(b: bytes memory) public returns (()) { blob = Blob.SomeBytes(b); } - public function getBytes() -> memory(bytes) { - match blob { - | Blob.NoBlob => revertEmpty(); return memory(0); - | Blob.SomeBytes(b) => return b; - } + function getBytes() public returns (bytes memory) { + match (blob ) { + case Blob.NoBlob { revertEmpty(); return memory(0); + } case Blob.SomeBytes(b) { return b; + } } } // Loads the whole ADT back from storage and inspects its tag. - public function isEmpty() -> bool { - match blob { - | Blob.NoBlob => return true; - | Blob.SomeBytes(_) => return false; - } + function isEmpty() public returns (bool) { + match (blob ) { + case Blob.NoBlob { return true; + } case Blob.SomeBytes(_) { return false; + } } } } diff --git a/test/examples/dispatch/stringid.solc b/test/examples/dispatch/stringid.solc index 6e5c1d8ae..5c0e15834 100644 --- a/test/examples/dispatch/stringid.solc +++ b/test/examples/dispatch/stringid.solc @@ -1,10 +1,10 @@ -import std.{*}; -import std.dispatch.{*}; -import std.opcodes.{mstore, mload}; +import {*} from std; +import {*} from std.dispatch; +import {mstore, mload} from std.opcodes; contract C { constructor() {} - public function id(x:memory(string)) -> (memory(string)) { + function id(x:string memory) public returns (string memory) { let ptr : word = Typedef.rep(x); let len : word; let n1 : word; @@ -18,14 +18,14 @@ contract C { return x; } - public function const_a() -> (memory(string)) { + function const_a() public returns (string memory) { let resPtr = allocate_memory(64); let payload : word = 0x7777777777777777777777777777777777777777777777777777777777777777; mstore(resPtr, 3); mstore(resPtr+32, payload); return memory(resPtr); } - public function mylen(x:memory(string)) -> uint256 { + function mylen(x:string memory) public returns (uint256) { let ptr : word = Typedef.rep(x); let l : word; let n1 : word; diff --git a/test/examples/dispatch/sum_wide_product.solc b/test/examples/dispatch/sum_wide_product.solc index 259047a9a..5500d691a 100644 --- a/test/examples/dispatch/sum_wide_product.solc +++ b/test/examples/dispatch/sum_wide_product.solc @@ -1,5 +1,5 @@ -import std.{*}; -import std.dispatch.{*}; +import {*} from std; +import {*} from std.dispatch; // Regression test for a yule backend bug, independent of the storage/Generic // work: matching a sum constructor whose payload is a product of arity >= 3. @@ -12,18 +12,18 @@ import std.dispatch.{*}; // No storage and no Generic derivation involved — just constructing and matching // an ordinary algebraic data type. -data Shape = Dot | Tri(uint256, uint256, uint256); +enum Shape { Dot, Tri(uint256, uint256, uint256) } contract C { constructor() {} // Build Tri(a,b,c) then match it back out: exercises a sum whose payload is // a 3-field product. - public function triSum(a : uint256, b : uint256, c : uint256) -> uint256 { + function triSum(a : uint256, b : uint256, c : uint256) public returns (uint256) { let s : Shape = Shape.Tri(a, b, c); - match s { - | Shape.Dot => return uint256(0); - | Shape.Tri(x, y, z) => return x + y + z; - } + match (s ) { + case Shape.Dot { return uint256(0); + } case Shape.Tri(x, y, z) { return x + y + z; + } } } } diff --git a/test/examples/dispatch/ufcs_array.solc b/test/examples/dispatch/ufcs_array.solc index 17b3112ed..33d58cfce 100644 --- a/test/examples/dispatch/ufcs_array.solc +++ b/test/examples/dispatch/ufcs_array.solc @@ -1,6 +1,6 @@ -import std.{*}; -import std.dispatch.{*}; -import std.opcodes.{mload, mstore}; +import {*} from std; +import {*} from std.dispatch; +import {mload, mstore} from std.opcodes; // UFCS counterpart of storage_array.solc. // @@ -19,16 +19,16 @@ import std.opcodes.{mload, mstore}; // the same runtime behaviour. Indexed access members[i] is unaffecte: it // is handled by field-access desugaring, not UFCS. contract MemberRegistry { - members : array(address); + members : address[]; constructor() {} - public function addMember(addr : address) -> () { + function addMember(addr : address) public returns (()) { members.push(addr); } // MemberNotFound() selector - public function removeMember(addr : address) -> () { + function removeMember(addr : address) public returns (()) { // foundIdx == length() acts as the "not found" sentinel. let foundIdx : uint256 = members.length(); let i : uint256; @@ -48,11 +48,11 @@ contract MemberRegistry { members.pop(); } - public function numberOfMembers() -> uint256 { + function numberOfMembers() public returns (uint256) { return members.length(); } - public function getMembers() -> memory(DynArray(address)) { + function getMembers() public returns (DynArray
memory) { let count : word = Typedef.rep(members.length()); let totalBytes : word = (count + 1) * 32; let ptr : word = allocate_memory(totalBytes); @@ -63,6 +63,6 @@ contract MemberRegistry { let addr : address = members[uint256(i)]; mstore(ptr + 32 + i * 32, Typedef.rep(addr)); } - return Typedef.abs(ptr) : memory(DynArray(address)); + return Typedef.abs(ptr) as DynArray
memory; } } diff --git a/test/examples/dispatch/weth9.solc b/test/examples/dispatch/weth9.solc index bd3125ea2..12dffdbbd 100644 --- a/test/examples/dispatch/weth9.solc +++ b/test/examples/dispatch/weth9.solc @@ -1,37 +1,37 @@ -import std.{*}; -import std.opcodes.{caller as caller_, callvalue as callvalue_, selfbalance, gas, call}; -import std.dispatch.{*}; +import {*} from std; +import {caller as caller_, callvalue as callvalue_, selfbalance, gas, call} from std.opcodes; +import {*} from std.dispatch; // Forward `wad` wei to `dst` via a zero-data CALL and revert on failure. -function sendValue(dst: address, wad: uint256) -> () { +function sendValue(dst: address, wad: uint256) returns (()) { let ret = call(gas(), Typedef.rep(dst), Typedef.rep(wad), 0, 0, 0, 0); require(ret != 0, Error(0x90b8ec18)); // TransferFailed() } -function caller() -> address { +function caller() returns (address) { return address(caller_()); } -function callvalue() -> uint256 { +function callvalue() returns (uint256) { return uint256(callvalue_()); } // Based on https://github.com/gnosis/canonical-weth/blob/master/contracts/WETH9.sol // That code is written WITHOUT checked arithmetic. contract WETH9 { - balances : mapping(address, uint256); - allowance : mapping(address, mapping(address, uint256)); + balances : mapping(address => uint256); + allowance : mapping(address => mapping(address => uint256)); constructor() {} // --- ETH <-> WETH --- - public payable function deposit() -> () { + function deposit() public payable returns (()) { let sender = caller(); balances[sender] = balances[sender] + callvalue(); } - public function withdraw(wad: uint256) -> () { + function withdraw(wad: uint256) public returns (()) { let sender = caller(); require(balances[sender] >= wad, Error(0xf4d678b8)); // InsufficientBalance() balances[sender] = balances[sender] - wad; @@ -39,35 +39,35 @@ contract WETH9 { } // totalSupply == ETH held by this contract (matches canonical WETH9). - public function totalSupply() -> uint256 { + function totalSupply() public returns (uint256) { return uint256(selfbalance()); } // --- ERC20 surface --- - public function balanceOf(account: address) -> uint256 { + function balanceOf(account: address) public returns (uint256) { return balances[account]; } - public function allowance(owner_: address, spender: address) -> uint256 { + function allowance(owner_: address, spender: address) public returns (uint256) { return allowance[owner_][spender]; } - public function approve(usr: address, wad: uint256) -> bool { + function approve(usr: address, wad: uint256) public returns (bool) { let sender = caller(); allowance[sender][usr] = wad; return true; } - public function transfer(dst: address, wad: uint256) -> bool { + function transfer(dst: address, wad: uint256) public returns (bool) { return transferFrom(caller(), dst, wad); } - public function transferFrom(src: address, dst: address, wad: uint256) -> bool { + function transferFrom(src: address, dst: address, wad: uint256) public returns (bool) { let sender = caller(); require(balances[src] >= wad, Error(0xf4d678b8)); // InsufficientBalance() - if (src != sender && allowance[src][sender] != (maxVal():uint256)) { + if (src != sender && allowance[src][sender] != (maxVal() as uint256)) { require(allowance[src][sender] >= wad, Error(0x13be252b)); // InsufficientAllowance() allowance[src][sender] -= wad; } @@ -77,7 +77,7 @@ contract WETH9 { } // Plain ETH transfers (no calldata, just value) auto-wrap into WETH. - payable fallback() -> () { + fallback() external payable { let sender = caller(); balances[sender] = balances[sender] + callvalue(); } diff --git a/test/examples/invokable/021nid.solc b/test/examples/invokable/021nid.solc index a8deffa32..d5da89d8a 100644 --- a/test/examples/invokable/021nid.solc +++ b/test/examples/invokable/021nid.solc @@ -1,15 +1,15 @@ contract Id1 { - public function id(x) { + function id(x) public { return x ; } - public function nid() { + function nid() public { return id; } - public function const(x, y) { return x; } + function const(x, y) public { return x; } - public function main() { + function main() public { return nid(42); } } diff --git a/test/examples/invokable/022nid-invoke.solc b/test/examples/invokable/022nid-invoke.solc index 81346bfef..97c64795e 100644 --- a/test/examples/invokable/022nid-invoke.solc +++ b/test/examples/invokable/022nid-invoke.solc @@ -1,22 +1,22 @@ -class self : Invokable(args, ret) { - function invoke (s:self, a:args) -> ret; +trait Invokable { + function invoke (s:self, a:args) returns (ret); } function id(x) { return x ; } - data IdToken(a) = IdToken + enum IdToken { IdToken } -instance IdToken(a) : Invokable(a,a) { - function invoke(token: IdToken(a), arg:a) -> a { +impl Invokable, a, a> { + function invoke(token: IdToken, arg:a) returns (a) { return id(arg); } } contract InvokeId { - public function id(x) { + function id(x) public { return x ; } @@ -26,11 +26,11 @@ contract InvokeId { } */ - public function nidimpl() { + function nidimpl() public { return IdToken; } - public function main() { + function main() public { // Instead of: `return nid(42)` return invoke(nidimpl(), 42); } diff --git a/test/examples/invokable/024lamid.solc b/test/examples/invokable/024lamid.solc index f4e794d5c..78dfeec4f 100644 --- a/test/examples/invokable/024lamid.solc +++ b/test/examples/invokable/024lamid.solc @@ -1,10 +1,10 @@ contract Id1 { - public function id(x) { + function id(x) public { return x ; } - public function main() { + function main() public { let nid = lam(x) {return x;}; return nid(42); } diff --git a/test/examples/invokable/025lamid-invoke.solc b/test/examples/invokable/025lamid-invoke.solc index 0697ad108..ed8d0ea5d 100644 --- a/test/examples/invokable/025lamid-invoke.solc +++ b/test/examples/invokable/025lamid-invoke.solc @@ -7,23 +7,23 @@ contract Id1 { } */ -class self : Invokable(args, ret) { - function invoke (s:self, a:args) -> ret; +trait Invokable { + function invoke (s:self, a:args) returns (ret); } -function lam0impl(x: c) -> c { return x; } +function lam0impl(x: c) returns (c) { return x; } -data Lam0Token(a) = Lam0Token +enum Lam0Token { Lam0Token } -instance Lam0Token(a) : Invokable(a,a) { - function invoke(token: Lam0Token(a), arg:a) -> a { +impl Invokable, a, a> { + function invoke(token: Lam0Token, arg:a) returns (a) { return lam0impl(arg); } } contract InvokeLam { -public function main() { +function main() public { let nid = Lam0Token; return invoke(nid, 42); } diff --git a/test/examples/invokable/026capture.solc b/test/examples/invokable/026capture.solc index 4da248157..4cf84554c 100644 --- a/test/examples/invokable/026capture.solc +++ b/test/examples/invokable/026capture.solc @@ -8,7 +8,7 @@ contract Id1 { } */ -function addW(x: Word, y:Word) -> Word { +function addW(x: Word, y:Word) returns (Word) { let res : Word; assembly { res := add(x, y) @@ -16,29 +16,29 @@ function addW(x: Word, y:Word) -> Word { return res; } -class self : Invokable(args, ret) { - function invoke (s:self, a:args) -> ret; +trait Invokable { + function invoke (s:self, a:args) returns (ret); } // env might be a tuple, here it is a single Word -function lam1impl(env: Word, x: c) -> c { +function lam1impl(env: Word, x: c) returns (c) { let y = env; return addW(x,y); } -data Lam1Closure(a) = Lam1Closure(Word) +enum Lam1Closure { Lam1Closure(Word) } -instance Lam1Closure(a) : Invokable(a,Word) { - function invoke(clos: Lam1Closure(a), arg:a) -> Word { - match clos { - | Lam1Closure(env) => return lam1impl(env, arg); - }; +impl Invokable, a, Word> { + function invoke(clos: Lam1Closure, arg:a) returns (Word) { + match (clos ) { + case Lam1Closure(env) { return lam1impl(env, arg); + } } } } contract InvokeCapLam { -public function main() { +function main() public { let y = 42; let clos = Lam1Closure(y); diff --git a/test/examples/invokable/027retfun.solc b/test/examples/invokable/027retfun.solc index 7bdefb80b..5001a76e4 100644 --- a/test/examples/invokable/027retfun.solc +++ b/test/examples/invokable/027retfun.solc @@ -12,32 +12,32 @@ contract Id1 { } */ -class self : Invokable(args, ret) { - function invoke (s:self, a:args) -> ret; +trait Invokable { + function invoke (s:self, a:args) returns (ret); } // env might be a tuple, here it is a single Word -function lam1impl(env: Word, x: c) -> c { return env; } +function lam1impl(env: Word, x: c) returns (c) { return env; } -data Lam1Closure(a) = Lam1Closure(Word) +enum Lam1Closure { Lam1Closure(Word) } -instance Lam1Closure(a) : Invokable(a,Word) { - function invoke(clos: Lam1Closure(a), arg:a) -> Word { - match clos { - | Lam1Closure(env) => return lam1impl(env, arg); - }; +impl Invokable, a, Word> { + function invoke(clos: Lam1Closure, arg:a) returns (Word) { + match (clos ) { + case Lam1Closure(env) { return lam1impl(env, arg); + } } } } contract InvokeCapLam { -public function foo() { +function foo() public { let y = 42; let clos = Lam1Closure(y); return clos; } -public function main() { +function main() public { return invoke(foo(), 17); } diff --git a/test/examples/invokable/028modifier.solc b/test/examples/invokable/028modifier.solc index 264f49dd0..897aa3a32 100644 --- a/test/examples/invokable/028modifier.solc +++ b/test/examples/invokable/028modifier.solc @@ -2,7 +2,7 @@ function add1(x) { return addW(x,1); } -function addW(x: Word, y:Word) -> Word { +function addW(x: Word, y:Word) returns (Word) { let res : Word; assembly { res := add(x, y) @@ -10,8 +10,8 @@ function addW(x: Word, y:Word) -> Word { return res; } -class self : Invokable(args, ret) { - function invoke (s:self, a:args) -> ret; +trait Invokable { + function invoke (s:self, a:args) returns (ret); } @@ -33,14 +33,14 @@ contract Id1 { } */ -function foo(x:Word) -> Word { +function foo(x:Word) returns (Word) { return addW(x, 2); } -data FooToken = FooToken +enum FooToken { FooToken } -instance FooToken:Invokable(Word, Word) { - function invoke(self:FooToken, arg: Word) -> Word { +impl Invokable { + function invoke(self:FooToken, arg: Word) returns (Word) { return foo(arg); } } @@ -48,7 +48,7 @@ instance FooToken:Invokable(Word, Word) { // lambda in add1mod captures a function // so env contains the closure -forall f.(f: Invokable(Word,Word)) => function lam1impl (env : f, a:Word) { +function lam1impl (env : f, a:Word) where f: Invokable { let f = env; return add1(invoke(f, a)); } @@ -56,7 +56,7 @@ forall f.(f: Invokable(Word,Word)) => function lam1impl (env : f, a:Word) { // we want: // data Lam1Closure = f:Invokable(Word,Word) => Lam1Closure(f) -data Lam1Closure(f) = Lam1Closure(f) +enum Lam1Closure { Lam1Closure(f) } /* function extractEnv(clos: Lam1Closure(f)) -> f { @@ -65,11 +65,11 @@ function extractEnv(clos: Lam1Closure(f)) -> f { }; } */ -instance (f:Invokable(Word,Word)) => Lam1Closure(f) : Invokable(Word,Word) { - function invoke(clos, arg:Word) -> Word { - match clos { - | Lam1Closure(env) => return lam1impl(env, arg); - }; +impl Invokable, Word, Word> where f: Invokable { + function invoke(clos, arg:Word) returns (Word) { + match (clos ) { + case Lam1Closure(env) { return lam1impl(env, arg); + } } } } @@ -80,7 +80,7 @@ function add1mod(f) { contract Modifier { -public function main() { +function main() public { let barClos = add1mod(FooToken); return invoke(barClos, 39); } diff --git a/test/examples/invokable/031enum.solc b/test/examples/invokable/031enum.solc index b31d30cd9..be6682112 100644 --- a/test/examples/invokable/031enum.solc +++ b/test/examples/invokable/031enum.solc @@ -1,4 +1,4 @@ -function addW(x: Word, y:Word) -> Word { +function addW(x: Word, y:Word) returns (Word) { let res : Word; assembly { res := add(x, y) @@ -6,45 +6,45 @@ function addW(x: Word, y:Word) -> Word { return res; } -class a:Enum { - function fromEnum(x:a) -> Word; +trait Enum { + function fromEnum(x:a) returns (Word); } - data Color = R | G | B + enum Color { R, G, B } -instance Color : Enum { +impl Enum { function fromEnum(c) { - match c { - | R => return 1; - | Color.G => return 2; - | Color.B => return 3; - }; + match (c ) { + case R { return 1; + } case Color.G { return 2; + } case Color.B { return 3; + } } } } -data Bool = False | True +enum Bool { False, True } -instance Bool : Enum { +impl Enum { function fromEnum(b) { - match b { - | False => return 0; - | Bool.True => return 1; - }; + match (b ) { + case False { return 0; + } case Bool.True { return 1; + } } } } -data FromEnumToken(a) = FromEnumToken +enum FromEnumToken { FromEnumToken } -class self : Invokable(args, ret) { - function invoke (s:self, a:args) -> ret; +trait Invokable { + function invoke (s:self, a:args) returns (ret); } -instance (a:Enum) => FromEnumToken(a) : Invokable(a,Word) { - function invoke(fet : FromEnumToken(a), arg) -> Word { +impl Invokable, a, Word> where a: Enum { + function invoke(fet : FromEnumToken, arg) returns (Word) { return fromEnum(arg); } } contract RGB { - public function main() { + function main() public { /* let x = fromEnum(Color.B); let y = fromEnum(Bool.True); diff --git a/test/examples/opcodes/all-shapes.solc b/test/examples/opcodes/all-shapes.solc index c09bb4691..1b3bd6cf0 100644 --- a/test/examples/opcodes/all-shapes.solc +++ b/test/examples/opcodes/all-shapes.solc @@ -1,30 +1,30 @@ -import std.opcodes.{*}; +import {*} from std.opcodes; // Compilation test for the std/opcodes wrappers. // Picks two opcodes from each of the four shape categories so the // pipeline exercises every wrapper signature. // no inputs, no return -function shape_void_void() -> () { +function shape_void_void() returns (()) { stop(); invalid(); } // no inputs, returns a word -function shape_void_word() -> word { +function shape_void_word() returns (word) { let a = address(); let t = timestamp(); return a; } // inputs, no return -function shape_word_void(x: word) -> () { +function shape_word_void(x: word) returns (()) { pop(x); mstore(0, x); } // inputs, returns a word -function shape_word_word(a: word, b: word) -> word { +function shape_word_word(a: word, b: word) returns (word) { let s = add(a, b); let m = mload(0); return s; diff --git a/test/examples/pragmas/bound.solc b/test/examples/pragmas/bound.solc index 546c850dc..1749c1037 100644 --- a/test/examples/pragmas/bound.solc +++ b/test/examples/pragmas/bound.solc @@ -1,15 +1,15 @@ -forall a . class a:D { function f(x:a); } -forall a b . class a:F(b) {} +trait D { function f(x:a); } +trait F {} -data Memory(a) = Memory(word); +enum Memory { Memory(word) } -forall a . instance Memory(a):F(Memory(Memory(Memory(a)))) {} -forall a c . instance (c:D,a:F(c)) => Memory(Memory(Memory(a))):D { - function f(x:Memory(Memory(Memory(a)))) {} +impl F, Memory>>> {} +impl D>>> where c: D, a: F { + function f(x:Memory>>) {} } -forall b . function g(y:b) { - let x : Memory(Memory(Memory(Memory(b)))); +function g(y:b) { + let x : Memory>>>; f(x); } diff --git a/test/examples/pragmas/coverage.solc b/test/examples/pragmas/coverage.solc index c412dc914..d3ee3c1df 100644 --- a/test/examples/pragmas/coverage.solc +++ b/test/examples/pragmas/coverage.solc @@ -1,8 +1,8 @@ -pragma no-coverage-condition ; +pragma solcore noCoverageCondition ; -data List(a) = Nil | Cons(a,List(a)); -data Bool = True | False ; +enum List { Nil, Cons(a, List) } +enum Bool { True, False } -forall a b c . class a : C(b,c) {} +trait C {} -forall a b . instance List(b) : C (a, List(a)) {} +impl C, a, List> {} diff --git a/test/examples/pragmas/patterson.solc b/test/examples/pragmas/patterson.solc index f66a88f56..0d59a7a04 100644 --- a/test/examples/pragmas/patterson.solc +++ b/test/examples/pragmas/patterson.solc @@ -1,16 +1,16 @@ -forall self . class self:A {} -forall self . class self:B {} -forall self . class self:C {} -forall self . class self:D {} +trait A {} +trait B {} +trait C {} +trait D {} -data Uint256 = U; -data T(x) = T; -data S(x) = SCons; +enum Uint256 { U } +enum T { T } +enum S { SCons } // This works. -forall U . U : A => instance T(U):D {} +impl D> where U: A {} // This should also work, but reports a violation of the Paterson condition. -forall U . U : A, U : B, U : C => instance S(U):D {} +impl D> where U: A, U: B, U: C {} diff --git a/test/examples/spec/00answer.solc b/test/examples/spec/00answer.solc index ba55aa252..48c89978f 100644 --- a/test/examples/spec/00answer.solc +++ b/test/examples/spec/00answer.solc @@ -1,5 +1,5 @@ contract Answer { - public function main() -> word { + function main() public returns (word) { return 42; } } \ No newline at end of file diff --git a/test/examples/spec/010answer.solc b/test/examples/spec/010answer.solc index 5699ce863..5681aed3e 100644 --- a/test/examples/spec/010answer.solc +++ b/test/examples/spec/010answer.solc @@ -1,5 +1,5 @@ contract Answer { - public function main() { + function main() public { return 42; } } \ No newline at end of file diff --git a/test/examples/spec/011id.solc b/test/examples/spec/011id.solc index 2e79a47e6..cc2f792ed 100644 --- a/test/examples/spec/011id.solc +++ b/test/examples/spec/011id.solc @@ -1,14 +1,14 @@ contract Id1 { - data Bool = False | True; + enum Bool { False, True } - public function id(x) { + function id(x) public { return x ; } - public function const(x, y) { return x; } + function const(x, y) public { return x; } - public function main() { + function main() public { return const(id(42), Bool.False); } } diff --git a/test/examples/spec/012nid.solc b/test/examples/spec/012nid.solc index a27a65659..e2a17171e 100644 --- a/test/examples/spec/012nid.solc +++ b/test/examples/spec/012nid.solc @@ -1,15 +1,15 @@ contract Id1 { - public function id(x) { + function id(x) public { return x ; } - public function nid() { + function nid() public { return id; } - public function const(x, y) { return x; } + function const(x, y) public { return x; } - public function main() { + function main() public { return const(nid(42), id(1)); } } diff --git a/test/examples/spec/013comp.solc b/test/examples/spec/013comp.solc index a6900271f..c69c4ef31 100644 --- a/test/examples/spec/013comp.solc +++ b/test/examples/spec/013comp.solc @@ -1,15 +1,15 @@ contract Compose { - public function compose(f,g) { + function compose(f,g) public { return lam (x) { return f(g(x)); } ; } - public function id(x) { return x; } + function id(x) public { return x; } - public function idid() { return compose(id,id); } + function idid() public { return compose(id,id); } - public function main() { + function main() public { let f = compose(id,id); return f(42); } diff --git a/test/examples/spec/01id.solc b/test/examples/spec/01id.solc index 7e2868436..e83ac75b5 100644 --- a/test/examples/spec/01id.solc +++ b/test/examples/spec/01id.solc @@ -1,14 +1,14 @@ contract Id1 { - data Bool = False | True; + enum Bool { False, True } - public function id(x : word) -> word { + function id(x : word) public returns (word) { return x ; } - public function const(x : word, y : Bool) -> word { return x; } + function const(x : word, y : Bool) public returns (word) { return x; } - public function main() -> word { + function main() public returns (word) { return const(id(42), Bool.False); } } diff --git a/test/examples/spec/021not.solc b/test/examples/spec/021not.solc index df5b93773..0db946d57 100644 --- a/test/examples/spec/021not.solc +++ b/test/examples/spec/021not.solc @@ -1,21 +1,21 @@ contract Not { - data Bool = False | True; + enum Bool { False, True } - public function main() -> word { + function main() public returns (word) { return fromBool(bnot(Bool.False)); } - public function fromBool(b : Bool) -> word { + function fromBool(b : Bool) public returns (word) { match(b) { - | Bool.False => return 0; - | Bool.True => return 1; - } + case Bool.False { return 0; + } case Bool.True { return 1; + } } } - public function bnot(b : Bool) -> Bool { - match b { - | Bool.False => return Bool.True; - | Bool.True => return Bool.False; - } + function bnot(b : Bool) public returns (Bool) { + match (b ) { + case Bool.False { return Bool.True; + } case Bool.True { return Bool.False; + } } } } diff --git a/test/examples/spec/022add.solc b/test/examples/spec/022add.solc index 3ef65f351..ef9006805 100644 --- a/test/examples/spec/022add.solc +++ b/test/examples/spec/022add.solc @@ -1,4 +1,4 @@ -function add(x : word, y : word) -> word { +function add(x : word, y : word) returns (word) { let res: word; assembly { res := add(x, y) @@ -7,7 +7,7 @@ function add(x : word, y : word) -> word { } contract Add1 { - public function main() -> word { + function main() public returns (word) { return add(40, 2); } } diff --git a/test/examples/spec/024arith.solc b/test/examples/spec/024arith.solc index a79ab49c2..acd4fb307 100644 --- a/test/examples/spec/024arith.solc +++ b/test/examples/spec/024arith.solc @@ -1,6 +1,6 @@ -function add(x : word, y : word) -> word { +function add(x : word, y : word) returns (word) { let res: word; assembly { res := add(x, y) @@ -8,7 +8,7 @@ function add(x : word, y : word) -> word { return res; } -function sub(x : word, y : word) -> word { +function sub(x : word, y : word) returns (word) { let res: word; assembly { res := sub(x, y) @@ -16,7 +16,7 @@ function sub(x : word, y : word) -> word { return res; } -function div(x : word, y: word) -> word { +function div(x : word, y: word) returns (word) { let res: word; assembly { res := div(x, y) @@ -24,7 +24,7 @@ function div(x : word, y: word) -> word { return res; } -function sdiv(x : word, y: word) -> word { +function sdiv(x : word, y: word) returns (word) { let res: word; assembly { res := sdiv(x, y) @@ -32,7 +32,7 @@ function sdiv(x : word, y: word) -> word { return res; } -function mod(x : word, y: word) -> word { +function mod(x : word, y: word) returns (word) { let res: word; assembly { res := mod(x, y) @@ -40,7 +40,7 @@ function mod(x : word, y: word) -> word { return res; } -function smod(x : word, y: word) -> word { +function smod(x : word, y: word) returns (word) { let res: word; assembly { res := smod(x, y) @@ -48,7 +48,7 @@ function smod(x : word, y: word) -> word { return res; } -function exp(x : word, y: word) -> word { +function exp(x : word, y: word) returns (word) { let res: word; assembly { res := exp(x, y) @@ -58,7 +58,7 @@ function exp(x : word, y: word) -> word { contract Arith { - public function main() -> word { + function main() public returns (word) { return add(mod(sub(div(exp(2,18),4), 1), 16), 27); } } diff --git a/test/examples/spec/027sstore.solc b/test/examples/spec/027sstore.solc index cfd5619af..e4b8d5f40 100644 --- a/test/examples/spec/027sstore.solc +++ b/test/examples/spec/027sstore.solc @@ -1,5 +1,5 @@ contract Sstore { - public function main() { + function main() public { let res : word; assembly { sstore(0, 42) diff --git a/test/examples/spec/02nid.solc b/test/examples/spec/02nid.solc index 166d01e2a..b8dc51c6a 100644 --- a/test/examples/spec/02nid.solc +++ b/test/examples/spec/02nid.solc @@ -1,15 +1,15 @@ contract Id1 { - public function id(x : word) -> word { + function id(x : word) public returns (word) { return x ; } - public function nid(x : word) -> word { + function nid(x : word) public returns (word) { return id(x); } - public function const(x : word, y : word) -> word { return x; } + function const(x : word, y : word) public returns (word) { return x; } - public function main() -> word { + function main() public returns (word) { return const(nid(42), id(1)); } } diff --git a/test/examples/spec/031maybe.solc b/test/examples/spec/031maybe.solc index d1de1135f..68cb37faf 100644 --- a/test/examples/spec/031maybe.solc +++ b/test/examples/spec/031maybe.solc @@ -1,16 +1,16 @@ contract Option { - data Option(a) = None | Some(a); + enum Option { None, Some(a) } - public function just(x : word) -> Option(word) { return Option.Some(x); } + function just(x : word) public returns (Option) { return Option.Some(x); } - public function maybe(n : word, o : Option(word)) -> word { - match o { - | Option.None => return n; - | Option.Some(x) => return x; - } + function maybe(n : word, o : Option) public returns (word) { + match (o ) { + case Option.None { return n; + } case Option.Some(x) { return x; + } } } - public function main() -> word { + function main() public returns (word) { return maybe(0, Option.Some(42)); } } diff --git a/test/examples/spec/032simplejoin.solc b/test/examples/spec/032simplejoin.solc index 074e2100c..ebcd59ff8 100644 --- a/test/examples/spec/032simplejoin.solc +++ b/test/examples/spec/032simplejoin.solc @@ -1,35 +1,35 @@ contract Option { - data Option(a) = None | Some(a); + enum Option { None, Some(a) } - public function just(x : word) -> Option(word) { return Option.Some(x); } + function just(x : word) public returns (Option) { return Option.Some(x); } - public function maybe(n : word, o : Option(word)) -> word { - match o { - | Option.None => return n; - | Option.Some(x) => return x; - } + function maybe(n : word, o : Option) public returns (word) { + match (o ) { + case Option.None { return n; + } case Option.Some(x) { return x; + } } } - public function join(mmx : Option(Option(word))) -> Option(word) { - match mmx { - | Option.None => return Option.None; - | Option.Some(Option.None) => return Option.None; - | Option.Some(Option.Some(x)) => return Option.Some(x); - } + function join(mmx : Option>) public returns (Option) { + match (mmx ) { + case Option.None { return Option.None; + } case Option.Some(Option.None) { return Option.None; + } case Option.Some(Option.Some(x)) { return Option.Some(x); + } } } - public function join2(mmx : Option(Option(word))) -> Option(word) { - match mmx { - | Option.Some(m) => match m { - | Option.None => return Option.None; - | Option.Some(x) => return Option.Some(x); - } - | _ => return Option.None; - } + function join2(mmx : Option>) public returns (Option) { + match (mmx ) { + case Option.Some(m) { match (m ) { + case Option.None { return Option.None; + } case Option.Some(x) { return Option.Some(x); + } } + } default { return Option.None; + } } } - public function main() -> word { + function main() public returns (word) { return maybe(0, join(Option.Some(Option.Some(42)))); } } diff --git a/test/examples/spec/033join.solc b/test/examples/spec/033join.solc index d6664528c..7e6b865c0 100644 --- a/test/examples/spec/033join.solc +++ b/test/examples/spec/033join.solc @@ -1,23 +1,23 @@ contract Option { - data Option(a) = None | Some(a); + enum Option { None, Some(a) } - public function just(x : word) -> Option(word) { return Option.Some(x); } + function just(x : word) public returns (Option) { return Option.Some(x); } - public function maybe(n : word, o : Option(word)) -> word { - match o { - | Option.None => return n; - | Option.Some(x) => return x; - } + function maybe(n : word, o : Option) public returns (word) { + match (o ) { + case Option.None { return n; + } case Option.Some(x) { return x; + } } } - public function join(mmx : Option(Option(word))) -> Option(word) { - match mmx { - | Option.Some(Option.Some(x)) => return Option.Some(x); - | _ => return Option.None; - } + function join(mmx : Option>) public returns (Option) { + match (mmx ) { + case Option.Some(Option.Some(x)) { return Option.Some(x); + } default { return Option.None; + } } } - public function main() -> word { + function main() public returns (word) { return maybe(0, join(Option.Some(Option.Some(42)))); } } diff --git a/test/examples/spec/034cojoin.solc b/test/examples/spec/034cojoin.solc index f31954dbc..20b429644 100644 --- a/test/examples/spec/034cojoin.solc +++ b/test/examples/spec/034cojoin.solc @@ -1,41 +1,41 @@ contract Option { - data Option(a) = None | Some(a); + enum Option { None, Some(a) } - public function just(x : word) -> Option(word) { return Option.Some(x); } + function just(x : word) public returns (Option) { return Option.Some(x); } - public function maybe(n : word, o : Option(word)) -> word { - match o { - | Option.None => return n; - | Option.Some(x) => return x; - } + function maybe(n : word, o : Option) public returns (word) { + match (o ) { + case Option.None { return n; + } case Option.Some(x) { return x; + } } } - public function join(mmx : Option(Option(word))) -> Option(word) { + function join(mmx : Option>) public returns (Option) { let result = Option.None; - match mmx { - | Option.Some(Option.Some(x)) => result = Option.Some(x); - | Option.None => result = Option.None; - | Option.Some(Option.None) => result = Option.None; - | _ => result = Option.None; - } + match (mmx ) { + case Option.Some(Option.Some(x)) { result = Option.Some(x); + } case Option.None { result = Option.None; + } case Option.Some(Option.None) { result = Option.None; + } default { result = Option.None; + } } return result; } - public function extract(mx : Option(word)) -> word { - match mx { - | Option.Some(x) => return x; - | Option.None => return 0; - } + function extract(mx : Option) public returns (word) { + match (mx ) { + case Option.Some(x) { return x; + } case Option.None { return 0; + } } } - public function cojoin(x : Option(word)) -> Option(Option(word)) { // Test that sum types can grow + function cojoin(x : Option) public returns (Option>) { // Test that sum types can grow let result = Option.None; result = Option.Some(x); return result; } - public function main() -> word { + function main() public returns (word) { return maybe(0, join(cojoin(Option.Some(42)))); } } diff --git a/test/examples/spec/035padding.solc b/test/examples/spec/035padding.solc index c7b687c91..73fa5eb8d 100644 --- a/test/examples/spec/035padding.solc +++ b/test/examples/spec/035padding.solc @@ -1,14 +1,14 @@ contract Option { - data Option(a) = None | Some(a); + enum Option { None, Some(a) } - public function maybe(n : word, o : Option(word)) -> word { - match o { - | Option.Some(x) => return x; - | Option.None => return n; - } + function maybe(n : word, o : Option) public returns (word) { + match (o ) { + case Option.Some(x) { return x; + } case Option.None { return n; + } } } - public function main() -> word { + function main() public returns (word) { return maybe(7, Option.None); } } diff --git a/test/examples/spec/036wildcard.solc b/test/examples/spec/036wildcard.solc index 1e83f44f7..deb796a2e 100644 --- a/test/examples/spec/036wildcard.solc +++ b/test/examples/spec/036wildcard.solc @@ -1,14 +1,14 @@ contract Option { - data Option(a) = None | Some(a); + enum Option { None, Some(a) } - public function maybe(n : word, o : Option(word)) -> word { - match o { - | Option.Some(x) => return x; - | _ => return n; - } + function maybe(n : word, o : Option) public returns (word) { + match (o ) { + case Option.Some(x) { return x; + } default { return n; + } } } - public function main() -> word { + function main() public returns (word) { return maybe(7, Option.None); } } diff --git a/test/examples/spec/037dwarves.solc b/test/examples/spec/037dwarves.solc index 94c725297..37ca92e8b 100644 --- a/test/examples/spec/037dwarves.solc +++ b/test/examples/spec/037dwarves.solc @@ -1,17 +1,17 @@ contract Dwarves { - data Dwarf = Doc | Grumpy | Sleepy | Bashful | Happy | Sneezy | Dopey; + enum Dwarf { Doc, Grumpy, Sleepy, Bashful, Happy, Sneezy, Dopey } - public function fromEnum(c : Dwarf) -> word { - match c { - | Dwarf.Doc => return 1; - | Dwarf.Grumpy => return 2; - | Dwarf.Sleepy => return 3; - | Dwarf.Bashful => return 4; - | Dwarf.Happy => return 5; - | _ => return 0; - } + function fromEnum(c : Dwarf) public returns (word) { + match (c ) { + case Dwarf.Doc { return 1; + } case Dwarf.Grumpy { return 2; + } case Dwarf.Sleepy { return 3; + } case Dwarf.Bashful { return 4; + } case Dwarf.Happy { return 5; + } default { return 0; + } } } - public function main() -> word { return fromEnum(Dwarf.Happy); } + function main() public returns (word) { return fromEnum(Dwarf.Happy); } } diff --git a/test/examples/spec/038food0.solc b/test/examples/spec/038food0.solc index 9d7d33a9a..ba7ebb6f7 100644 --- a/test/examples/spec/038food0.solc +++ b/test/examples/spec/038food0.solc @@ -1,23 +1,23 @@ -data Food = Curry | Beans | Other; -data CFood = Red(Food) | Green(Food) | Nocolor; +enum Food { Curry, Beans, Other } +enum CFood { Red(Food), Green(Food), Nocolor } - function fromEnum(x : CFood) -> word { - match x { - | CFood.Red(Food.Curry) => return 1; - | CFood.Green(Food.Beans) => return 42; - | _ => return 3; - } + function fromEnum(x : CFood) returns (word) { + match (x ) { + case CFood.Red(Food.Curry) { return 1; + } case CFood.Green(Food.Beans) { return 42; + } default { return 3; + } } } contract FoodContract { - public function id(x : CFood) -> CFood { + function id(x : CFood) public returns (CFood) { return(x); } - public function main() -> word { + function main() public returns (word) { return fromEnum(id(CFood.Green(Food.Beans))); } } diff --git a/test/examples/spec/039food.solc b/test/examples/spec/039food.solc index ef63da674..d6fd14e37 100644 --- a/test/examples/spec/039food.solc +++ b/test/examples/spec/039food.solc @@ -1,29 +1,29 @@ -data Food = Curry | Beans | Other; -data CFood = Red(Food) | Green(Food) | Nocolor; +enum Food { Curry, Beans, Other } +enum CFood { Red(Food), Green(Food), Nocolor } - function fromEnum(x : Food) -> word { - match x { - | Food.Curry => return 1; - | Food.Beans => return 42; - | Food.Other => return 3; - } + function fromEnum(x : Food) returns (word) { + match (x ) { + case Food.Curry { return 1; + } case Food.Beans { return 42; + } case Food.Other { return 3; + } } } contract FoodContract { - public function eat(x : CFood) -> Food { - match x { - | CFood.Red(f) => return f; - | CFood.Green(f) => return f; - | _ => return Food.Other; - } + function eat(x : CFood) public returns (Food) { + match (x ) { + case CFood.Red(f) { return f; + } case CFood.Green(f) { return f; + } default { return Food.Other; + } } } - public function main() -> word { + function main() public returns (word) { return fromEnum(eat(CFood.Green(Food.Beans))); } } diff --git a/test/examples/spec/041pair.solc b/test/examples/spec/041pair.solc index b8180a0a0..c1c666754 100644 --- a/test/examples/spec/041pair.solc +++ b/test/examples/spec/041pair.solc @@ -1,12 +1,12 @@ contract Pair { - public function fst(p : (word, word)) -> word { - match p { - | (a,b) => return a; - } + function fst(p : (word, word)) public returns (word) { + match (p ) { + case (a,b) { return a; + } } } - public function main() -> word { + function main() public returns (word) { return fst((1,0)); } } diff --git a/test/examples/spec/042triple.solc b/test/examples/spec/042triple.solc index 10c3724c0..cf867fa53 100644 --- a/test/examples/spec/042triple.solc +++ b/test/examples/spec/042triple.solc @@ -1,12 +1,12 @@ contract Triple { - public function asel(t : (word, word, word)) -> word { - match t { - | (a,b,c) => return c; - } + function asel(t : (word, word, word)) public returns (word) { + match (t ) { + case (a,b,c) { return c; + } } } - public function main() -> word { + function main() public returns (word) { return asel((1,21,42)); } } diff --git a/test/examples/spec/043fstsnd.solc b/test/examples/spec/043fstsnd.solc index 62db7ccf7..465b6827c 100644 --- a/test/examples/spec/043fstsnd.solc +++ b/test/examples/spec/043fstsnd.solc @@ -1,21 +1,21 @@ -data B = F | T; -data Pair(a,b) = Pair(a,b); +enum B { F, T } +enum Pair { Pair(a, b) } -forall a b . function fst (p : Pair(a, b)) -> a { - match p { - | Pair(x,y) => return x; - } +function fst (p : Pair) returns (a) { + match (p ) { + case Pair(x,y) { return x; + } } } -forall a b . function snd(p : Pair(a, b)) -> b { - match p { - | Pair(x,y) => return y; - } +function snd(p : Pair) returns (b) { + match (p ) { + case Pair(x,y) { return y; + } } } -function add(x : word, y : word) -> word { +function add(x : word, y : word) returns (word) { let res: word; assembly { res := add(x, y) @@ -24,10 +24,10 @@ function add(x : word, y : word) -> word { } -function addPair(p : Pair(word, word)) -> word { +function addPair(p : Pair) returns (word) { return add(fst(p), snd(p)); } contract FstSnd { - public function main() -> word { return addPair(Pair(41,1)); } + function main() public returns (word) { return addPair(Pair(41,1)); } } diff --git a/test/examples/spec/047rgb.solc b/test/examples/spec/047rgb.solc index 576182e56..b4744643f 100644 --- a/test/examples/spec/047rgb.solc +++ b/test/examples/spec/047rgb.solc @@ -1,10 +1,10 @@ contract RGB { - data Color = R | G | B; - public function main() -> word { - match Color.B { - | Color.R => return 4; - | Color.G => return 2; - | Color.B => return 42; - } + enum Color { R, G, B } + function main() public returns (word) { + match (Color.B ) { + case Color.R { return 4; + } case Color.G { return 2; + } case Color.B { return 42; + } } } } diff --git a/test/examples/spec/048rgb2.solc b/test/examples/spec/048rgb2.solc index 5e33af5d6..af5c34579 100644 --- a/test/examples/spec/048rgb2.solc +++ b/test/examples/spec/048rgb2.solc @@ -1,13 +1,13 @@ contract RGB { - data Color = R | G | B; + enum Color { R, G, B } - public function fromEnum(c : Color) -> word { - match c { - | Color.R => return 4; - | Color.G => return 2; - | Color.B => return 42; - } + function fromEnum(c : Color) public returns (word) { + match (c ) { + case Color.R { return 4; + } case Color.G { return 2; + } case Color.B { return 42; + } } } - public function main() -> word { return fromEnum(Color.B); } + function main() public returns (word) { return fromEnum(Color.B); } } diff --git a/test/examples/spec/049rgb3.solc b/test/examples/spec/049rgb3.solc index 8cfbaeca4..603aac33e 100644 --- a/test/examples/spec/049rgb3.solc +++ b/test/examples/spec/049rgb3.solc @@ -1,17 +1,17 @@ -data RGB = Red(word) | Green(word) | Blue(word); +enum RGB { Red(word), Green(word), Blue(word) } contract RGB3 { - public function choose(c:RGB) -> word { + function choose(c:RGB) public returns (word) { let res : word; - match c { - | .Red(x) => assembly { res := add(x,1) } - | .Green(x) => assembly { res := add(x,2) } - | .Blue(x) => assembly { res := add(x,3) } - } + match (c ) { + case .Red(x) { assembly { res := add(x,1) } + } case .Green(x) { assembly { res := add(x,2) } + } case .Blue(x) { assembly { res := add(x,3) } + } } return res; } - public function main() -> word { - choose(RGB.Green(42)) + function main() public returns (word) { + return choose(RGB.Green(42)); } } \ No newline at end of file diff --git a/test/examples/spec/051expreturn.solc b/test/examples/spec/051expreturn.solc index 9bbbd0565..cb62446ef 100644 --- a/test/examples/spec/051expreturn.solc +++ b/test/examples/spec/051expreturn.solc @@ -1,10 +1,10 @@ -data Bool = False | True; -data W = W(Word); -data U = U; +enum Bool { False, True } +enum W { W(Word) } +enum U { U } // empty class needed since forall expects a nonempty context -class a :Top {} -instance a:Top {} +trait Top {} +impl Top {} /* For experiments, special handling when emitting code */ // this does not work, typechecker forces a ~ b @@ -13,44 +13,44 @@ instance a:Top {} // forall a.(a:Top) => function ereturn(x:a) -> a // or -forall a . function ereturn(x:a) -> Unit { let res: Unit; return res; } +function ereturn(x:a) returns (Unit) { let res: Unit; return res; } // and then cast it to any type using unsafeCast /* simulate match expression x = match { | Bool.False => return 77; | Bool.True => W(22) } */ -function elimBool1(b:Bool) -> Word { +function elimBool1(b:Bool) returns (Word) { let x : W; x = W(1); - match b { + match (b ) { // this works // | Bool.False => x = unsafeCast(ereturn(77)); // but this does not - unknown intermediate type // | Bool.False => x = unsafeCast(unsafeCast(ereturn(77))); // what about "return(return 77)"? // this works - | Bool.False => x = unsafeCast(ereturn(ereturn(77))); + case Bool.False { x = unsafeCast(ereturn(ereturn(77))); // but this does not // | Bool.False => x = unsafeCast(ereturn(unsafeCast(ereturn(77)))); - | Bool.True => x = W(22); - } + } case Bool.True { x = W(22); + } } - match x { - | W(y) => return y; - } + match (x ) { + case W(y) { return y; + } } } // "semicolon" -forall a. function semi(x:a) -> U { return U;} +function semi(x:a) returns (U) { return U;} -forall a b. function unsafeCast(x:a) -> b { +function unsafeCast(x:a) returns (b) { let res: b; return res; } contract ExpReturn { - public function main() -> Word { + function main() public returns (Word) { return elimBool1(Bool.False); // return elimBool1(Bool.False); } diff --git a/test/examples/spec/051negBool.solc b/test/examples/spec/051negBool.solc index f034aa1b1..a431bff87 100644 --- a/test/examples/spec/051negBool.solc +++ b/test/examples/spec/051negBool.solc @@ -1,29 +1,29 @@ -class a : Neg { - function neg(x:a) -> a; +trait Neg { + function neg(x:a) returns (a); } -data B = F | T; +enum B { F, T } -instance B : Neg { +impl Neg { function neg (x : B) { - match x { - | B.F => return B.T; - | B.T => return B.F; - } + match (x ) { + case B.F { return B.T; + } case B.T { return B.F; + } } } } contract NegBool { - public function fromB(b) { - match b { - | B.F => return 0; - | B.T => return 1; - } + function fromB(b) public { + match (b ) { + case B.F { return 0; + } case B.T { return 1; + } } } - public function main() { return fromB(Neg.neg(B.F)); } + function main() public { return fromB(Neg.neg(B.F)); } } diff --git a/test/examples/spec/052negPair.solc b/test/examples/spec/052negPair.solc index f578d8e9a..5565265e3 100644 --- a/test/examples/spec/052negPair.solc +++ b/test/examples/spec/052negPair.solc @@ -1,34 +1,34 @@ -class a : Neg { - function neg(x:a) -> a; +trait Neg { + function neg(x:a) returns (a); } -data B = F | T; -data Pair(a,b) = Pair(a,b); +enum B { F, T } +enum Pair { Pair(a, b) } -instance B : Neg { +impl Neg { function neg (x : B) { - match x { - | B.F => return B.T; - | B.T => return B.F; - } + match (x ) { + case B.F { return B.T; + } case B.T { return B.F; + } } } } function fst (p) { - match p { - | Pair(x,y) => return x; - } + match (p ) { + case Pair(x,y) { return x; + } } } function snd(p) { - match p { - | Pair(x,y) => return y; - } + match (p ) { + case Pair(x,y) { return y; + } } } -instance (a:Neg,b:Neg) => Pair(a,b):Neg { +impl Neg> where a: Neg, b: Neg { function neg(p) { return Pair(Neg.neg (fst(p)), Neg.neg(snd (p))); } @@ -45,19 +45,19 @@ instance (a:Neg,b:Neg) => Pair(a,b):Neg { */ contract NegPair { - public function bnot(x) { - match x { - | B.T => return B.F; - | B.F => return B.T; - } + function bnot(x) public { + match (x ) { + case B.T { return B.F; + } case B.F { return B.T; + } } } - public function fromB(b) { - match b { - | B.F => return 0; - | B.T => return 1; - } + function fromB(b) public { + match (b ) { + case B.F { return 0; + } case B.T { return 1; + } } } - public function main() { return fromB(fst(Neg.neg(Pair(B.F,B.T)))); } + function main() public { return fromB(fst(Neg.neg(Pair(B.F,B.T)))); } } diff --git a/test/examples/spec/052return.solc b/test/examples/spec/052return.solc index e62afc9bf..1e0a06965 100644 --- a/test/examples/spec/052return.solc +++ b/test/examples/spec/052return.solc @@ -1,6 +1,6 @@ -data Bool = False | True; -data W = W(word); -data U = U; +enum Bool { False, True } +enum W { W(word) } +enum U { U } /* For experiments, special handling when emitting code */ @@ -10,18 +10,18 @@ data U = U; // function ereturn(x:a) -> a // or -function ereturn(x:a) -> unit { let res: unit; return res; } +function ereturn(x:a) returns (unit) { let res: unit; return res; } // and then cast it to any type using unsafeCast /* simulate match expression x = match { | Bool.False => return 77; | Bool.True => W(22) } */ -function elimBool1(b:Bool) -> word { +function elimBool1(b:Bool) returns (word) { let x : W; x = W(1); - match b { + match (b ) { // this works - | Bool.False => x = unsafeCast(ereturn(77)); + case Bool.False { x = unsafeCast(ereturn(77)); // but this does not - unknown intermediate type // | Bool.False => x = unsafeCast(unsafeCast(ereturn(77))); // what about "return(return 77)"? @@ -32,25 +32,25 @@ function elimBool1(b:Bool) -> word { // this does not work (monomorphisation fails): // | Bool.False => x = unsafeCast(ereturn(unsafeCast(ereturn(77)))); - | Bool.True => x = W(22); - } + } case Bool.True { x = W(22); + } } - match x { - | W(y) => return y; - } + match (x ) { + case W(y) { return y; + } } } // "semicolon" -function semi(x:a) -> U { return U;} +function semi(x:a) returns (U) { return U;} -function unsafeCast(x:a) -> b { +function unsafeCast(x:a) returns (b) { let res: b; return res; } contract ExpReturn { - public function main() -> word { + function main() public returns (word) { return elimBool1(Bool.False); // return elimBool1(Bool.True); } diff --git a/test/examples/spec/053return.solc b/test/examples/spec/053return.solc index 0639c116a..12d2bef90 100644 --- a/test/examples/spec/053return.solc +++ b/test/examples/spec/053return.solc @@ -1,35 +1,35 @@ -data Bool = False | True; -data W = W(word); +enum Bool { False, True } +enum W { W(word) } /* For experiments, special handling when emitting code */ -function ereturn(x:a) -> b { let res: b; return res; } +function ereturn(x:a) returns (b) { let res: b; return res; } /* simulate match expression x = match { | Bool.False => return 77; | Bool.True => W(22) } */ -function elimBool1(b:Bool) -> word { +function elimBool1(b:Bool) returns (word) { let x : W; x = W(1); - match b { + match (b ) { // this works - | Bool.False => x = ereturn(77); + case Bool.False { x = ereturn(77); // what about "return(return 77)"? // this does not work (monomorphisation fails) // | Bool.False => x = ereturn(ereturn(77)); - | Bool.True => x = W(22); - } + } case Bool.True { x = W(22); + } } - match x { - | W(y) => return y; - } + match (x ) { + case W(y) { return y; + } } } contract ExpReturn { - public function main() -> word { + function main() public returns (word) { return elimBool1(Bool.False); // return elimBool1(Bool.True); } diff --git a/test/examples/spec/06comp.solc b/test/examples/spec/06comp.solc index 301615d76..3df3e8b60 100644 --- a/test/examples/spec/06comp.solc +++ b/test/examples/spec/06comp.solc @@ -1,9 +1,9 @@ contract Compose { - public function id(x : word) -> word { return x; } + function id(x : word) public returns (word) { return x; } - public function idid(x : word) -> word { return id(id(x)); } + function idid(x : word) public returns (word) { return id(id(x)); } - public function main() -> word { + function main() public returns (word) { return idid(42); } } diff --git a/test/examples/spec/09not.solc b/test/examples/spec/09not.solc index df5b93773..0db946d57 100644 --- a/test/examples/spec/09not.solc +++ b/test/examples/spec/09not.solc @@ -1,21 +1,21 @@ contract Not { - data Bool = False | True; + enum Bool { False, True } - public function main() -> word { + function main() public returns (word) { return fromBool(bnot(Bool.False)); } - public function fromBool(b : Bool) -> word { + function fromBool(b : Bool) public returns (word) { match(b) { - | Bool.False => return 0; - | Bool.True => return 1; - } + case Bool.False { return 0; + } case Bool.True { return 1; + } } } - public function bnot(b : Bool) -> Bool { - match b { - | Bool.False => return Bool.True; - | Bool.True => return Bool.False; - } + function bnot(b : Bool) public returns (Bool) { + match (b ) { + case Bool.False { return Bool.True; + } case Bool.True { return Bool.False; + } } } } diff --git a/test/examples/spec/101struct1Field.solc b/test/examples/spec/101struct1Field.solc index 35840a395..cc4ccfbdd 100644 --- a/test/examples/spec/101struct1Field.solc +++ b/test/examples/spec/101struct1Field.solc @@ -1,78 +1,78 @@ /////// Construction -class abs:Typedef(rep) { - function rep(x:abs) -> rep; - function abs(x:rep) -> abs; +trait Typedef { + function rep(x:abs) returns (rep); + function abs(x:rep) returns (abs); } -data uint = uint(word); +enum uint { uint(word) } -instance word:Typedef(word) { - function rep(x:word) -> word { return x; } - function abs(x:word) -> word { return x;} +impl Typedef { + function rep(x:word) returns (word) { return x; } + function abs(x:word) returns (word) { return x;} } -instance uint:Typedef(word) { - function rep(x:uint) -> word { - match x { - | uint(y) => return y; - } +impl Typedef { + function rep(x:uint) returns (word) { + match (x ) { + case uint(y) { return y; + } } } - function abs(x:word) -> uint { + function abs(x:word) returns (uint) { return uint(x); } } -data memory(a) = memory(word); -data memoryRef(a) = memoryRef(word); -data Proxy(a) = Proxy; +enum memory { memory(word) } +enum memoryRef { memoryRef(word) } +enum Proxy { Proxy } -instance memory(a):Typedef(word) { - function rep(x:memory(a)) -> word { - match x { - | memory(y) => return y; - } +impl Typedef { + function rep(x:a memory) returns (word) { + match (x ) { + case memory(y) { return y; + } } } - function abs(x:word) -> memory(a) { + function abs(x:word) returns (a memory) { return memory(x); } } -instance memoryRef(a):Typedef(word) { - function rep(x:memoryRef(a)) -> word { - match x { - | memoryRef(y) => return y; - } +impl Typedef, word> { + function rep(x:memoryRef) returns (word) { + match (x ) { + case memoryRef(y) { return y; + } } } - function abs(x:word) -> memoryRef(a) { + function abs(x:word) returns (memoryRef) { return memoryRef(x); } } -class lhs:Assign(rhs) { - function assign(l:lhs, r:rhs) -> (); +trait Assign { + function assign(l:lhs, r:rhs) returns (()); } -data ref(a) = ref(a); +enum ref { ref(a) } -instance ref(a):Assign(a) { - function assign(l:ref(a), r:a) -> () { +impl Assign, a> { + function assign(l:ref, r:a) returns (()) { // builtin "stack store" - return (); + return; } } -class self:MemoryType { - function load(ptr:word) -> self; - function store(ptr:word, value:self) -> (); +trait MemoryType { + function load(ptr:word) returns (self); + function store(ptr:word, value:self) returns (()); } -class self:MemorySize { - function size(x:Proxy(self)) -> word; +trait MemorySize { + function size(x:Proxy) returns (word); } -function mload_(x:word) -> word { +function mload_(x:word) returns (word) { let res: word; assembly { res := mload(x) @@ -84,70 +84,65 @@ function mstore_(a:word, v:word) { assembly { mstore(a,v) } } -instance word:MemoryType { - function load(ptr:word) -> word { +impl MemoryType { + function load(ptr:word) returns (word) { let r:word; assembly { r := mload(ptr) } return r; } - function store(ptr:word, value:word) -> () { + function store(ptr:word, value:word) returns (()) { assembly { mstore(ptr, value) } } } -instance uint:MemoryType { - function load(ptr:word) -> uint { - return Typedef.abs(mload_(ptr)):uint; // type annotation needed due to a typechecker bug +impl MemoryType { + function load(ptr:word) returns (uint) { + return Typedef.abs(mload_(ptr)) as uint; // type annotation needed due to a typechecker bug } - function store(ptr:word, value:uint) -> () { + function store(ptr:word, value:uint) returns (()) { return mstore_(ptr, Typedef.rep(value)); } } -forall a . a : MemoryType => instance memoryRef(a):Assign(a) { - function assign(l:memoryRef(a), y:a) { +impl Assign, a> where a: MemoryType { + function assign(l:memoryRef, y:a) { MemoryType.store(Typedef.rep(l), y); } } -data MemberAccessProxy(a, field, offset) = MemberAccessProxy(a, field, Proxy(offset)); +enum MemberAccessProxy { MemberAccessProxy(a, field, Proxy) } -forall a field offset . -function memberAccessD1(x:MemberAccessProxy(a, field, offset)) -> a { - match x { - | MemberAccessProxy(y,z,p) => return y; - } +function memberAccessD1(x:MemberAccessProxy) returns (a) { + match (x ) { + case MemberAccessProxy(y,z,p) { return y; + } } } -class self:LValueMemberAccess(memberRefType) { - function memberAccess(x:self) -> memberRefType; +trait LValueMemberAccess { + function memberAccess(x:self) returns (memberRefType); } -forall self memberValueType . -class self:RValueMemberAccess(memberValueType) { - function memberAccess(x:self) -> memberValueType; +trait RValueMemberAccess { + function memberAccess(x:self) returns (memberValueType); } // This is *a lot* of pragmas... // pragma no-coverage-condition CStructField, LValueMemberAccess, RValueMemberAccess; // pragma no-patterson-condition LValueMemberAccess, RValueMemberAccess; // pragma no-bounded-variable-condition LValueMemberAccess, RValueMemberAccess; -class self:CStructField(fieldType, offsetType) {} -data StructField(structType, fieldSelector) = StructField(structType); - -forall structType fieldSelector fieldType offsetType - . StructField(structType, fieldSelector):CStructField(fieldType, offsetType) - , offsetType:MemorySize - => instance MemberAccessProxy(memory(structType), fieldSelector, offsetType):LValueMemberAccess(memoryRef(fieldType)) { - function memberAccess(x:MemberAccessProxy(memory(structType), fieldSelector, offsetType)) -> memoryRef(fieldType) { +trait CStructField {} +enum StructField { StructField(structType) } + +impl LValueMemberAccess, memoryRef> where StructField: CStructField, offsetType: MemorySize { + function memberAccess(x:MemberAccessProxy) returns (memoryRef) { let ptr:word = Typedef.rep(memberAccessD1(x)); - let size:word = MemorySize.size(Proxy:Proxy(offsetType)); + let size:word = MemorySize.size(Proxy as Proxy); assembly { ptr := add(ptr, size) } @@ -155,29 +150,29 @@ forall structType fieldSelector fieldType offsetType } } -instance ():MemorySize { - function size(x:Proxy(())) -> word { +impl MemorySize<()> { + function size(x:Proxy<()>) returns (word) { return 0; } } -instance word:MemorySize { - function size(x:Proxy(word)) -> word { +impl MemorySize { + function size(x:Proxy) returns (word) { return 32; } } -instance uint:MemorySize { - function size(x:Proxy(uint)) -> word { +impl MemorySize { + function size(x:Proxy) returns (word) { return 32; } } -forall a b . a:MemorySize, b:MemorySize => instance (a,b):MemorySize { - function size(x:Proxy((a,b))) -> word { - let a_sz:word = MemorySize.size(Proxy:Proxy(a)); - let b_sz:word = MemorySize.size(Proxy:Proxy(b)); +impl MemorySize<(a, b)> where a: MemorySize, b: MemorySize { + function size(x:Proxy<(a, b)>) returns (word) { + let a_sz:word = MemorySize.size(Proxy as Proxy); + let b_sz:word = MemorySize.size(Proxy as Proxy); assembly { a_sz := add(a_sz, b_sz) } @@ -185,14 +180,10 @@ forall a b . a:MemorySize, b:MemorySize => instance (a,b):MemorySize { } } -forall structType fieldSelector fieldType offsetType - . StructField(structType, fieldSelector):CStructField(fieldType, offsetType) - , fieldType:MemoryType - , offsetType:MemorySize - => instance MemberAccessProxy(memory(structType), fieldSelector, offsetType):RValueMemberAccess(fieldType) { - function memberAccess(x:MemberAccessProxy(memory(structType), fieldSelector, offsetType)) -> fieldType { +impl RValueMemberAccess, fieldType> where StructField: CStructField, fieldType: MemoryType, offsetType: MemorySize { + function memberAccess(x:MemberAccessProxy) returns (fieldType) { let ptr:word = Typedef.rep(memberAccessD1(x)); - let size:word = MemorySize.size(Proxy:Proxy(offsetType)); + let size:word = MemorySize.size(Proxy as Proxy); assembly { ptr := add(ptr, size) } @@ -203,23 +194,23 @@ forall structType fieldSelector fieldType offsetType ////// Testing // struct S { fld1:word; } -data S = S(word); -data fld1_sel = fld1_sel; +enum S { S(word) } +enum fld1_sel { fld1_sel } // data y_sel = y_sel; // data z_sel = z_sel; -instance StructField(S, x_sel):CStructField(word, ()) {} +impl CStructField, word, ()> {} // instance StructField(S, y_sel):CStructField(uint, word) {} // BUG: This next one should really be the following, but that breaks weirdly: // (I get a patterson condition violation on an invoke instance for g) -instance StructField(S, z_sel):CStructField(word, (word,uint)) {} +impl CStructField, word, (word, uint)> {} // So instead I use: // instance StructField(S, z_sel):CStructField(word, word) {} function f() { - let x:memory(word); - let y:memory(word); + let x:word memory; + let y:word memory; // x = y Assign.assign(ref(x), y); /* @@ -232,12 +223,12 @@ function f() { */ } -function g() -> word { - let s:memory(S) = Typedef.abs(0x80); +function g() returns (word) { + let s:S memory = Typedef.abs(0x80); - let offset0 : Proxy( () ) = Proxy; + let offset0 : Proxy<()> = Proxy; // s.fld1 = y - let fld1_lval : memoryRef(word) + let fld1_lval : memoryRef = LValueMemberAccess.memberAccess(MemberAccessProxy(s, fld1_sel, offset0)); Assign.assign(fld1_lval, y); // return s.fld1 @@ -247,7 +238,7 @@ function g() -> word { } contract C { - public function main() { + function main() public { f(); return g(); } diff --git a/test/examples/spec/102uintField.solc b/test/examples/spec/102uintField.solc index 629c07623..3d0d8224a 100644 --- a/test/examples/spec/102uintField.solc +++ b/test/examples/spec/102uintField.solc @@ -1,12 +1,12 @@ /////// Construction -class abs:Typedef(rep) { - function rep(x:abs) -> rep; - function abs(x:rep) -> abs; +trait Typedef { + function rep(x:abs) returns (rep); + function abs(x:rep) returns (abs); } -data uint = uint(word); +enum uint { uint(word) } // this does not work :( /* @@ -17,66 +17,66 @@ forall a } */ -instance uint:Typedef(word) { - function rep(x:uint) -> word { - match x { - | uint(y) => return y; - } +impl Typedef { + function rep(x:uint) returns (word) { + match (x ) { + case uint(y) { return y; + } } } - function abs(x:word) -> uint { + function abs(x:word) returns (uint) { return uint(x); } } -data memory(a) = memory(word); -data memoryRef(a) = memoryRef(word); -data Proxy(a) = Proxy; +enum memory { memory(word) } +enum memoryRef { memoryRef(word) } +enum Proxy { Proxy } -instance memory(a):Typedef(word) { - function rep(x:memory(a)) -> word { - match x { - | memory(y) => return y; - } +impl Typedef { + function rep(x:a memory) returns (word) { + match (x ) { + case memory(y) { return y; + } } } - function abs(x:word) -> memory(a) { + function abs(x:word) returns (a memory) { return memory(x); } } -instance memoryRef(a):Typedef(word) { - function rep(x:memoryRef(a)) -> word { - match x { - | memoryRef(y) => return y; - } +impl Typedef, word> { + function rep(x:memoryRef) returns (word) { + match (x ) { + case memoryRef(y) { return y; + } } } - function abs(x:word) -> memoryRef(a) { + function abs(x:word) returns (memoryRef) { return memoryRef(x); } } -class lhs:Assign(rhs) { - function assign(l:lhs, r:rhs) -> (); +trait Assign { + function assign(l:lhs, r:rhs) returns (()); } -data ref(a) = ref(a); +enum ref { ref(a) } -instance ref(a):Assign(a) { - function assign(l:ref(a), r:a) -> () { +impl Assign, a> { + function assign(l:ref, r:a) returns (()) { // builtin "stack store" - return (); + return; } } -class self:MemoryType { - function load(ptr:word) -> self; - function store(ptr:word, value:self) -> (); +trait MemoryType { + function load(ptr:word) returns (self); + function store(ptr:word, value:self) returns (()); } -class self:MemorySize { - function size(x:Proxy(self)) -> word; +trait MemorySize { + function size(x:Proxy) returns (word); } -function mload_(x:word) -> word { +function mload_(x:word) returns (word) { let res: word; assembly { res := mload(x) @@ -88,70 +88,65 @@ function mstore_(a:word, v:word) { assembly { mstore(a,v) } } -instance word:MemoryType { - function load(ptr:word) -> word { +impl MemoryType { + function load(ptr:word) returns (word) { let r:word; assembly { r := mload(ptr) } return r; } - function store(ptr:word, value:word) -> () { + function store(ptr:word, value:word) returns (()) { assembly { mstore(ptr, value) } } } -instance uint:MemoryType { - function load(ptr:word) -> uint { - return Typedef.abs(mload_(ptr)):uint; // type annotation needed due to a typechecker bug +impl MemoryType { + function load(ptr:word) returns (uint) { + return Typedef.abs(mload_(ptr)) as uint; // type annotation needed due to a typechecker bug } - function store(ptr:word, value:uint) -> () { + function store(ptr:word, value:uint) returns (()) { return mstore_(ptr, Typedef.rep(value)); } } -forall a . a : MemoryType => instance memoryRef(a):Assign(a) { - function assign(l:memoryRef(a), y:a) { +impl Assign, a> where a: MemoryType { + function assign(l:memoryRef, y:a) { MemoryType.store(Typedef.rep(l), y); } } -data MemberAccessProxy(a, field, offset) = MemberAccessProxy(a, field, Proxy(offset)); +enum MemberAccessProxy { MemberAccessProxy(a, field, Proxy) } -forall a field offset . -function memberAccessD1(x:MemberAccessProxy(a, field, offset)) -> a { - match x { - | MemberAccessProxy(y,z,p) => return y; - } +function memberAccessD1(x:MemberAccessProxy) returns (a) { + match (x ) { + case MemberAccessProxy(y,z,p) { return y; + } } } -class self:LValueMemberAccess(memberRefType) { - function memberAccess(x:self) -> memberRefType; +trait LValueMemberAccess { + function memberAccess(x:self) returns (memberRefType); } -forall self memberValueType . -class self:RValueMemberAccess(memberValueType) { - function memberAccess(x:self) -> memberValueType; +trait RValueMemberAccess { + function memberAccess(x:self) returns (memberValueType); } // This is *a lot* of pragmas... // pragma no-coverage-condition CStructField, LValueMemberAccess, RValueMemberAccess; // pragma no-patterson-condition LValueMemberAccess, RValueMemberAccess; // pragma no-bounded-variable-condition LValueMemberAccess, RValueMemberAccess; -class self:CStructField(fieldType, offsetType) {} -data StructField(structType, fieldSelector) = StructField(structType); - -forall structType fieldSelector fieldType offsetType - . StructField(structType, fieldSelector):CStructField(fieldType, offsetType) - , offsetType:MemorySize - => instance MemberAccessProxy(memory(structType), fieldSelector, offsetType):LValueMemberAccess(memoryRef(fieldType)) { - function memberAccess(x:MemberAccessProxy(memory(structType), fieldSelector, offsetType)) -> memoryRef(fieldType) { +trait CStructField {} +enum StructField { StructField(structType) } + +impl LValueMemberAccess, memoryRef> where StructField: CStructField, offsetType: MemorySize { + function memberAccess(x:MemberAccessProxy) returns (memoryRef) { let ptr:word = Typedef.rep(memberAccessD1(x)); - let size:word = MemorySize.size(Proxy:Proxy(offsetType)); + let size:word = MemorySize.size(Proxy as Proxy); assembly { ptr := add(ptr, size) } @@ -159,29 +154,29 @@ forall structType fieldSelector fieldType offsetType } } -instance ():MemorySize { - function size(x:Proxy(())) -> word { +impl MemorySize<()> { + function size(x:Proxy<()>) returns (word) { return 0; } } -instance word:MemorySize { - function size(x:Proxy(word)) -> word { +impl MemorySize { + function size(x:Proxy) returns (word) { return 32; } } -instance uint:MemorySize { - function size(x:Proxy(uint)) -> word { +impl MemorySize { + function size(x:Proxy) returns (word) { return 32; } } -forall a b . a:MemorySize, b:MemorySize => instance (a,b):MemorySize { - function size(x:Proxy((a,b))) -> word { - let a_sz:word = MemorySize.size(Proxy:Proxy(a)); - let b_sz:word = MemorySize.size(Proxy:Proxy(b)); +impl MemorySize<(a, b)> where a: MemorySize, b: MemorySize { + function size(x:Proxy<(a, b)>) returns (word) { + let a_sz:word = MemorySize.size(Proxy as Proxy); + let b_sz:word = MemorySize.size(Proxy as Proxy); assembly { a_sz := add(a_sz, b_sz) } @@ -189,30 +184,26 @@ forall a b . a:MemorySize, b:MemorySize => instance (a,b):MemorySize { } } -forall structType fieldSelector fieldType offsetType - . StructField(structType, fieldSelector):CStructField(fieldType, offsetType) - , fieldType:MemoryType - , offsetType:MemorySize - => instance MemberAccessProxy(memory(structType), fieldSelector, offsetType):RValueMemberAccess(fieldType) { - function memberAccess(x:MemberAccessProxy(memory(structType), fieldSelector, offsetType)) -> fieldType { +impl RValueMemberAccess, fieldType> where StructField: CStructField, fieldType: MemoryType, offsetType: MemorySize { + function memberAccess(x:MemberAccessProxy) returns (fieldType) { let ptr:word = Typedef.rep(memberAccessD1(x)); - let size:word = MemorySize.size(Proxy:Proxy(offsetType)); + let size:word = MemorySize.size(Proxy as Proxy); assembly { ptr := add(ptr, size) } - return MemoryType.load(ptr):fieldType; + return MemoryType.load(ptr) as fieldType; } } ////// Testing // struct S { fld1:uint; } -data S = S(uint); -data fld1_sel = fld1_sel; +enum S { S(uint) } +enum fld1_sel { fld1_sel } // data y_sel = y_sel; // data z_sel = z_sel; -instance StructField(S, fld1_sel):CStructField(uint, ()) {} +impl CStructField, uint, ()> {} // instance StructField(S, y_sel):CStructField(uint, uint) {} // BUG: This next one should really be the following, but that breaks weirdly: // (I get a patterson condition violation on an invoke instance for g) @@ -222,8 +213,8 @@ instance StructField(S, fld1_sel):CStructField(uint, ()) {} function f() { - let x:memory(word); - let y:memory(word); + let x:word memory; + let y:word memory; // x = y Assign.assign(ref(x), y); /* @@ -236,25 +227,25 @@ function f() { */ } -function g() -> word { - let s:memory(S) = Typedef.abs(0x80); +function g() returns (word) { + let s:S memory = Typedef.abs(0x80); // let y:word = 42; let z:uint = uint(42); - let offset0 : Proxy( () ) = Proxy; + let offset0 : Proxy<()> = Proxy; // s.fld1 = z - let fld1_lval : memoryRef(uint) + let fld1_lval : memoryRef = LValueMemberAccess.memberAccess(MemberAccessProxy(s, fld1_sel, offset0)); Assign.assign(fld1_lval, z); // return s.fld1 let r : uint = uint(17); r = RValueMemberAccess.memberAccess(MemberAccessProxy(s, fld1_sel, offset0) ); - let r2 : word = Typedef.rep(r : uint); + let r2 : word = Typedef.rep(r as uint); return r2; } contract C { - public function main() { + function main() public { f(); return g(); } diff --git a/test/examples/spec/103struct3Fields.solc b/test/examples/spec/103struct3Fields.solc index 87bf761b8..6b8662ced 100644 --- a/test/examples/spec/103struct3Fields.solc +++ b/test/examples/spec/103struct3Fields.solc @@ -10,13 +10,13 @@ function add(x : word, y : word) { } /////// Construction -class abs:Typedef(rep) { - function rep(x:abs) -> rep; - function abs(x:rep) -> abs; +trait Typedef { + function rep(x:abs) returns (rep); + function abs(x:rep) returns (abs); } -data uint = uint(word); +enum uint { uint(word) } // this does not work :( /* @@ -27,66 +27,66 @@ forall a } */ -instance uint:Typedef(word) { - function rep(x:uint) -> word { - match x { - | uint(y) => return y; - } +impl Typedef { + function rep(x:uint) returns (word) { + match (x ) { + case uint(y) { return y; + } } } - function abs(x:word) -> uint { + function abs(x:word) returns (uint) { return uint(x); } } -data memory(a) = memory(word); -data memoryRef(a) = memoryRef(word); -data Proxy(a) = Proxy; +enum memory { memory(word) } +enum memoryRef { memoryRef(word) } +enum Proxy { Proxy } -instance memory(a):Typedef(word) { - function rep(x:memory(a)) -> word { - match x { - | memory(y) => return y; - } +impl Typedef { + function rep(x:a memory) returns (word) { + match (x ) { + case memory(y) { return y; + } } } - function abs(x:word) -> memory(a) { + function abs(x:word) returns (a memory) { return memory(x); } } -instance memoryRef(a):Typedef(word) { - function rep(x:memoryRef(a)) -> word { - match x { - | memoryRef(y) => return y; - } +impl Typedef, word> { + function rep(x:memoryRef) returns (word) { + match (x ) { + case memoryRef(y) { return y; + } } } - function abs(x:word) -> memoryRef(a) { + function abs(x:word) returns (memoryRef) { return memoryRef(x); } } -class lhs:Assign(rhs) { - function assign(l:lhs, r:rhs) -> (); +trait Assign { + function assign(l:lhs, r:rhs) returns (()); } -data ref(a) = ref(a); +enum ref { ref(a) } -instance ref(a):Assign(a) { - function assign(l:ref(a), r:a) -> () { +impl Assign, a> { + function assign(l:ref, r:a) returns (()) { // builtin "stack store" - return (); + return; } } -class self:MemoryType { - function load(ptr:word) -> self; - function store(ptr:word, value:self) -> (); +trait MemoryType { + function load(ptr:word) returns (self); + function store(ptr:word, value:self) returns (()); } -class self:MemorySize { - function size(x:Proxy(self)) -> word; +trait MemorySize { + function size(x:Proxy) returns (word); } -function mload_(x:word) -> word { +function mload_(x:word) returns (word) { let res: word; assembly { res := mload(x) @@ -98,66 +98,61 @@ function mstore_(a:word, v:word) { assembly { mstore(a,v) } } -instance word:MemoryType { - function load(ptr:word) -> word { +impl MemoryType { + function load(ptr:word) returns (word) { let r:word; assembly { r := mload(ptr) } return r; } - function store(ptr:word, value:word) -> () { + function store(ptr:word, value:word) returns (()) { assembly { mstore(ptr, value) } } } -instance uint:MemoryType { - function load(ptr:word) -> uint { - return Typedef.abs(mload_(ptr)):uint; +impl MemoryType { + function load(ptr:word) returns (uint) { + return Typedef.abs(mload_(ptr)) as uint; } - function store(ptr:word, value:uint) -> () { + function store(ptr:word, value:uint) returns (()) { return mstore_(ptr, Typedef.rep(value)); } } -forall a . a : MemoryType => instance memoryRef(a):Assign(a) { - function assign(l:memoryRef(a), y:a) { +impl Assign, a> where a: MemoryType { + function assign(l:memoryRef, y:a) { MemoryType.store(Typedef.rep(l), y); } } -data MemberAccessProxy(a, field, offset) = MemberAccessProxy(a, field); +enum MemberAccessProxy { MemberAccessProxy(a, field) } -forall a field offset . -function memberAccessD1(x:MemberAccessProxy(a, field, offset)) -> a { - match x { - | MemberAccessProxy(y,z) => return y; - } +function memberAccessD1(x:MemberAccessProxy) returns (a) { + match (x ) { + case MemberAccessProxy(y,z) { return y; + } } } -class self:LValueMemberAccess(memberRefType) { - function memberAccess(x:self) -> memberRefType; +trait LValueMemberAccess { + function memberAccess(x:self) returns (memberRefType); } -forall self memberValueType . -class self:RValueMemberAccess(memberValueType) { - function memberAccess(x:self) -> memberValueType; +trait RValueMemberAccess { + function memberAccess(x:self) returns (memberValueType); } -class self:CStructField(fieldType, offsetType) {} -data StructField(structType, fieldSelector) = StructField(structType); +trait CStructField {} +enum StructField { StructField(structType) } -forall structType fieldSelector fieldType offsetType - . StructField(structType, fieldSelector):CStructField(fieldType, offsetType) - , offsetType:MemorySize - => instance MemberAccessProxy(memory(structType), fieldSelector, offsetType):LValueMemberAccess(memoryRef(fieldType)) { - function memberAccess(x:MemberAccessProxy(memory(structType), fieldSelector, offsetType)) -> memoryRef(fieldType) { +impl LValueMemberAccess, memoryRef> where StructField: CStructField, offsetType: MemorySize { + function memberAccess(x:MemberAccessProxy) returns (memoryRef) { let ptr:word = Typedef.rep(memberAccessD1(x)); - let size:word = MemorySize.size(Proxy:Proxy(offsetType)); + let size:word = MemorySize.size(Proxy as Proxy); assembly { ptr := add(ptr, size) } @@ -165,20 +160,20 @@ forall structType fieldSelector fieldType offsetType } } -instance ():MemorySize { - function size(x:Proxy(())) -> word { +impl MemorySize<()> { + function size(x:Proxy<()>) returns (word) { return 0; } } -instance word:MemorySize { - function size(x:Proxy(word)) -> word { +impl MemorySize { + function size(x:Proxy) returns (word) { return 32; } } -instance uint:MemorySize { - function size(x:Proxy(uint)) -> word { +impl MemorySize { + function size(x:Proxy) returns (word) { return 32; } } @@ -194,10 +189,10 @@ forall a b . a:Typedef(b), b:MemorySize } */ -forall a b . a:MemorySize, b:MemorySize => instance (a,b):MemorySize { - function size(x:Proxy((a,b))) -> word { - let a_sz:word = MemorySize.size(Proxy:Proxy(a)); - let b_sz:word = MemorySize.size(Proxy:Proxy(b)); +impl MemorySize<(a, b)> where a: MemorySize, b: MemorySize { + function size(x:Proxy<(a, b)>) returns (word) { + let a_sz:word = MemorySize.size(Proxy as Proxy); + let b_sz:word = MemorySize.size(Proxy as Proxy); assembly { a_sz := add(a_sz, b_sz) } @@ -205,48 +200,44 @@ forall a b . a:MemorySize, b:MemorySize => instance (a,b):MemorySize { } } -forall structType fieldSelector fieldType offsetType - . StructField(structType, fieldSelector):CStructField(fieldType, offsetType) - , fieldType:MemoryType - , offsetType:MemorySize - => instance MemberAccessProxy(memory(structType), fieldSelector, offsetType):RValueMemberAccess(fieldType) { - function memberAccess(x:MemberAccessProxy(memory(structType), fieldSelector, offsetType)) -> fieldType { +impl RValueMemberAccess, fieldType> where StructField: CStructField, fieldType: MemoryType, offsetType: MemorySize { + function memberAccess(x:MemberAccessProxy) returns (fieldType) { let ptr:word = Typedef.rep(memberAccessD1(x)); - let size:word = MemorySize.size(Proxy:Proxy(offsetType)); + let size:word = MemorySize.size(Proxy as Proxy); assembly { ptr := add(ptr, size) } - return MemoryType.load(ptr):fieldType; + return MemoryType.load(ptr) as fieldType; } } ////// Testing // struct S { fld1:uint; fld2:word; fld3:word } -data S = S; // (uint, word, word); -data fld1_sel = fld1_sel; -data fld2_sel = fld2_sel; -data fld3_sel = fld3_sel; +enum S { S } // (uint, word, word); +enum fld1_sel { fld1_sel } +enum fld2_sel { fld2_sel } +enum fld3_sel { fld3_sel } // form: // instance StructField(S, f_sel):CStructField(ftype, preceding)) {} -instance StructField(S, fld1_sel):CStructField(uint, ()) {} -instance StructField(S, fld2_sel):CStructField(word, uint) {} -instance StructField(S, fld3_sel):CStructField(word, (uint, word)) {} +impl CStructField, uint, ()> {} +impl CStructField, word, uint> {} +impl CStructField, word, (uint, word)> {} -function g() -> word { - let s:memory(S) = Typedef.abs(0x80); - let fld1_map : MemberAccessProxy(memory(S), fld1_sel, ()) = MemberAccessProxy(s, fld1_sel); - let fld2_map : MemberAccessProxy(memory(S), fld2_sel, uint) = MemberAccessProxy(s, fld2_sel); +function g() returns (word) { + let s:S memory = Typedef.abs(0x80); + let fld1_map : MemberAccessProxy = MemberAccessProxy(s, fld1_sel); + let fld2_map : MemberAccessProxy = MemberAccessProxy(s, fld2_sel); let fld3_map = MemberAccessProxy(s, fld3_sel) - : MemberAccessProxy(memory(S), fld3_sel, (uint,word)); + as MemberAccessProxy; // let y:word = 13; let z:uint = uint(13); // s.fld1 = z - let fld1_lval : memoryRef(uint) + let fld1_lval : memoryRef = LValueMemberAccess.memberAccess(fld1_map ); Assign.assign(fld1_lval, z); @@ -270,7 +261,7 @@ function g() -> word { let f3 : word; f3 = RValueMemberAccess.memberAccess(fld3_map); - let f12 = add(Typedef.rep(f1) : word, f2); + let f12 = add(Typedef.rep(f1) as word, f2); let f123 = add(f12, f3); return f123; @@ -278,7 +269,7 @@ function g() -> word { } contract C { - public function main() { + function main() public { return g(); } } diff --git a/test/examples/spec/105nestedStruct.solc b/test/examples/spec/105nestedStruct.solc index 6a6cc7dff..27188e069 100644 --- a/test/examples/spec/105nestedStruct.solc +++ b/test/examples/spec/105nestedStruct.solc @@ -10,13 +10,13 @@ function add(x : word, y : word) { } /////// Construction -class abs:Typedef(rep) { - function rep(x:abs) -> rep; - function abs(x:rep) -> abs; +trait Typedef { + function rep(x:abs) returns (rep); + function abs(x:rep) returns (abs); } -data uint = uint(word); +enum uint { uint(word) } // this does not work :( /* @@ -27,66 +27,66 @@ forall a } */ -instance uint:Typedef(word) { - function rep(x:uint) -> word { - match x { - | uint(y) => return y; - } +impl Typedef { + function rep(x:uint) returns (word) { + match (x ) { + case uint(y) { return y; + } } } - function abs(x:word) -> uint { + function abs(x:word) returns (uint) { return uint(x); } } -data memory(a) = memory(word); -data memoryRef(a) = memoryRef(word); -data Proxy(a) = Proxy; +enum memory { memory(word) } +enum memoryRef { memoryRef(word) } +enum Proxy { Proxy } -instance memory(a):Typedef(word) { - function rep(x:memory(a)) -> word { - match x { - | memory(y) => return y; - } +impl Typedef { + function rep(x:a memory) returns (word) { + match (x ) { + case memory(y) { return y; + } } } - function abs(x:word) -> memory(a) { + function abs(x:word) returns (a memory) { return memory(x); } } -instance memoryRef(a):Typedef(word) { - function rep(x:memoryRef(a)) -> word { - match x { - | memoryRef(y) => return y; - } +impl Typedef, word> { + function rep(x:memoryRef) returns (word) { + match (x ) { + case memoryRef(y) { return y; + } } } - function abs(x:word) -> memoryRef(a) { + function abs(x:word) returns (memoryRef) { return memoryRef(x); } } -class lhs:Assign(rhs) { - function assign(l:lhs, r:rhs) -> (); +trait Assign { + function assign(l:lhs, r:rhs) returns (()); } -data ref(a) = ref(a); +enum ref { ref(a) } -instance ref(a):Assign(a) { - function assign(l:ref(a), r:a) -> () { +impl Assign, a> { + function assign(l:ref, r:a) returns (()) { // builtin "stack store" - return (); + return; } } -class self:MemoryType { - function load(ptr:word) -> self; - function store(ptr:word, value:self) -> (); +trait MemoryType { + function load(ptr:word) returns (self); + function store(ptr:word, value:self) returns (()); } -class self:MemorySize { - function size(x:Proxy(self)) -> word; +trait MemorySize { + function size(x:Proxy) returns (word); } -function mload_(x:word) -> word { +function mload_(x:word) returns (word) { let res: word; assembly { res := mload(x) @@ -98,75 +98,70 @@ function mstore_(a:word, v:word) { assembly { mstore(a,v) } } -instance word:MemoryType { - function load(ptr:word) -> word { +impl MemoryType { + function load(ptr:word) returns (word) { let r:word; assembly { r := mload(ptr) } return r; } - function store(ptr:word, value:word) -> () { + function store(ptr:word, value:word) returns (()) { assembly { mstore(ptr, value) } } } -instance uint:MemoryType { - function load(ptr:word) -> uint { +impl MemoryType { + function load(ptr:word) returns (uint) { return Typedef.abs(mload_(ptr)); } - function store(ptr:word, value:uint) -> () { + function store(ptr:word, value:uint) returns (()) { return mstore_(ptr, Typedef.rep(value)); } } -forall a . instance memory(a):MemoryType { - function load(ptr:word) -> memory(a) { +impl MemoryType { + function load(ptr:word) returns (a memory) { return Typedef.abs(mload_(ptr)); } - function store(ptr:word, value:memory(a)) -> () { + function store(ptr:word, value:a memory) returns (()) { return mstore_(ptr, Typedef.rep(value)); } } -forall a . a : MemoryType => instance memoryRef(a):Assign(a) { - function assign(l:memoryRef(a), y:a) { +impl Assign, a> where a: MemoryType { + function assign(l:memoryRef, y:a) { MemoryType.store(Typedef.rep(l), y); } } -data MemberAccessProxy(a, field, offset) = MemberAccessProxy(a, field); +enum MemberAccessProxy { MemberAccessProxy(a, field) } -forall a field offset . -function memberAccessD1(x:MemberAccessProxy(a, field, offset)) -> a { - match x { - | MemberAccessProxy(y,z) => return y; - } +function memberAccessD1(x:MemberAccessProxy) returns (a) { + match (x ) { + case MemberAccessProxy(y,z) { return y; + } } } -class self:LValueMemberAccess(memberRefType) { - function memberAccess(x:self) -> memberRefType; +trait LValueMemberAccess { + function memberAccess(x:self) returns (memberRefType); } -forall self memberValueType . -class self:RValueMemberAccess(memberValueType) { - function memberAccess(x:self) -> memberValueType; +trait RValueMemberAccess { + function memberAccess(x:self) returns (memberValueType); } -class self:CStructField(fieldType, offsetType) {} -data StructField(structType, fieldSelector) = StructField(structType); +trait CStructField {} +enum StructField { StructField(structType) } -forall structType fieldSelector fieldType offsetType - . StructField(structType, fieldSelector):CStructField(fieldType, offsetType) - , offsetType:MemorySize - => instance MemberAccessProxy(memory(structType), fieldSelector, offsetType):LValueMemberAccess(memoryRef(fieldType)) { - function memberAccess(x:MemberAccessProxy(memory(structType), fieldSelector, offsetType)) -> memoryRef(fieldType) { +impl LValueMemberAccess, memoryRef> where StructField: CStructField, offsetType: MemorySize { + function memberAccess(x:MemberAccessProxy) returns (memoryRef) { let ptr:word = Typedef.rep(memberAccessD1(x)); - let size:word = MemorySize.size(Proxy:Proxy(offsetType)); + let size:word = MemorySize.size(Proxy as Proxy); assembly { ptr := add(ptr, size) } @@ -174,27 +169,26 @@ forall structType fieldSelector fieldType offsetType } } -instance ():MemorySize { - function size(x:Proxy(())) -> word { +impl MemorySize<()> { + function size(x:Proxy<()>) returns (word) { return 0; } } -instance word:MemorySize { - function size(x:Proxy(word)) -> word { +impl MemorySize { + function size(x:Proxy) returns (word) { return 32; } } -instance uint:MemorySize { - function size(x:Proxy(uint)) -> word { +impl MemorySize { + function size(x:Proxy) returns (word) { return 32; } } -forall a -. instance memory(a):MemorySize { - function size(x:Proxy(memory(a))) -> word { +impl MemorySize { + function size(x:Proxy) returns (word) { return 32; } } @@ -210,10 +204,10 @@ forall a b . a:Typedef(b), b:MemorySize } */ -forall a b . a:MemorySize, b:MemorySize => instance (a,b):MemorySize { - function size(x:Proxy((a,b))) -> word { - let a_sz:word = MemorySize.size(Proxy:Proxy(a)); - let b_sz:word = MemorySize.size(Proxy:Proxy(b)); +impl MemorySize<(a, b)> where a: MemorySize, b: MemorySize { + function size(x:Proxy<(a, b)>) returns (word) { + let a_sz:word = MemorySize.size(Proxy as Proxy); + let b_sz:word = MemorySize.size(Proxy as Proxy); assembly { a_sz := add(a_sz, b_sz) } @@ -221,55 +215,51 @@ forall a b . a:MemorySize, b:MemorySize => instance (a,b):MemorySize { } } -forall structType fieldSelector fieldType offsetType - . StructField(structType, fieldSelector):CStructField(fieldType, offsetType) - , fieldType:MemoryType - , offsetType:MemorySize - => instance MemberAccessProxy(memory(structType), fieldSelector, offsetType):RValueMemberAccess(fieldType) { - function memberAccess(x:MemberAccessProxy(memory(structType), fieldSelector, offsetType)) -> fieldType { +impl RValueMemberAccess, fieldType> where StructField: CStructField, fieldType: MemoryType, offsetType: MemorySize { + function memberAccess(x:MemberAccessProxy) returns (fieldType) { let ptr:word = Typedef.rep(memberAccessD1(x)); - let size:word = MemorySize.size(Proxy:Proxy(offsetType)); + let size:word = MemorySize.size(Proxy as Proxy); assembly { ptr := add(ptr, size) } - return MemoryType.load(ptr):fieldType; + return MemoryType.load(ptr) as fieldType; } } ////// Testing // struct S { fld1:uint; fld2:word; fld3:word } -data S = S; // (uint, word, word); +enum S { S } // (uint, word, word); // struct W { flds : memory(W) } -data W = W; +enum W { W } -data fld1_sel = fld1_sel; -data fld2_sel = fld2_sel; -data fld3_sel = fld3_sel; +enum fld1_sel { fld1_sel } +enum fld2_sel { fld2_sel } +enum fld3_sel { fld3_sel } -data flds_sel = flds_sel; +enum flds_sel { flds_sel } // form: // instance StructField(S, f_sel):CStructField(ftype, preceding)) {} -instance StructField(S, fld1_sel):CStructField(uint, ()) {} -instance StructField(S, fld2_sel):CStructField(word, uint) {} -instance StructField(S, fld3_sel):CStructField(word, (uint, word)) {} +impl CStructField, uint, ()> {} +impl CStructField, word, uint> {} +impl CStructField, word, (uint, word)> {} -instance StructField(W, flds_sel):CStructField(memory(S), ()) {} +impl CStructField, S memory, ()> {} -function makeS() -> memory(S) { - let s:memory(S) = Typedef.abs(0x80); - let fld1_map : MemberAccessProxy(memory(S), fld1_sel, ()) = MemberAccessProxy(s, fld1_sel); - let fld2_map : MemberAccessProxy(memory(S), fld2_sel, uint) = MemberAccessProxy(s, fld2_sel); +function makeS() returns (S memory) { + let s:S memory = Typedef.abs(0x80); + let fld1_map : MemberAccessProxy = MemberAccessProxy(s, fld1_sel); + let fld2_map : MemberAccessProxy = MemberAccessProxy(s, fld2_sel); let fld3_map = MemberAccessProxy(s, fld3_sel) - : MemberAccessProxy(memory(S), fld3_sel, (uint,word)); + as MemberAccessProxy; // let y:word = 13; let z:uint = uint(13); // s.fld1 = z - let fld1_lval : memoryRef(uint) + let fld1_lval : memoryRef = LValueMemberAccess.memberAccess(fld1_map ); Assign.assign(fld1_lval, z); @@ -285,12 +275,12 @@ function makeS() -> memory(S) { return s; } -function readS(s:memory(S)) -> word { - let s:memory(S) = Typedef.abs(0x80); - let fld1_map : MemberAccessProxy(memory(S), fld1_sel, ()) = MemberAccessProxy(s, fld1_sel); - let fld2_map : MemberAccessProxy(memory(S), fld2_sel, uint) = MemberAccessProxy(s, fld2_sel); +function readS(s:S memory) returns (word) { + let s:S memory = Typedef.abs(0x80); + let fld1_map : MemberAccessProxy = MemberAccessProxy(s, fld1_sel); + let fld2_map : MemberAccessProxy = MemberAccessProxy(s, fld2_sel); let fld3_map = MemberAccessProxy(s, fld3_sel) - : MemberAccessProxy(memory(S), fld3_sel, (uint,word)); + as MemberAccessProxy; // let f1 = s.fld1 let f1 : uint; @@ -303,41 +293,41 @@ function readS(s:memory(S)) -> word { let f3 : word; f3 = RValueMemberAccess.memberAccess(fld3_map); - let f12 = add(Typedef.rep(f1) : word, f2); + let f12 = add(Typedef.rep(f1) as word, f2); let f123 = add(f12, f3); return f123; } -function rwS() -> word { - let s:memory(S) = makeS(); +function rwS() returns (word) { + let s:S memory = makeS(); return readS(s); } -function makeW(s:memory(S)) -> memory(W) { - let w:memory(W) = Typedef.abs(0xe0); - let flds_map : MemberAccessProxy(memory(W), flds_sel, ()) = MemberAccessProxy(w, flds_sel); +function makeW(s:S memory) returns (W memory) { + let w:W memory = Typedef.abs(0xe0); + let flds_map : MemberAccessProxy = MemberAccessProxy(w, flds_sel); // w.flds = s - let flds_lval : memoryRef(memory(S)) + let flds_lval : memoryRef = LValueMemberAccess.memberAccess(flds_map ); Assign.assign(flds_lval, s); return w; } -function readW(w:memory(W)) -> memory(S) { - let flds_map : MemberAccessProxy(memory(W), flds_sel, ()) = MemberAccessProxy(w, flds_sel); +function readW(w:W memory) returns (S memory) { + let flds_map : MemberAccessProxy = MemberAccessProxy(w, flds_sel); return RValueMemberAccess.memberAccess(flds_map); } contract C { - public function main() { - let s:memory(S) = makeS(); - let w:memory(W) = makeW(s); - let s2:memory(S) = readW(w); + function main() public { + let s:S memory = makeS(); + let w:W memory = makeW(s); + let s2:S memory = readW(w); return readS(s2); } } diff --git a/test/examples/spec/10negBool.solc b/test/examples/spec/10negBool.solc index af8297a97..a0802b327 100644 --- a/test/examples/spec/10negBool.solc +++ b/test/examples/spec/10negBool.solc @@ -1,29 +1,29 @@ -forall a . class a : Neg { - function neg(x:a) -> a; +trait Neg { + function neg(x:a) returns (a); } -data B = F | T; +enum B { F, T } -instance B : Neg { - function neg (x : B) -> B { - match x { - | B.F => return B.T; - | B.T => return B.F; - } +impl Neg { + function neg (x : B) returns (B) { + match (x ) { + case B.F { return B.T; + } case B.T { return B.F; + } } } } contract NegBool { - public function fromB(b : B) -> word { - match b { - | B.F => return 0; - | B.T => return 1; - } + function fromB(b : B) public returns (word) { + match (b ) { + case B.F { return 0; + } case B.T { return 1; + } } } - public function main() -> word { return fromB(Neg.neg(B.F)); } + function main() public returns (word) { return fromB(Neg.neg(B.F)); } } diff --git a/test/examples/spec/111storageStruct.solc b/test/examples/spec/111storageStruct.solc index a65ae96c0..8a08c7cf2 100644 --- a/test/examples/spec/111storageStruct.solc +++ b/test/examples/spec/111storageStruct.solc @@ -10,13 +10,13 @@ function add(x : word, y : word) { } /////// Construction -class abs:Typedef(rep) { - function rep(x:abs) -> rep; - function abs(x:rep) -> abs; +trait Typedef { + function rep(x:abs) returns (rep); + function abs(x:rep) returns (abs); } -data uint = uint(word); +enum uint { uint(word) } // this does not work :( /* @@ -27,44 +27,44 @@ forall a } */ -instance uint:Typedef(word) { - function rep(x:uint) -> word { - match x { - | uint(y) => return y; - } +impl Typedef { + function rep(x:uint) returns (word) { + match (x ) { + case uint(y) { return y; + } } } - function abs(x:word) -> uint { + function abs(x:word) returns (uint) { return uint(x); } } -data storage(a) = storage(word); -data storageRef(a) = storageRef(word); -data Proxy(a) = Proxy; +enum storage { storage(word) } +enum storageRef { storageRef(word) } +enum Proxy { Proxy } -instance storage(a):Typedef(word) { - function rep(x:storage(a)) -> word { - match x { - | storage(y) => return y; - } +impl Typedef { + function rep(x:a storage) returns (word) { + match (x ) { + case storage(y) { return y; + } } } - function abs(x:word) -> storage(a) { + function abs(x:word) returns (a storage) { return storage(x); } } -instance storageRef(a):Typedef(word) { - function rep(x:storageRef(a)) -> word { - match x { - | storageRef(y) => return y; - } +impl Typedef, word> { + function rep(x:storageRef) returns (word) { + match (x ) { + case storageRef(y) { return y; + } } } - function abs(x:word) -> storageRef(a) { + function abs(x:word) returns (storageRef) { return storageRef(x); } } -class lhs:Assign(rhs) { - function assign(l:lhs, r:rhs) -> (); +trait Assign { + function assign(l:lhs, r:rhs) returns (()); } /* @@ -78,17 +78,17 @@ instance ref(a):Assign(a) { } */ -class self:StorageType { - function sload(ptr:word) -> self; - function store(ptr:word, value:self) -> (); +trait StorageType { + function sload(ptr:word) returns (self); + function store(ptr:word, value:self) returns (()); } -class self:StorageSize { - function size(x:Proxy(self)) -> word; +trait StorageSize { + function size(x:Proxy) returns (word); } -function sload_(x:word) -> word { +function sload_(x:word) returns (word) { let res: word; assembly { res := sload(x) @@ -100,66 +100,61 @@ function sstore_(a:word, v:word) { assembly { sstore(a,v) } } -instance word:StorageType { - function sload(ptr:word) -> word { +impl StorageType { + function sload(ptr:word) returns (word) { let r:word; assembly { r := sload(ptr) } return r; } - function store(ptr:word, value:word) -> () { + function store(ptr:word, value:word) returns (()) { assembly { sstore(ptr, value) } } } -instance uint:StorageType { - function sload(ptr:word) -> uint { - return Typedef.abs(sload_(ptr)):uint; // type annotation needed due to a typechecker bug +impl StorageType { + function sload(ptr:word) returns (uint) { + return Typedef.abs(sload_(ptr)) as uint; // type annotation needed due to a typechecker bug } - function store(ptr:word, value:uint) -> () { + function store(ptr:word, value:uint) returns (()) { return sstore_(ptr, Typedef.rep(value)); } } -forall a . a : StorageType => instance storageRef(a):Assign(a) { - function assign(l:storageRef(a), y:a) { +impl Assign, a> where a: StorageType { + function assign(l:storageRef, y:a) { StorageType.store(Typedef.rep(l), y); } } -data MemberAccessProxy(a, field, offset) = MemberAccessProxy(a, field); +enum MemberAccessProxy { MemberAccessProxy(a, field) } -forall a field offset . -function memberAccessD1(x:MemberAccessProxy(a, field, offset)) -> a { - match x { - | MemberAccessProxy(y,z) => return y; - } +function memberAccessD1(x:MemberAccessProxy) returns (a) { + match (x ) { + case MemberAccessProxy(y,z) { return y; + } } } -class self:LValueMemberAccess(memberRefType) { - function memberAccess(x:self) -> memberRefType; +trait LValueMemberAccess { + function memberAccess(x:self) returns (memberRefType); } -forall self memberValueType . -class self:RValueMemberAccess(memberValueType) { - function memberAccess(x:self) -> memberValueType; +trait RValueMemberAccess { + function memberAccess(x:self) returns (memberValueType); } -class self:CStructField(fieldType, offsetType) {} -data StructField(structType, fieldSelector) = StructField(structType); +trait CStructField {} +enum StructField { StructField(structType) } -forall structType fieldSelector fieldType offsetType - . StructField(structType, fieldSelector):CStructField(fieldType, offsetType) - , offsetType:StorageSize - => instance MemberAccessProxy(storage(structType), fieldSelector, offsetType):LValueMemberAccess(storageRef(fieldType)) { - function memberAccess(x:MemberAccessProxy(storage(structType), fieldSelector, offsetType)) -> storageRef(fieldType) { +impl LValueMemberAccess, storageRef> where StructField: CStructField, offsetType: StorageSize { + function memberAccess(x:MemberAccessProxy) returns (storageRef) { let ptr:word = Typedef.rep(memberAccessD1(x)); - let size:word = StorageSize.size(Proxy:Proxy(offsetType)); + let size:word = StorageSize.size(Proxy as Proxy); assembly { ptr := add(ptr, size) } @@ -167,20 +162,20 @@ forall structType fieldSelector fieldType offsetType } } -instance ():StorageSize { - function size(x:Proxy(())) -> word { +impl StorageSize<()> { + function size(x:Proxy<()>) returns (word) { return 0; } } -instance word:StorageSize { - function size(x:Proxy(word)) -> word { +impl StorageSize { + function size(x:Proxy) returns (word) { return 1; } } -instance uint:StorageSize { - function size(x:Proxy(uint)) -> word { +impl StorageSize { + function size(x:Proxy) returns (word) { return 1; } } @@ -196,10 +191,10 @@ forall a b . a:Typedef(b), b:StorageSize } */ -forall a b . a:StorageSize, b:StorageSize => instance (a,b):StorageSize { - function size(x:Proxy((a,b))) -> word { - let a_sz:word = StorageSize.size(Proxy:Proxy(a)); - let b_sz:word = StorageSize.size(Proxy:Proxy(b)); +impl StorageSize<(a, b)> where a: StorageSize, b: StorageSize { + function size(x:Proxy<(a, b)>) returns (word) { + let a_sz:word = StorageSize.size(Proxy as Proxy); + let b_sz:word = StorageSize.size(Proxy as Proxy); assembly { a_sz := add(a_sz, b_sz) } @@ -207,18 +202,14 @@ forall a b . a:StorageSize, b:StorageSize => instance (a,b):StorageSize { } } -forall structType fieldSelector fieldType offsetType - . StructField(structType, fieldSelector):CStructField(fieldType, offsetType) - , fieldType:StorageType - , offsetType:StorageSize - => instance MemberAccessProxy(storage(structType), fieldSelector, offsetType):RValueMemberAccess(fieldType) { - function memberAccess(x:MemberAccessProxy(storage(structType), fieldSelector, offsetType)) -> fieldType { +impl RValueMemberAccess, fieldType> where StructField: CStructField, fieldType: StorageType, offsetType: StorageSize { + function memberAccess(x:MemberAccessProxy) returns (fieldType) { let ptr:word = Typedef.rep(memberAccessD1(x)); - let size:word = StorageSize.size(Proxy:Proxy(offsetType)); + let size:word = StorageSize.size(Proxy as Proxy); assembly { ptr := add(ptr, size) } - return StorageType.sload(ptr):fieldType; + return StorageType.sload(ptr) as fieldType; } } @@ -227,30 +218,30 @@ forall structType fieldSelector fieldType offsetType ////// Testing // struct S { fld1:uint; fld2:word; fld3:word } -data S = S; // (uint, word, word); -data fld1_sel = fld1_sel; -data fld2_sel = fld2_sel; -data fld3_sel = fld3_sel; +enum S { S } // (uint, word, word); +enum fld1_sel { fld1_sel } +enum fld2_sel { fld2_sel } +enum fld3_sel { fld3_sel } // form: // instance StructField(S, f_sel):CStructField(ftype, preceding)) {} -instance StructField(S, fld1_sel):CStructField(uint, ()) {} -instance StructField(S, fld2_sel):CStructField(word, uint) {} -instance StructField(S, fld3_sel):CStructField(word, (uint, word)) {} +impl CStructField, uint, ()> {} +impl CStructField, word, uint> {} +impl CStructField, word, (uint, word)> {} -function g() -> word { - let s:storage(S) = Typedef.abs(0x80); - let fld1_map : MemberAccessProxy(storage(S), fld1_sel, ()) = MemberAccessProxy(s, fld1_sel); - let fld2_map : MemberAccessProxy(storage(S), fld2_sel, uint) = MemberAccessProxy(s, fld2_sel); +function g() returns (word) { + let s:S storage = Typedef.abs(0x80); + let fld1_map : MemberAccessProxy = MemberAccessProxy(s, fld1_sel); + let fld2_map : MemberAccessProxy = MemberAccessProxy(s, fld2_sel); let fld3_map = MemberAccessProxy(s, fld3_sel) - : MemberAccessProxy(storage(S), fld3_sel, (uint,word)); + as MemberAccessProxy; // let y:word = 13; let z:uint = uint(13); // s.fld1 = z - let fld1_lval : storageRef(uint) + let fld1_lval : storageRef = LValueMemberAccess.memberAccess(fld1_map ); Assign.assign(fld1_lval, z); @@ -274,7 +265,7 @@ function g() -> word { let f3 : word; f3 = RValueMemberAccess.memberAccess(fld3_map); - let f12 = add(Typedef.rep(f1) : word, f2); + let f12 = add(Typedef.rep(f1) as word, f2); let f123 = add(f12, f3); return f123; @@ -282,7 +273,7 @@ function g() -> word { } contract C { - public function main() { + function main() public { return g(); } } diff --git a/test/examples/spec/112ContractStorage.solc b/test/examples/spec/112ContractStorage.solc index f672661a2..02e1edf60 100644 --- a/test/examples/spec/112ContractStorage.solc +++ b/test/examples/spec/112ContractStorage.solc @@ -16,16 +16,16 @@ contract Counter { // form: // instance StructField(S, f_sel):CStructField(ftype, preceding)) {} -data CounterCxt = CounterCxt; -data counter_sel = counter_sel; -instance StructField(ContractStorage(CounterCxt), counter_sel):CStructField(word, ()) {} +enum CounterCxt { CounterCxt } +enum counter_sel { counter_sel } +impl CStructField, counter_sel>, word, ()> {} contract Counter { // struct CounterCxt { counter:word } - public function main() -> word { - let cxt : ContractStorage(CounterCxt) = ContractStorage(CounterCxt); - let counter_map : MemberAccessProxy(ContractStorage(CounterCxt), counter_sel, ()) + function main() public returns (word) { + let cxt : ContractStorage = ContractStorage(CounterCxt); + let counter_map : MemberAccessProxy, counter_sel, ()> = MemberAccessProxy(cxt, counter_sel); // let c1 = this.counter diff --git a/test/examples/spec/113counter.solc b/test/examples/spec/113counter.solc index 7fd85e4bc..8d2ecc74c 100644 --- a/test/examples/spec/113counter.solc +++ b/test/examples/spec/113counter.solc @@ -11,11 +11,11 @@ contract Counter { } */ -data counter_sel = counter_sel; -instance StructField(ContractStorage(()), counter_sel):CStructField(word, ()) {} +enum counter_sel { counter_sel } +impl CStructField, counter_sel>, word, ()> {} contract Counter { - public function main () -> word { + function main () public returns (word) { let counter_map /*: MemberAccessProxy(ContractStorage(()), counter_sel, ()) */ = MemberAccessProxy(ContractStorage(()), counter_sel); Assign.assign(LValueMemberAccess.memberAccess(MemberAccessProxy(ContractStorage(()), counter_sel)), add(rval(counter_map), 1)); return rval(counter_map); diff --git a/test/examples/spec/11negPair.solc b/test/examples/spec/11negPair.solc index c18c0272b..8e3329ff4 100644 --- a/test/examples/spec/11negPair.solc +++ b/test/examples/spec/11negPair.solc @@ -1,53 +1,53 @@ -forall a . class a : Neg { - function neg(x:a) -> a; +trait Neg { + function neg(x:a) returns (a); } -data B = F | T; +enum B { F, T } -instance B : Neg { - function neg (x : B) -> B { - match x { - | B.F => return B.T; - | B.T => return B.F; - } +impl Neg { + function neg (x : B) returns (B) { + match (x ) { + case B.F { return B.T; + } case B.T { return B.F; + } } } } -forall a b . function fst (p : (a, b)) -> a { - match p { - | (x,y) => return x; - } +function fst (p : (a, b)) returns (a) { + match (p ) { + case (x,y) { return x; + } } } -forall a b . function snd(p : (a, b)) -> b { - match p { - | (x,y) => return y; - } +function snd(p : (a, b)) returns (b) { + match (p ) { + case (x,y) { return y; + } } } -forall a b . a : Neg, b : Neg => instance (a,b):Neg { - function neg(p : (a,b)) -> (a,b) { +impl Neg<(a, b)> where a: Neg, b: Neg { + function neg(p : (a, b)) returns ((a, b)) { return (Neg.neg (fst(p)), Neg.neg(snd (p))); } } contract NegPair { - public function bnot(x : B) -> B { - match x { - | B.T => return B.F; - | B.F => return B.T; - } + function bnot(x : B) public returns (B) { + match (x ) { + case B.T { return B.F; + } case B.F { return B.T; + } } } - public function fromB(b : B) -> word { - match b { - | B.F => return 0; - | B.T => return 1; - } + function fromB(b : B) public returns (word) { + match (b ) { + case B.F { return 0; + } case B.T { return 1; + } } } - public function main() -> word { return fromB(fst(Neg.neg((B.F,B.T)))); } + function main() public returns (word) { return fromB(fst(Neg.neg((B.F,B.T)))); } } diff --git a/test/examples/spec/120basicCounter.solc b/test/examples/spec/120basicCounter.solc index 026e1e032..82e52f47f 100644 --- a/test/examples/spec/120basicCounter.solc +++ b/test/examples/spec/120basicCounter.solc @@ -1,8 +1,8 @@ -import std.{*}; +import {*} from std; contract Counter { counter : word; - public function main() -> word { + function main() public returns (word) { counter = Num.add(counter, 42); return counter; } diff --git a/test/examples/spec/121counter.solc b/test/examples/spec/121counter.solc index 2908b6ef5..e404a9542 100644 --- a/test/examples/spec/121counter.solc +++ b/test/examples/spec/121counter.solc @@ -1,13 +1,13 @@ // test single contract field import std; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; contract Counter { counter : word; - public function main() -> word { + function main() public returns (word) { counter = std.addWord(counter, 1); return counter; } diff --git a/test/examples/spec/122counters.solc b/test/examples/spec/122counters.solc index 4b13c41db..c210740ba 100644 --- a/test/examples/spec/122counters.solc +++ b/test/examples/spec/122counters.solc @@ -1,5 +1,5 @@ // test multiple contract fields -import std.{*}; +import {*} from std; // import StorageLib; @@ -7,7 +7,7 @@ contract Counter { counter1 : word; counter2 : uint256; counter3 : word; - public function main() -> word { + function main() public returns (word) { counter1 += 1; counter3 += 2; return counter1 + counter3; diff --git a/test/examples/spec/123stackAndStorage.solc b/test/examples/spec/123stackAndStorage.solc index 8da859fac..dd1415ac3 100644 --- a/test/examples/spec/123stackAndStorage.solc +++ b/test/examples/spec/123stackAndStorage.solc @@ -1,12 +1,12 @@ // test multiple contract fields -import std.{*}; +import {*} from std; contract Counter { counter1 : word; counter2 : uint256; counter3 : word; - public function main() -> word { + function main() public returns (word) { let x: word; x = counter1 + 1; counter1 = x; diff --git a/test/examples/spec/126nanoerc20.solc b/test/examples/spec/126nanoerc20.solc index 865965a7a..aa02db060 100644 --- a/test/examples/spec/126nanoerc20.solc +++ b/test/examples/spec/126nanoerc20.solc @@ -1,10 +1,10 @@ -import std.{*}; -import std.{address, uint256, mapping, Num, Add, Sub, Bounded, Eq, Ord, Typedef, ge, not}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +import {*} from std; +import {address, uint256, mapping, Num, Add, Sub, Bounded, Eq, Ord, Typedef, ge, not} from std; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; -function caller() -> address { +function caller() returns (address) { let res: word; assembly { res := caller() @@ -12,24 +12,24 @@ function caller() -> address { return address(res); } -function myrevert( msg: (word, word) ) -> () { - match msg { - | (str, len) => +function myrevert( msg: (word, word) ) returns (()) { + match (msg ) { + case (str, len) { let str1 = str; let len1 = len; assembly { mstore(0, str1) revert(0, len1) } - } + } } } -function myrequire(cond: bool, msg: (word, word) ) -> () { +function myrequire(cond: bool, msg: (word, word) ) returns (()) { if( not(cond) ) { myrevert(msg); } } -function require1(cond: bool) -> () { +function require1(cond: bool) returns (()) { myrequire (cond, (0x72657175697265313a204641494c, 14) /* "require1: FAIL" */ ); } -function nop() -> () { return ();} +function nop() returns (()) { return;} contract Uint { reserved : word; @@ -37,15 +37,15 @@ contract Uint { owner : address; decimals : uint256; totalSupply : uint256; - balances : mapping(address,uint256); + balances : mapping(address => uint256); - public function mint(amount:uint256) -> () { + function mint(amount:uint256) public returns (()) { balances[owner] = Num.add(balances[owner], amount); totalSupply = Num.add(totalSupply, amount); } // function transferFrom(address src, address dst, uint256 amt) public returns (bool) - public function transferFrom(src:address, dst:address, amt:uint256) -> bool { + function transferFrom(src:address, dst:address, amt:uint256) public returns (bool) { require1(ge(balances[src], amt)); /* @@ -58,26 +58,26 @@ contract Uint { } - public function withdraw(src:address, amt:uint256) -> () { - balances[src] = Num.sub(balances[src], amt):uint256; + function withdraw(src:address, amt:uint256) public returns (()) { + balances[src] = Num.sub(balances[src], amt) as uint256; } - public function deposit(dst:address, amt:uint256) -> () { - balances[dst] = Num.add(balances[dst], amt):uint256; + function deposit(dst:address, amt:uint256) public returns (()) { + balances[dst] = Num.add(balances[dst], amt) as uint256; } - public function init() -> () { + function init() public returns (()) { owner = address(0x123456789abcdef); msg_sender = caller(); decimals = uint256(18); } - public function main() -> uint256 { + function main() public returns (uint256) { init(); mint(uint256(1000)); let src : address = owner; transferFrom(owner, msg_sender, uint256(42)); - return balances[msg_sender] : uint256; + return balances[msg_sender] as uint256; } } diff --git a/test/examples/spec/127microerc20.solc b/test/examples/spec/127microerc20.solc index 339205819..7dfab94ff 100644 --- a/test/examples/spec/127microerc20.solc +++ b/test/examples/spec/127microerc20.solc @@ -1,10 +1,10 @@ -import std.{*}; -import std.{address, uint256, mapping, Num, Add, Sub, Bounded, Eq, Ord, Typedef, ge, ne, not}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +import {*} from std; +import {address, uint256, mapping, Num, Add, Sub, Bounded, Eq, Ord, Typedef, ge, ne, not} from std; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; -function caller() -> address { +function caller() returns (address) { let res: word; assembly { res := caller() @@ -12,23 +12,23 @@ function caller() -> address { return address(res); } -function require1fail() -> () { +function require1fail() returns (()) { let res: word; assembly { mstore(0x0, 0x72657175697265313a204641494c) // "require1: FAIL" revert(0,32) } - return (); // for the typechecker + return; // for the typechecker } -function require1(cond: bool) -> () { - match cond { - | false => return require1fail(); - | true => return (); - } +function require1(cond: bool) returns (()) { + match (cond ) { + case false { return require1fail(); + } case true { return; + } } } -function nop() -> () { return ();} +function nop() returns (()) { return;} contract Mini { reserved : word; @@ -36,10 +36,10 @@ contract Mini { owner : address; decimals : uint256; totalSupply : uint256; - balances : mapping(address,uint256); - allowance : mapping(address, mapping(address, uint256)); + balances : mapping(address => uint256); + allowance : mapping(address => mapping(address => uint256)); - public function mint(amount:uint256) -> () { + function mint(amount:uint256) public returns (()) { balances[owner] = Num.add(balances[owner], amount); totalSupply = Num.add(totalSupply, amount); } @@ -60,16 +60,16 @@ contract Mini { */ // function transferFrom(src:address, dst:address, amt:uint256) -> bool { - public function transferFrom(src : address, dst : address, amt : uint256) -> bool { + function transferFrom(src : address, dst : address, amt : uint256) public returns (bool) { require1(ge(balances[src], amt)); match (Eq.eq(src, msg_sender)) { - | true => match ne(allowance[src][msg_sender], Num.maxVal():uint256) { - | true => require1(false); - | false => (); - } - | false => (); - } + case true { match (ne(allowance[src][msg_sender], Num.maxVal() as uint256) ) { + case true { require1(false); + } case false { (); + } } + } case false { (); + } } /* if ((src != msg_sender) && (allowance [src][msg_sender] != (Num.maxVal():uint256)) ) { @@ -77,7 +77,7 @@ contract Mini { } */ balances[src] = Num.sub(balances[src], amt); - balances[dst] = Num.add(balances[dst], amt):uint256; + balances[dst] = Num.add(balances[dst], amt) as uint256; return true; } @@ -90,18 +90,18 @@ contract Mini { */ - public function init() -> () { + function init() public returns (()) { owner = address(0x123456789abcdef); msg_sender = caller(); decimals = uint256(18); } - public function main() -> uint256 { + function main() public returns (uint256) { init(); mint(uint256(1000)); allowance[owner][msg_sender] = uint256(10000); transferFrom(owner, msg_sender, uint256(42)); - return balances[msg_sender] : uint256; + return balances[msg_sender] as uint256; } } diff --git a/test/examples/spec/128minierc20.solc b/test/examples/spec/128minierc20.solc index a3c21a15e..c4e484fe6 100644 --- a/test/examples/spec/128minierc20.solc +++ b/test/examples/spec/128minierc20.solc @@ -1,10 +1,10 @@ -import std.{*}; -import std.{address, uint256, mapping, Num, Add, Sub, Bounded, Eq, Ord, Typedef, ge, ne, not}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +import {*} from std; +import {address, uint256, mapping, Num, Add, Sub, Bounded, Eq, Ord, Typedef, ge, ne, not} from std; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; -function caller() -> address { +function caller() returns (address) { let res: word; assembly { res := caller() @@ -12,11 +12,11 @@ function caller() -> address { return address(res); } -function myrevert(msg: word) -> () { +function myrevert(msg: word) returns (()) { assembly { mstore(0, msg) revert(0, 32) } } -function myrequire(cond: bool, msg: word ) -> () { +function myrequire(cond: bool, msg: word ) returns (()) { if( !cond ) { myrevert(msg); } } @@ -25,10 +25,10 @@ contract MiniERC20 { owner : address; decimals : uint256; totalSupply : uint256; - balances : mapping(address,uint256); - allowance : mapping(address, mapping(address, uint256)); + balances : mapping(address => uint256); + allowance : mapping(address => mapping(address => uint256)); - public function mint(amount:uint256) -> () { + function mint(amount:uint256) public returns (()) { balances[owner] = Num.add(balances[owner], amount); totalSupply = Num.add(totalSupply, amount); } @@ -48,13 +48,13 @@ contract MiniERC20 { } */ - public function transferFrom(src:address, dst:address, amt:uint256) -> bool { + function transferFrom(src:address, dst:address, amt:uint256) public returns (bool) { let msg_sender = caller(); myrequire( balances[src] >= amt /* "token/insufficient-balance" */ , 0x746f6b656e2f696e73756666696369656e742d62616c616e6365 ); - if (src != msg_sender && allowance[src][msg_sender] != (Num.maxVal():uint256)) { + if (src != msg_sender && allowance[src][msg_sender] != (Num.maxVal() as uint256)) { myrequire( allowance[src][msg_sender] >= amt /* "token/insufficient-allowance" */ , 0x746f6b656e2f696e73756666696369656e742d616c6c6f77616e6365 ); @@ -73,7 +73,7 @@ contract MiniERC20 { } */ - public function approve(usr: address, amt: uint256) -> bool { + function approve(usr: address, amt: uint256) public returns (bool) { let msg_sender = caller(); allowance[msg_sender][usr] = amt; // emit Approval(msg.sender, usr, amt); @@ -81,12 +81,12 @@ contract MiniERC20 { } - public function init() -> () { + function init() public returns (()) { owner = address(0x123456789abcdef); decimals = uint256(18); // Num.fromWord(18) fails, which may be a problem } - public function main() -> uint256 { + function main() public returns (uint256) { let msg_sender = caller(); init(); mint(uint256(1000)); diff --git a/test/examples/spec/129arraystorage.solc b/test/examples/spec/129arraystorage.solc index 41ff13b97..3fe8b0ace 100644 --- a/test/examples/spec/129arraystorage.solc +++ b/test/examples/spec/129arraystorage.solc @@ -1,16 +1,16 @@ // Exercises storage arrays (array(member)) modeled on storage mappings. -import std.{*}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +import {*} from std; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; contract ArrayStorage { reserved : word; // forge uses at least 1 storage slot - function main() -> uint256 { + function main() returns (uint256) { // A storage array sitting at a fixed slot. The slot itself stores the // length; elements live at keccak256(slot) + i. - let arr : storage(array(uint256)) = storage(0x100); + let arr : uint256[] storage = storage(0x100); // push appends and grows the length automatically. ArrayPush.push(arr, uint256(42)); diff --git a/test/examples/spec/130arrayfield.solc b/test/examples/spec/130arrayfield.solc index 2a8f27e27..9e24f901e 100644 --- a/test/examples/spec/130arrayfield.solc +++ b/test/examples/spec/130arrayfield.solc @@ -1,14 +1,14 @@ // Storage array as a contract field: `arr : array(uint256)`. -import std.{*}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +import {*} from std; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; contract ArrayField { reserved : word; // forge uses at least 1 storage slot - arr : array(uint256); + arr : uint256[]; - function main() -> uint256 { + function main() returns (uint256) { // push appends and grows the length automatically. ArrayPush.push(arr, uint256(42)); ArrayPush.push(arr, uint256(100)); diff --git a/test/examples/spec/131constructor.solc b/test/examples/spec/131constructor.solc index 4e0381b90..ab26a3ff6 100644 --- a/test/examples/spec/131constructor.solc +++ b/test/examples/spec/131constructor.solc @@ -2,13 +2,13 @@ contract Counter { - public function setCounter(v: word) { + function setCounter(v: word) public { assembly { sstore(0x00, v) } } - public function getCounter() -> word { + function getCounter() public returns (word) { let res; assembly { res := sload(0x00) @@ -21,7 +21,7 @@ contract Counter { setCounter(42); } - public function main() -> word { + function main() public returns (word) { return getCounter(); } } diff --git a/test/examples/spec/131localindex.solc b/test/examples/spec/131localindex.solc index 98122c2e0..3c128410b 100644 --- a/test/examples/spec/131localindex.solc +++ b/test/examples/spec/131localindex.solc @@ -2,16 +2,16 @@ // The local already holds the storage reference, so the desugaring emits // `ridx(arr, i)` / `lidx(arr, i)` directly (cf. 129arraystorage.solc, which // had to spell out `ridx` by hand). -import std.{*}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +import {*} from std; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; contract LocalIndex { reserved : word; // forge uses at least 1 storage slot - function main() -> uint256 { - let arr : storage(array(uint256)) = storage(0x100); + function main() returns (uint256) { + let arr : uint256[] storage = storage(0x100); ArrayPush.push(arr, uint256(42)); ArrayPush.push(arr, uint256(100)); diff --git a/test/examples/spec/132nestedarray.solc b/test/examples/spec/132nestedarray.solc index 04abe1bee..35d0b8a4e 100644 --- a/test/examples/spec/132nestedarray.solc +++ b/test/examples/spec/132nestedarray.solc @@ -1,16 +1,16 @@ // Nested storage arrays: `array(array(uint256))` with `grid[i][j]` used as both // an l-value and an r-value. The inner index desugars as an l-value, yielding the // `storage(array(uint256))` handle that the outer index then consumes. -import std.{*}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +import {*} from std; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; contract NestedArray { reserved : word; // forge uses at least 1 storage slot - grid : array(array(uint256)); + grid : uint256[][]; - function main() -> uint256 { + function main() returns (uint256) { // Grow the outer array; the inner arrays start empty. Array.setLength(grid, uint256(2)); diff --git a/test/examples/spec/133arraystring.solc b/test/examples/spec/133arraystring.solc index bc55c6875..50f2beffb 100644 --- a/test/examples/spec/133arraystring.solc +++ b/test/examples/spec/133arraystring.solc @@ -1,17 +1,17 @@ // Storage arrays whose element type is dynamic. Declaring the field and taking // its length must work even before `push` accepts dynamic values; the element // slot itself is what holds the length / short-string encoding. -import std.{*}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +import {*} from std; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; contract ArrayOfDynamic { reserved : word; // forge uses at least 1 storage slot - names : array(string); - blobs : array(bytes); + names : string[]; + blobs : bytes[]; - function main() -> uint256 { + function main() returns (uint256) { return Array.length(names) + Array.length(blobs); } } diff --git a/test/examples/spec/135aliaspush.solc b/test/examples/spec/135aliaspush.solc index bb7127c0d..caaaa2aac 100644 --- a/test/examples/spec/135aliaspush.solc +++ b/test/examples/spec/135aliaspush.solc @@ -1,16 +1,16 @@ // Binding a storage array field to a local is an *alias*, not a copy: the local // holds the same slot, so growing it grows the field. (Solidity's `T[] storage p`.) -import std.{*}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +import {*} from std; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; contract AliasPush { reserved : word; // forge uses at least 1 storage slot - xs : array(uint256); + xs : uint256[]; - function main() -> uint256 { - let p : storage(array(uint256)) = xs; + function main() returns (uint256) { + let p : uint256[] storage = xs; ArrayPush.push(p, uint256(1)); // The push went through the alias, so the field sees it. return Array.length(xs); diff --git a/test/examples/spec/135cons3.solc b/test/examples/spec/135cons3.solc index 9e808a4c7..76876c040 100644 --- a/test/examples/spec/135cons3.solc +++ b/test/examples/spec/135cons3.solc @@ -1,10 +1,9 @@ // test constructor with multiple args -import std.{*}; +import {*} from std; // import prelude; -forall t.t:Typedef(word) => -function log1(v:t, topic:word) -> () { +function log1(v:t, topic:word) returns (()) where t: Typedef { let w : word = Typedef.rep(v); assembly { mstore(0,w) @@ -15,15 +14,15 @@ function log1(v:t, topic:word) -> () { contract Counter { // setCounter & getCounter are intentionally low-level to avoid clutter - public function setCounter(v: uint256) -> () { - match v { | uint256(w) => + function setCounter(v: uint256) public returns (()) { + match (v ) { case uint256(w) { assembly { sstore(0x00, w) } - } + } } } - public function getCounter() -> uint256 { + function getCounter() public returns (uint256) { let res; assembly { res := sload(0x00) @@ -85,12 +84,12 @@ contract Counter { return(0, size) |] */ - return (); + return; } */ // TODO: remove main, use dispatch instead - function main() -> uint256 { + function main() returns (uint256) { return getCounter(); } diff --git a/test/examples/spec/903badassign.solc b/test/examples/spec/903badassign.solc index d3efe69b8..ba798476d 100644 --- a/test/examples/spec/903badassign.solc +++ b/test/examples/spec/903badassign.solc @@ -1,27 +1,27 @@ contract Option { - data Option(a) = None | Some(a); + enum Option { None, Some(a) } - public function just(x : word) -> Option(word) { return Option.Some(x); } + function just(x : word) public returns (Option) { return Option.Some(x); } - public function maybe(n : word, o : Option(word)) -> word { - match o { - | Option.None => return n; - | Option.Some(x) => return x; - } + function maybe(n : word, o : Option) public returns (word) { + match (o ) { + case Option.None { return n; + } case Option.Some(x) { return x; + } } } - public function join(mmx : Option(Option(word))) -> Option(word) { + function join(mmx : Option>) public returns (Option) { let result = Option.None; - match mmx { - | Option.Some(Option.Some(x)) => result = Option.Some(x); - | Option.None => result = Option.None; - | Option.Some(Option.None) => result = Option.None; - | _ => result = Option.None; - } + match (mmx ) { + case Option.Some(Option.Some(x)) { result = Option.Some(x); + } case Option.None { result = Option.None; + } case Option.Some(Option.None) { result = Option.None; + } default { result = Option.None; + } } return result; } - public function main() -> word { + function main() public returns (word) { return maybe(0, join(Option.Some(Option.Some(42)))); } } diff --git a/test/examples/spec/939badfood.solc b/test/examples/spec/939badfood.solc index eb81d6b15..afa3eb5ad 100644 --- a/test/examples/spec/939badfood.solc +++ b/test/examples/spec/939badfood.solc @@ -1,21 +1,21 @@ -forall a . class a: Enum { - function fromEnum(x : a) -> word; +trait Enum { + function fromEnum(x : a) returns (word); } -data Food = Curry | Beans | Other; +enum Food { Curry, Beans, Other } -instance Food : Enum { - function fromEnum(x : Food) -> word { - match x { - | Food.Curry => return 1; - | Food.Beans => return 2; - | Food.Other => return 3; - } +impl Enum { + function fromEnum(x : Food) returns (word) { + match (x ) { + case Food.Curry { return 1; + } case Food.Beans { return 2; + } case Food.Other { return 3; + } } } } contract FoodContract { - public function main() -> word { + function main() public returns (word) { return Enum.fromEnum(Food.Beans); } } diff --git a/test/examples/spec/SimpleField.solc b/test/examples/spec/SimpleField.solc index 3aa1d3e49..992084417 100644 --- a/test/examples/spec/SimpleField.solc +++ b/test/examples/spec/SimpleField.solc @@ -1,16 +1,16 @@ -import std.{*}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; +import {*} from std; +pragma solcore noPattersonCondition ; +pragma solcore noCoverageCondition ; +pragma solcore noBoundVariableCondition ; contract Simple { myval : word ; - public function getVal () -> word { + function getVal () public returns (word) { return myval ; } - public function main () -> word { + function main () public returns (word) { return getVal(); } } diff --git a/test/examples/spec/StorageLib.solc b/test/examples/spec/StorageLib.solc index 9047f00a9..fcb282f2d 100644 --- a/test/examples/spec/StorageLib.solc +++ b/test/examples/spec/StorageLib.solc @@ -10,14 +10,13 @@ function add(x : word, y : word) { } /////// Construction -forall abs rep. -class abs:Typedef(rep) { - function rep(x:abs) -> rep; - function abs(x:rep) -> abs; +trait Typedef { + function rep(x:abs) returns (rep); + function abs(x:rep) returns (abs); } -data uint = uint(word); +enum uint { uint(word) } // this does not work :( /* @@ -28,75 +27,69 @@ forall a } */ -instance uint:Typedef(word) { - function rep(x:uint) -> word { - match x { - | uint(y) => return y; - } +impl Typedef { + function rep(x:uint) returns (word) { + match (x ) { + case uint(y) { return y; + } } } - function abs(x:word) -> uint { + function abs(x:word) returns (uint) { return uint(x); } } -data storage(a) = storage(word); -data ContractStorage(cxt) = ContractStorage(cxt); +enum storage { storage(word) } +enum ContractStorage { ContractStorage(cxt) } -data storageRef(a) = storageRef(word); -data Proxy(a) = Proxy; +enum storageRef { storageRef(word) } +enum Proxy { Proxy } -forall a. -instance storage(a):Typedef(word) { - function rep(x:storage(a)) -> word { - match x { - | storage(y) => return y; - } +impl Typedef { + function rep(x:a storage) returns (word) { + match (x ) { + case storage(y) { return y; + } } } - function abs(x:word) -> storage(a) { + function abs(x:word) returns (a storage) { return storage(x); } } -forall a. -instance storageRef(a):Typedef(word) { - function rep(x:storageRef(a)) -> word { - match x { - | storageRef(y) => return y; - } +impl Typedef, word> { + function rep(x:storageRef) returns (word) { + match (x ) { + case storageRef(y) { return y; + } } } - function abs(x:word) -> storageRef(a) { + function abs(x:word) returns (storageRef) { return storageRef(x); } } -forall lhs rhs. -class lhs:Assign(rhs) { - function assign(l:lhs, r:rhs) -> (); +trait Assign { + function assign(l:lhs, r:rhs) returns (()); } -data ref(a) = ref(a); +enum ref { ref(a) } -forall a. -instance ref(a):Assign(a) { - function assign(l:ref(a), r:a) -> () { +impl Assign, a> { + function assign(l:ref, r:a) returns (()) { // builtin "stack store" - return (); + return; } } -forall self. -class self:StorageType { - function sload(ptr:word) -> self; - function store(ptr:word, value:self) -> (); +trait StorageType { + function sload(ptr:word) returns (self); + function store(ptr:word, value:self) returns (()); } -forall self. -class self:StorageSize { - function size(x:Proxy(self)) -> word; +trait StorageSize { + function size(x:Proxy) returns (word); } -function sload_(x:word) -> word { +function sload_(x:word) returns (word) { let res: word; assembly { res := sload(x) @@ -108,68 +101,61 @@ function sstore_(a:word, v:word) { assembly { sstore(a,v) } } -instance word:StorageType { - function sload(ptr:word) -> word { +impl StorageType { + function sload(ptr:word) returns (word) { let r:word; assembly { r := sload(ptr) } return r; } - function store(ptr:word, value:word) -> () { + function store(ptr:word, value:word) returns (()) { assembly { sstore(ptr, value) } } } -instance uint:StorageType { - function sload(ptr:word) -> uint { - return Typedef.abs(sload_(ptr)):uint; // type annotation needed due to a typechecker bug +impl StorageType { + function sload(ptr:word) returns (uint) { + return Typedef.abs(sload_(ptr)) as uint; // type annotation needed due to a typechecker bug } - function store(ptr:word, value:uint) -> () { + function store(ptr:word, value:uint) returns (()) { return sstore_(ptr, Typedef.rep(value)); } } -forall a . a : StorageType => instance storageRef(a):Assign(a) { - function assign(l:storageRef(a), y:a) -> () { +impl Assign, a> where a: StorageType { + function assign(l:storageRef, y:a) returns (()) { StorageType.store(Typedef.rep(l), y); } } -data MemberAccessProxy(a, field, offset) = MemberAccessProxy(a, field); -forall a field offset . -function memberAccessD1(x:MemberAccessProxy(a, field, offset)) -> a { - match x { - | MemberAccessProxy(y,z) => return y; - } +enum MemberAccessProxy { MemberAccessProxy(a, field) } +function memberAccessD1(x:MemberAccessProxy) returns (a) { + match (x ) { + case MemberAccessProxy(y,z) { return y; + } } } -forall self memberRefType . -class self:LValueMemberAccess(memberRefType) { - function memberAccess(x:self) -> memberRefType; +trait LValueMemberAccess { + function memberAccess(x:self) returns (memberRefType); } -forall self memberValueType . -class self:RValueMemberAccess(memberValueType) { - function memberAccess(x:self) -> memberValueType; +trait RValueMemberAccess { + function memberAccess(x:self) returns (memberValueType); } -forall self fieldType offsetType . -class self:CStructField(fieldType, offsetType) {} +trait CStructField {} -data StructField(structType, fieldSelector) = StructField(structType); +enum StructField { StructField(structType) } -forall structType fieldSelector fieldType offsetType - . StructField(structType, fieldSelector):CStructField(fieldType, offsetType) - , offsetType:StorageSize - => instance MemberAccessProxy(storage(structType), fieldSelector, offsetType):LValueMemberAccess(storageRef(fieldType)) { - function memberAccess(x:MemberAccessProxy(storage(structType), fieldSelector, offsetType)) -> storageRef(fieldType) { +impl LValueMemberAccess, storageRef> where StructField: CStructField, offsetType: StorageSize { + function memberAccess(x:MemberAccessProxy) returns (storageRef) { let ptr:word = Typedef.rep(memberAccessD1(x)); - let size:word = StorageSize.size(Proxy:Proxy(offsetType)); + let size:word = StorageSize.size(Proxy as Proxy); assembly { ptr := add(ptr, size) } @@ -177,20 +163,20 @@ forall structType fieldSelector fieldType offsetType } } -instance ():StorageSize { - function size(x:Proxy(())) -> word { +impl StorageSize<()> { + function size(x:Proxy<()>) returns (word) { return 0; } } -instance word:StorageSize { - function size(x:Proxy(word)) -> word { +impl StorageSize { + function size(x:Proxy) returns (word) { return 1; } } -instance uint:StorageSize { - function size(x:Proxy(uint)) -> word { +impl StorageSize { + function size(x:Proxy) returns (word) { return 1; } } @@ -206,10 +192,10 @@ forall a b . a:Typedef(b), b:StorageSize } */ -forall a b . a:StorageSize, b:StorageSize => instance (a,b):StorageSize { - function size(x:Proxy((a,b))) -> word { - let a_sz:word = StorageSize.size(Proxy:Proxy(a)); - let b_sz:word = StorageSize.size(Proxy:Proxy(b)); +impl StorageSize<(a, b)> where a: StorageSize, b: StorageSize { + function size(x:Proxy<(a, b)>) returns (word) { + let a_sz:word = StorageSize.size(Proxy as Proxy); + let b_sz:word = StorageSize.size(Proxy as Proxy); assembly { a_sz := add(a_sz, b_sz) } @@ -217,16 +203,13 @@ forall a b . a:StorageSize, b:StorageSize => instance (a,b):StorageSize { } } -pragma no-patterson-condition RValueMemberAccess; // this is due to ContractStorage(cxt); probably not needed once we have local instances -pragma no-coverage-condition LValueMemberAccess, RValueMemberAccess; +pragma solcore noPattersonCondition RValueMemberAccess; // this is due to ContractStorage(cxt); probably not needed once we have local instances +pragma solcore noCoverageCondition LValueMemberAccess, RValueMemberAccess; -forall cxt fieldSelector fieldType offsetType - . StructField(ContractStorage(cxt), fieldSelector):CStructField(fieldType, offsetType) - , offsetType:StorageSize - => instance MemberAccessProxy(ContractStorage(cxt), fieldSelector, offsetType):LValueMemberAccess(storageRef(fieldType)) { - function memberAccess(x:MemberAccessProxy(ContractStorage(cxt), fieldSelector, offsetType)) -> storageRef(fieldType) { +impl LValueMemberAccess, fieldSelector, offsetType>, storageRef> where StructField, fieldSelector>: CStructField, offsetType: StorageSize { + function memberAccess(x:MemberAccessProxy, fieldSelector, offsetType>) returns (storageRef) { let ptr:word = 0x100; // forge uses at least 1 storage slot - let offsetSize:word = StorageSize.size(Proxy:Proxy(offsetType)); + let offsetSize:word = StorageSize.size(Proxy as Proxy); assembly { ptr := add(ptr, offsetSize) @@ -235,19 +218,14 @@ forall cxt fieldSelector fieldType offsetType } } -forall cxt fieldSelector fieldType offsetType - . StructField(ContractStorage(cxt), fieldSelector):CStructField(fieldType, offsetType) - , fieldType:StorageType - , offsetType:StorageSize - => instance MemberAccessProxy(ContractStorage(cxt), fieldSelector, offsetType):RValueMemberAccess(fieldType) { - function memberAccess(x:MemberAccessProxy(ContractStorage(cxt), fieldSelector, offsetType)) -> fieldType { +impl RValueMemberAccess, fieldSelector, offsetType>, fieldType> where StructField, fieldSelector>: CStructField, fieldType: StorageType, offsetType: StorageSize { + function memberAccess(x:MemberAccessProxy, fieldSelector, offsetType>) returns (fieldType) { let ptr:word = 0x100; - let offsetSize:word = StorageSize.size(Proxy:Proxy(offsetType)); - return StorageType.sload(add(ptr, offsetSize)):fieldType; + let offsetSize:word = StorageSize.size(Proxy as Proxy); + return StorageType.sload(add(ptr, offsetSize)) as fieldType; } } -forall a b. a:RValueMemberAccess(b) => -function rval(x:a) -> b { +function rval(x:a) returns (b) where a: RValueMemberAccess { return RValueMemberAccess.memberAccess(x); } diff --git a/test/examples/spec/attic/051expreturn.solc b/test/examples/spec/attic/051expreturn.solc index 33b372b37..12e77f588 100644 --- a/test/examples/spec/attic/051expreturn.solc +++ b/test/examples/spec/attic/051expreturn.solc @@ -1,10 +1,10 @@ -data Bool = False | True; -data W = W(Word); -data U = U; +enum Bool { False, True } +enum W { W(Word) } +enum U { U } // empty class needed since forall expects a nonempty context -class a :Top {} -instance a:Top {} +trait Top {} +impl Top {} /* For experiments, special handling when emitting code */ // this does not work, typechecker forces a ~ b @@ -13,38 +13,38 @@ instance a:Top {} // forall a.(a:Top) => function ereturn(x:a) -> a // or -forall a:Top . function ereturn(x:a) -> Unit { let res: Unit; return res; } +function ereturn(x:a) returns (Unit) where a: Top { let res: Unit; return res; } // and then cast it to any type using unsafeCast /* simulate match expression x = match { | Bool.False => return 77; | Bool.True => W(22) } */ -function elimBool1(b:Bool) -> Word { +function elimBool1(b:Bool) returns (Word) { let x : W; x = W(1); - match b { + match (b ) { // this works // | Bool.False => x = unsafeCast(ereturn(77)); // but this does not - unknown intermediate type // | Bool.False => x = unsafeCast(unsafeCast(ereturn(77))); // what about "return(return 77)"? // this works - | Bool.False => x = unsafeCast(ereturn(ereturn(77))); + case Bool.False { x = unsafeCast(ereturn(ereturn(77))); // but this does not // | Bool.False => x = unsafeCast(ereturn(unsafeCast(ereturn(77)))); - | Bool.True => x = W(22); - }; + } case Bool.True { x = W(22); + } } - match x { - | W(y) => return y; - }; + match (x ) { + case W(y) { return y; + } } } // "semicolon" -forall a:Top . function semi(x:a) -> U { return U;} +function semi(x:a) returns (U) where a: Top { return U;} -forall a:Top, b:Top . function unsafeCast(x:a) -> b { +function unsafeCast(x:a) returns (b) where a: Top, b: Top { let res: b; return res; } @@ -53,7 +53,7 @@ contract ExpReturn { - public function main() -> Word { + function main() public returns (Word) { return elimBool1(Bool.False); // return elimBool1(Bool.False); } diff --git a/test/examples/spec/attic/052return.solc b/test/examples/spec/attic/052return.solc index 620987e91..1e0a06965 100644 --- a/test/examples/spec/attic/052return.solc +++ b/test/examples/spec/attic/052return.solc @@ -1,6 +1,6 @@ -data Bool = False | True; -data W = W(word); -data U = U; +enum Bool { False, True } +enum W { W(word) } +enum U { U } /* For experiments, special handling when emitting code */ @@ -10,18 +10,18 @@ data U = U; // function ereturn(x:a) -> a // or -function ereturn(x:a) -> unit { let res: unit; return res; } +function ereturn(x:a) returns (unit) { let res: unit; return res; } // and then cast it to any type using unsafeCast /* simulate match expression x = match { | Bool.False => return 77; | Bool.True => W(22) } */ -function elimBool1(b:Bool) -> word { +function elimBool1(b:Bool) returns (word) { let x : W; x = W(1); - match b { + match (b ) { // this works - | Bool.False => x = unsafeCast(ereturn(77)); + case Bool.False { x = unsafeCast(ereturn(77)); // but this does not - unknown intermediate type // | Bool.False => x = unsafeCast(unsafeCast(ereturn(77))); // what about "return(return 77)"? @@ -32,25 +32,25 @@ function elimBool1(b:Bool) -> word { // this does not work (monomorphisation fails): // | Bool.False => x = unsafeCast(ereturn(unsafeCast(ereturn(77)))); - | Bool.True => x = W(22); - }; + } case Bool.True { x = W(22); + } } - match x { - | W(y) => return y; - }; + match (x ) { + case W(y) { return y; + } } } // "semicolon" -function semi(x:a) -> U { return U;} +function semi(x:a) returns (U) { return U;} -function unsafeCast(x:a) -> b { +function unsafeCast(x:a) returns (b) { let res: b; return res; } contract ExpReturn { - public function main() -> word { + function main() public returns (word) { return elimBool1(Bool.False); // return elimBool1(Bool.True); } diff --git a/test/examples/spec/attic/053return.solc b/test/examples/spec/attic/053return.solc index 29836b50d..12d2bef90 100644 --- a/test/examples/spec/attic/053return.solc +++ b/test/examples/spec/attic/053return.solc @@ -1,35 +1,35 @@ -data Bool = False | True; -data W = W(word); +enum Bool { False, True } +enum W { W(word) } /* For experiments, special handling when emitting code */ -function ereturn(x:a) -> b { let res: b; return res; } +function ereturn(x:a) returns (b) { let res: b; return res; } /* simulate match expression x = match { | Bool.False => return 77; | Bool.True => W(22) } */ -function elimBool1(b:Bool) -> word { +function elimBool1(b:Bool) returns (word) { let x : W; x = W(1); - match b { + match (b ) { // this works - | Bool.False => x = ereturn(77); + case Bool.False { x = ereturn(77); // what about "return(return 77)"? // this does not work (monomorphisation fails) // | Bool.False => x = ereturn(ereturn(77)); - | Bool.True => x = W(22); - }; + } case Bool.True { x = W(22); + } } - match x { - | W(y) => return y; - }; + match (x ) { + case W(y) { return y; + } } } contract ExpReturn { - public function main() -> word { + function main() public returns (word) { return elimBool1(Bool.False); // return elimBool1(Bool.True); } diff --git a/test/imports/alias_dup.solc b/test/imports/alias_dup.solc index f30b5a80c..7eadac1d2 100644 --- a/test/imports/alias_dup.solc +++ b/test/imports/alias_dup.solc @@ -1,6 +1,6 @@ -import ambA as M; -import ambB as M; +import * as M from ambA; +import * as M from ambB; -function main(x: word) -> word { +function main(x: word) returns (word) { return M.pick(x); } diff --git a/test/imports/alias_hides_original_fail.solc b/test/imports/alias_hides_original_fail.solc index f3cc209b2..5defac0df 100644 --- a/test/imports/alias_hides_original_fail.solc +++ b/test/imports/alias_hides_original_fail.solc @@ -1,5 +1,5 @@ -import foo.bar as FB; +import * as FB from foo.bar; -function main() -> word { +function main() returns (word) { return foo.bar.value(); } diff --git a/test/imports/alias_unqualified_constr_fail.solc b/test/imports/alias_unqualified_constr_fail.solc index 1d03c05a0..1128347d1 100644 --- a/test/imports/alias_unqualified_constr_fail.solc +++ b/test/imports/alias_unqualified_constr_fail.solc @@ -1,5 +1,5 @@ -import booldef as B; +import * as B from booldef; -function mkTrue() -> B.Bool { +function mkTrue() returns (B.Bool) { return True; } diff --git a/test/imports/alias_unqualified_fun_fail.solc b/test/imports/alias_unqualified_fun_fail.solc index 58389b0e3..41b8d3ba7 100644 --- a/test/imports/alias_unqualified_fun_fail.solc +++ b/test/imports/alias_unqualified_fun_fail.solc @@ -1,5 +1,5 @@ -import foo as F; +import * as F from foo; -function main() -> word { +function main() returns (word) { return base(); } diff --git a/test/imports/alias_unqualified_type_fail.solc b/test/imports/alias_unqualified_type_fail.solc index 2105a670c..04f7a96d7 100644 --- a/test/imports/alias_unqualified_type_fail.solc +++ b/test/imports/alias_unqualified_type_fail.solc @@ -1,5 +1,5 @@ -import booldef as B; +import * as B from booldef; -function idBool(b: Bool) -> Bool { +function idBool(b: Bool) returns (Bool) { return b; } diff --git a/test/imports/ambA.solc b/test/imports/ambA.solc index ce94f824a..f3adceb8e 100644 --- a/test/imports/ambA.solc +++ b/test/imports/ambA.solc @@ -1,5 +1,5 @@ export { pick }; -function pick(x: word) -> word { +function pick(x: word) returns (word) { return x; } diff --git a/test/imports/ambB.solc b/test/imports/ambB.solc index ce94f824a..f3adceb8e 100644 --- a/test/imports/ambB.solc +++ b/test/imports/ambB.solc @@ -1,5 +1,5 @@ export { pick }; -function pick(x: word) -> word { +function pick(x: word) returns (word) { return x; } diff --git a/test/imports/amb_main.solc b/test/imports/amb_main.solc index d20d1d424..c0e3d961e 100644 --- a/test/imports/amb_main.solc +++ b/test/imports/amb_main.solc @@ -1,6 +1,6 @@ -import ambA.{pick}; -import ambB.{pick}; +import {pick} from ambA; +import {pick} from ambB; -function main(x: word) -> word { +function main(x: word) returns (word) { return pick(x); } diff --git a/test/imports/amb_ok.solc b/test/imports/amb_ok.solc index 5ae3f26c7..638e9d0bf 100644 --- a/test/imports/amb_ok.solc +++ b/test/imports/amb_ok.solc @@ -1,6 +1,6 @@ import ambA; import ambB; -function main(x: word) -> word { +function main(x: word) returns (word) { return ambA.pick(x); } diff --git a/test/imports/boolalias.solc b/test/imports/boolalias.solc index fa0943547..03a326805 100644 --- a/test/imports/boolalias.solc +++ b/test/imports/boolalias.solc @@ -1,5 +1,5 @@ -import booldef as B; +import * as B from booldef; -function fromAlias(b: B.Bool) -> B.Bool { +function fromAlias(b: B.Bool) returns (B.Bool) { return B.not(b); } diff --git a/test/imports/boolalias_open_fail.solc b/test/imports/boolalias_open_fail.solc index ea50f8dd2..69d5ce7a1 100644 --- a/test/imports/boolalias_open_fail.solc +++ b/test/imports/boolalias_open_fail.solc @@ -1,5 +1,5 @@ -import booldef as B; +import * as B from booldef; -function bad(b: Bool) -> Bool { +function bad(b: Bool) returns (Bool) { return not(b); } diff --git a/test/imports/boolaliastype.solc b/test/imports/boolaliastype.solc index bcf3e5546..778718cff 100644 --- a/test/imports/boolaliastype.solc +++ b/test/imports/boolaliastype.solc @@ -1,5 +1,5 @@ -import booldef as B; +import * as B from booldef; -function fromAliasType(b: B.Bool) -> B.Bool { +function fromAliasType(b: B.Bool) returns (B.Bool) { return B.not(b); } diff --git a/test/imports/boolconselect_fail.solc b/test/imports/boolconselect_fail.solc index f4143bbc6..915aaf304 100644 --- a/test/imports/boolconselect_fail.solc +++ b/test/imports/boolconselect_fail.solc @@ -1,5 +1,5 @@ -import booldef.{Bool}; +import {Bool} from booldef; -function mkTrue() -> Bool { +function mkTrue() returns (Bool) { return True; } diff --git a/test/imports/boolconselect_ok.solc b/test/imports/boolconselect_ok.solc index 5b7190370..2fbfea96d 100644 --- a/test/imports/boolconselect_ok.solc +++ b/test/imports/boolconselect_ok.solc @@ -1,5 +1,5 @@ -import booldef.{Bool}; +import {Bool} from booldef; -function mkTrue() -> Bool { +function mkTrue() returns (Bool) { return Bool.True; } diff --git a/test/imports/booldef.solc b/test/imports/booldef.solc index 639d21eb2..5c884d81c 100644 --- a/test/imports/booldef.solc +++ b/test/imports/booldef.solc @@ -1,22 +1,22 @@ export { Bool(*), not, C, D, id }; -data Bool = True | False; +enum Bool { True, False } -function not (b : Bool) -> Bool { - match b { - | Bool.True => return Bool.False; - | Bool.False => return Bool.True; - } +function not (b : Bool) returns (Bool) { + match (b ) { + case Bool.True { return Bool.False; + } case Bool.False { return Bool.True; + } } } -forall a . class a : C { - function c (x : a, y : a) -> word ; +trait C { + function c (x : a, y : a) returns (word) ; } -forall a . class a : D { - function d() -> a ; +trait D { + function d() returns (a) ; } -forall a . a : C, a : D => function id (x : a) -> word { +function id (x : a) returns (word) where a: C, a: D { return C.c(x, D.d()); } diff --git a/test/imports/boolmain.solc b/test/imports/boolmain.solc index a50de30f7..d6b08815b 100644 --- a/test/imports/boolmain.solc +++ b/test/imports/boolmain.solc @@ -1,5 +1,5 @@ import booldef; -function and(b1: booldef.Bool, b2: booldef.Bool) -> booldef.Bool { +function and(b1: booldef.Bool, b2: booldef.Bool) returns (booldef.Bool) { return b1; } diff --git a/test/imports/boolqualified.solc b/test/imports/boolqualified.solc index 01bf3a3ed..4ffcf72ff 100644 --- a/test/imports/boolqualified.solc +++ b/test/imports/boolqualified.solc @@ -1,5 +1,5 @@ import booldef; -function fromQualified(b: booldef.Bool) -> booldef.Bool { +function fromQualified(b: booldef.Bool) returns (booldef.Bool) { return booldef.not(b); } diff --git a/test/imports/boolqualifiedtype.solc b/test/imports/boolqualifiedtype.solc index 0b4d2b3cb..5892f3cdc 100644 --- a/test/imports/boolqualifiedtype.solc +++ b/test/imports/boolqualifiedtype.solc @@ -1,5 +1,5 @@ import booldef; -function fromQualifiedType(b: booldef.Bool) -> booldef.Bool { +function fromQualifiedType(b: booldef.Bool) returns (booldef.Bool) { return booldef.not(b); } diff --git a/test/imports/boolselect.solc b/test/imports/boolselect.solc index 1041fc714..bc70b0135 100644 --- a/test/imports/boolselect.solc +++ b/test/imports/boolselect.solc @@ -1,5 +1,5 @@ -import booldef.{Bool, not}; +import {Bool, not} from booldef; -function fromSelect(b: Bool) -> Bool { +function fromSelect(b: Bool) returns (Bool) { return not(b); } diff --git a/test/imports/cycleA.solc b/test/imports/cycleA.solc index 1ce73fd64..30bca38e6 100644 --- a/test/imports/cycleA.solc +++ b/test/imports/cycleA.solc @@ -2,6 +2,6 @@ import cycleB; export { fromCycleA }; export cycleB.{fromCycleB}; -function fromCycleA() -> word { +function fromCycleA() returns (word) { return cycleB.fromCycleB(); } diff --git a/test/imports/cycleB.solc b/test/imports/cycleB.solc index 71fb1cf52..07d187744 100644 --- a/test/imports/cycleB.solc +++ b/test/imports/cycleB.solc @@ -2,6 +2,6 @@ import cycleA; export { fromCycleB }; export cycleA.{fromCycleA}; -function fromCycleB() -> word { +function fromCycleB() returns (word) { return 2; } diff --git a/test/imports/cycle_main.solc b/test/imports/cycle_main.solc index 77d87240b..e8784d8ab 100644 --- a/test/imports/cycle_main.solc +++ b/test/imports/cycle_main.solc @@ -1,5 +1,5 @@ import cycleA; -function main() -> word { +function main() returns (word) { return cycleA.fromCycleB(); } diff --git a/test/imports/dot_context_expr.solc b/test/imports/dot_context_expr.solc index 02be5db60..2997317a9 100644 --- a/test/imports/dot_context_expr.solc +++ b/test/imports/dot_context_expr.solc @@ -1,14 +1,14 @@ import dot_left; import dot_right; -function mkLeft() -> dot_left.LeftOpt { +function mkLeft() returns (dot_left.LeftOpt) { let x: dot_left.LeftOpt = .Some(1); return x; } -function main() -> word { - match mkLeft() { - | .Some(v) => return v; - | .None => return 0; - } +function main() returns (word) { + match (mkLeft() ) { + case .Some(v) { return v; + } case .None { return 0; + } } } diff --git a/test/imports/dot_left.solc b/test/imports/dot_left.solc index 5511a1c6c..30203ed95 100644 --- a/test/imports/dot_left.solc +++ b/test/imports/dot_left.solc @@ -1,3 +1,3 @@ export { LeftOpt(*) }; -data LeftOpt = None | Some(word); +enum LeftOpt { None, Some(word) } diff --git a/test/imports/dot_right.solc b/test/imports/dot_right.solc index 82f8f8afe..8cc9becd1 100644 --- a/test/imports/dot_right.solc +++ b/test/imports/dot_right.solc @@ -1,3 +1,3 @@ export { RightOpt(*) }; -data RightOpt = None | Some(word); +enum RightOpt { None, Some(word) } diff --git a/test/imports/dupqual_a.solc b/test/imports/dupqual_a.solc index ed7f99ed7..61eae222e 100644 --- a/test/imports/dupqual_a.solc +++ b/test/imports/dupqual_a.solc @@ -1,5 +1,5 @@ export { foo }; -function foo(x: word) -> word { +function foo(x: word) returns (word) { return 1; } diff --git a/test/imports/dupqual_b.solc b/test/imports/dupqual_b.solc index 7ee6a4e56..87b4d50d6 100644 --- a/test/imports/dupqual_b.solc +++ b/test/imports/dupqual_b.solc @@ -1,5 +1,5 @@ export { foo }; -function foo(x: word) -> word { +function foo(x: word) returns (word) { return x; } diff --git a/test/imports/dupqual_main.solc b/test/imports/dupqual_main.solc index cbe4de151..e957ae83e 100644 --- a/test/imports/dupqual_main.solc +++ b/test/imports/dupqual_main.solc @@ -1,7 +1,7 @@ -import dupqual_a as m1; -import dupqual_b as m2; +import * as m1 from dupqual_a; +import * as m2 from dupqual_b; -function main(x: word) -> word { +function main(x: word) returns (word) { let y = m1.foo(x); return m2.foo(y); } diff --git a/test/imports/dupqual_module_main.solc b/test/imports/dupqual_module_main.solc index 5ef0d8ebf..6a887247b 100644 --- a/test/imports/dupqual_module_main.solc +++ b/test/imports/dupqual_module_main.solc @@ -1,7 +1,7 @@ import dupqual_a; import dupqual_b; -function main(x: word) -> word { +function main(x: word) returns (word) { let y = dupqual_a.foo(x); return dupqual_b.foo(y); } diff --git a/test/imports/export_item_dup_fail.solc b/test/imports/export_item_dup_fail.solc index 8d7a33f1d..d6d7abb89 100644 --- a/test/imports/export_item_dup_fail.solc +++ b/test/imports/export_item_dup_fail.solc @@ -1,6 +1,6 @@ export ambA.{pick}; export ambB.{pick}; -function main(x: word) -> word { +function main(x: word) returns (word) { return x; } diff --git a/test/imports/export_module_dup_fail.solc b/test/imports/export_module_dup_fail.solc index 118a875c6..95cbdc5ad 100644 --- a/test/imports/export_module_dup_fail.solc +++ b/test/imports/export_module_dup_fail.solc @@ -1,6 +1,6 @@ export foo as M; export booldef as M; -function main() -> word { +function main() returns (word) { return 0; } diff --git a/test/imports/external_lib_alias_main.solc b/test/imports/external_lib_alias_main.solc index 7853c68c3..7bc7882aa 100644 --- a/test/imports/external_lib_alias_main.solc +++ b/test/imports/external_lib_alias_main.solc @@ -1,5 +1,5 @@ -import @extlib.math.api as MathApi; +import * as MathApi from @extlib.math.api; -function main() -> word { +function main() returns (word) { return MathApi.sum(39); } diff --git a/test/imports/external_lib_main.solc b/test/imports/external_lib_main.solc index 5ffd122d0..12bb126a5 100644 --- a/test/imports/external_lib_main.solc +++ b/test/imports/external_lib_main.solc @@ -3,7 +3,7 @@ import @extlib.math.api; contract External { constructor() {} - public function main() -> word { + function main() public returns (word) { return math.api.sum(39); } } diff --git a/test/imports/extlib/math/api.solc b/test/imports/extlib/math/api.solc index 43dc18f7e..3ff33e31f 100644 --- a/test/imports/extlib/math/api.solc +++ b/test/imports/extlib/math/api.solc @@ -3,6 +3,6 @@ import lib.util; export {sum}; -function sum(x: word) -> word { +function sum(x: word) returns (word) { return add.inc(x) + util.offset(); } diff --git a/test/imports/extlib/math/internals/add.solc b/test/imports/extlib/math/internals/add.solc index 06449de71..dd5b16fd7 100644 --- a/test/imports/extlib/math/internals/add.solc +++ b/test/imports/extlib/math/internals/add.solc @@ -1,7 +1,7 @@ -import std.{Add}; +import {Add} from std; export {inc}; -function inc(x: word) -> word { +function inc(x: word) returns (word) { return x + 1; } diff --git a/test/imports/extlib/util.solc b/test/imports/extlib/util.solc index 21a006823..de93f722b 100644 --- a/test/imports/extlib/util.solc +++ b/test/imports/extlib/util.solc @@ -1,5 +1,5 @@ export {offset}; -function offset() -> word { +function offset() returns (word) { return 2; } diff --git a/test/imports/foo.solc b/test/imports/foo.solc index ca17ac815..ebef15811 100644 --- a/test/imports/foo.solc +++ b/test/imports/foo.solc @@ -1,5 +1,5 @@ export { base }; -function base() -> word { +function base() returns (word) { return 3; } diff --git a/test/imports/foo/bar.solc b/test/imports/foo/bar.solc index 4f2e503de..b0daa7d98 100644 --- a/test/imports/foo/bar.solc +++ b/test/imports/foo/bar.solc @@ -1,5 +1,5 @@ export { value }; -function value() -> word { +function value() returns (word) { return 7; } diff --git a/test/imports/foo/bar/baz.solc b/test/imports/foo/bar/baz.solc index 73dd9ef1e..aa7fac4b4 100644 --- a/test/imports/foo/bar/baz.solc +++ b/test/imports/foo/bar/baz.solc @@ -1,5 +1,5 @@ export { deep }; -function deep() -> word { +function deep() returns (word) { return 9; } diff --git a/test/imports/glob_amb_a.solc b/test/imports/glob_amb_a.solc index ccd8ec047..6970ebc04 100644 --- a/test/imports/glob_amb_a.solc +++ b/test/imports/glob_amb_a.solc @@ -1,5 +1,5 @@ export {*}; -function shared(x: word) -> word { +function shared(x: word) returns (word) { return x; } diff --git a/test/imports/glob_amb_b.solc b/test/imports/glob_amb_b.solc index ccd8ec047..6970ebc04 100644 --- a/test/imports/glob_amb_b.solc +++ b/test/imports/glob_amb_b.solc @@ -1,5 +1,5 @@ export {*}; -function shared(x: word) -> word { +function shared(x: word) returns (word) { return x; } diff --git a/test/imports/glob_amb_main_fail.solc b/test/imports/glob_amb_main_fail.solc index 168a2d20c..33225f89b 100644 --- a/test/imports/glob_amb_main_fail.solc +++ b/test/imports/glob_amb_main_fail.solc @@ -1,6 +1,6 @@ -import glob_amb_a.{*}; -import glob_amb_b.{*}; +import {*} from glob_amb_a; +import {*} from glob_amb_b; -function main(x: word) -> word { +function main(x: word) returns (word) { return shared(x); } diff --git a/test/imports/glob_export_mixed.solc b/test/imports/glob_export_mixed.solc index 0bed5cdc9..28313d8b6 100644 --- a/test/imports/glob_export_mixed.solc +++ b/test/imports/glob_export_mixed.solc @@ -1,5 +1,5 @@ export {*, main}; -function main(x: word) -> word { +function main(x: word) returns (word) { return x; } diff --git a/test/imports/glob_hiding_amb_ok.solc b/test/imports/glob_hiding_amb_ok.solc index 89bb50ab0..ce77d91a5 100644 --- a/test/imports/glob_hiding_amb_ok.solc +++ b/test/imports/glob_hiding_amb_ok.solc @@ -1,6 +1,6 @@ -import glob_amb_a.{*} hiding {shared}; -import glob_amb_b.{*}; +import {*} from glob_amb_a hiding {shared}; +import {*} from glob_amb_b; -function main(x: word) -> word { +function main(x: word) returns (word) { return shared(x); } diff --git a/test/imports/glob_import_dup.solc b/test/imports/glob_import_dup.solc index 100a698cc..0b333f6c1 100644 --- a/test/imports/glob_import_dup.solc +++ b/test/imports/glob_import_dup.solc @@ -1,5 +1,5 @@ -import globlib.{*, *}; +import {*, *} from globlib; -function main(x: word) -> word { +function main(x: word) returns (word) { return x; } diff --git a/test/imports/glob_import_hiding.solc b/test/imports/glob_import_hiding.solc index 385dff723..1abc308f6 100644 --- a/test/imports/glob_import_hiding.solc +++ b/test/imports/glob_import_hiding.solc @@ -1,8 +1,8 @@ -import globlib.{*} hiding {idWord}; +import {*} from globlib hiding {idWord}; -function main(x: word) -> word { +function main(x: word) returns (word) { let y: T = mkT(x); - match y { - | T.T(v) => return v; - } + match (y ) { + case T.T(v) { return v; + } } } diff --git a/test/imports/glob_import_hiding_unknown_fail.solc b/test/imports/glob_import_hiding_unknown_fail.solc index 7877da111..2f835adc7 100644 --- a/test/imports/glob_import_hiding_unknown_fail.solc +++ b/test/imports/glob_import_hiding_unknown_fail.solc @@ -1,5 +1,5 @@ -import globlib.{*} hiding {missing}; +import {*} from globlib hiding {missing}; -function main(x: word) -> word { +function main(x: word) returns (word) { return x; } diff --git a/test/imports/glob_import_mixed.solc b/test/imports/glob_import_mixed.solc index aabb81aa5..67e1088fa 100644 --- a/test/imports/glob_import_mixed.solc +++ b/test/imports/glob_import_mixed.solc @@ -1,5 +1,5 @@ -import globlib.{*, idWord}; +import {*, idWord} from globlib; -function main(x: word) -> word { +function main(x: word) returns (word) { return idWord(x); } diff --git a/test/imports/glob_import_ok.solc b/test/imports/glob_import_ok.solc index e87a4f800..e036986a2 100644 --- a/test/imports/glob_import_ok.solc +++ b/test/imports/glob_import_ok.solc @@ -1,8 +1,8 @@ -import globlib.{*}; +import {*} from globlib; -function main(x: word) -> word { +function main(x: word) returns (word) { let y: T = mkT(x); - match y { - | T.T(v) => return idWord(v); - } + match (y ) { + case T.T(v) { return idWord(v); + } } } diff --git a/test/imports/globlib.solc b/test/imports/globlib.solc index d433e74d5..431b3dd2d 100644 --- a/test/imports/globlib.solc +++ b/test/imports/globlib.solc @@ -1,11 +1,11 @@ export {*, T(*)}; -data T = T(word); +enum T { T(word) } -function idWord(x: word) -> word { +function idWord(x: word) returns (word) { return x; } -function mkT(x: word) -> T { +function mkT(x: word) returns (T) { return T.T(x); } diff --git a/test/imports/hidden_ctor_dot_fail.solc b/test/imports/hidden_ctor_dot_fail.solc index e6e2a41d1..286cd96b3 100644 --- a/test/imports/hidden_ctor_dot_fail.solc +++ b/test/imports/hidden_ctor_dot_fail.solc @@ -1,5 +1,5 @@ -import hidden_ctor_lib.{Token}; +import {Token} from hidden_ctor_lib; -function main() -> Token { +function main() returns (Token) { return .Err(1); } diff --git a/test/imports/hidden_ctor_expr_fail.solc b/test/imports/hidden_ctor_expr_fail.solc index 1515e1f77..c2df21972 100644 --- a/test/imports/hidden_ctor_expr_fail.solc +++ b/test/imports/hidden_ctor_expr_fail.solc @@ -1,5 +1,5 @@ -import hidden_ctor_lib.{Token}; +import {Token} from hidden_ctor_lib; -function main() -> Token { +function main() returns (Token) { return Token.Err(0); } diff --git a/test/imports/hidden_ctor_lib.solc b/test/imports/hidden_ctor_lib.solc index 4ecb42b8b..038b849d2 100644 --- a/test/imports/hidden_ctor_lib.solc +++ b/test/imports/hidden_ctor_lib.solc @@ -1,11 +1,11 @@ export {Token(Ok), mkOk, mkErr}; -data Token = Ok(word) | Err(word); +enum Token { Ok(word), Err(word) } -function mkOk(x: word) -> Token { +function mkOk(x: word) returns (Token) { return Token.Ok(x); } -function mkErr(x: word) -> Token { +function mkErr(x: word) returns (Token) { return Token.Err(x); } diff --git a/test/imports/hidden_ctor_nonexhaustive_fail.solc b/test/imports/hidden_ctor_nonexhaustive_fail.solc index d13d01676..5fe33f984 100644 --- a/test/imports/hidden_ctor_nonexhaustive_fail.solc +++ b/test/imports/hidden_ctor_nonexhaustive_fail.solc @@ -1,7 +1,7 @@ -import hidden_ctor_lib.{Token, mkOk}; +import {Token, mkOk} from hidden_ctor_lib; -function main() -> word { - match mkOk(1) { - | Token.Ok(v) => return v; - } +function main() returns (word) { + match (mkOk(1) ) { + case Token.Ok(v) { return v; + } } } diff --git a/test/imports/hidden_ctor_pattern_fail.solc b/test/imports/hidden_ctor_pattern_fail.solc index 3637f6135..0ddefec6a 100644 --- a/test/imports/hidden_ctor_pattern_fail.solc +++ b/test/imports/hidden_ctor_pattern_fail.solc @@ -1,8 +1,8 @@ -import hidden_ctor_lib.{Token, mkErr}; +import {Token, mkErr} from hidden_ctor_lib; -function main() -> word { - match mkErr(1) { - | Token.Err(v) => return v; - | _ => return 0; - } +function main() returns (word) { + match (mkErr(1) ) { + case Token.Err(v) { return v; + } default { return 0; + } } } diff --git a/test/imports/hidden_ctor_wildcard_ok.solc b/test/imports/hidden_ctor_wildcard_ok.solc index 25f93ee3f..4a25c3086 100644 --- a/test/imports/hidden_ctor_wildcard_ok.solc +++ b/test/imports/hidden_ctor_wildcard_ok.solc @@ -1,8 +1,8 @@ -import hidden_ctor_lib.{Token, mkErr}; +import {Token, mkErr} from hidden_ctor_lib; -function main() -> word { - match mkErr(1) { - | Token.Ok(v) => return v; - | _ => return 0; - } +function main() returns (word) { + match (mkErr(1) ) { + case Token.Ok(v) { return v; + } default { return 0; + } } } diff --git a/test/imports/import_std_minimal.solc b/test/imports/import_std_minimal.solc index f54d9a7f6..ce33f1995 100644 --- a/test/imports/import_std_minimal.solc +++ b/test/imports/import_std_minimal.solc @@ -1,3 +1,3 @@ import std; -function main() -> () {} +function main() returns (()) {} diff --git a/test/imports/leak_a.solc b/test/imports/leak_a.solc index 560e4fbf7..982d6b273 100644 --- a/test/imports/leak_a.solc +++ b/test/imports/leak_a.solc @@ -1,5 +1,5 @@ export { fromA }; -function fromA() -> word { +function fromA() returns (word) { return 1; } diff --git a/test/imports/leak_b.solc b/test/imports/leak_b.solc index 198768b24..3b9974915 100644 --- a/test/imports/leak_b.solc +++ b/test/imports/leak_b.solc @@ -1,5 +1,5 @@ export { fromB }; -function fromB() -> word { +function fromB() returns (word) { return fromA(); } diff --git a/test/imports/leak_main.solc b/test/imports/leak_main.solc index 7a52277c7..3efbc01be 100644 --- a/test/imports/leak_main.solc +++ b/test/imports/leak_main.solc @@ -1,6 +1,6 @@ import leak_a; import leak_b; -function main() -> word { +function main() returns (word) { return fromB(); } diff --git a/test/imports/mirror/helper.solc b/test/imports/mirror/helper.solc index d2d38ce76..f03838e76 100644 --- a/test/imports/mirror/helper.solc +++ b/test/imports/mirror/helper.solc @@ -1,3 +1,3 @@ export {T}; -data T = T; +enum T { T } diff --git a/test/imports/module_name_shadow.solc b/test/imports/module_name_shadow.solc index a22bc04ba..303ec7db4 100644 --- a/test/imports/module_name_shadow.solc +++ b/test/imports/module_name_shadow.solc @@ -1,9 +1,9 @@ -import foo as keep; +import * as keep from foo; -function keep() -> word { +function keep() returns (word) { return 1; } -function main() -> word { +function main() returns (word) { return keep(); } diff --git a/test/imports/module_qualified_constructor.solc b/test/imports/module_qualified_constructor.solc index 7f3d86407..f02f35ca4 100644 --- a/test/imports/module_qualified_constructor.solc +++ b/test/imports/module_qualified_constructor.solc @@ -1,5 +1,5 @@ import booldef; -function mk() -> booldef.Bool { +function mk() returns (booldef.Bool) { return booldef.Bool.True; } diff --git a/test/imports/module_qualified_constructor_alias.solc b/test/imports/module_qualified_constructor_alias.solc index f3896448b..83b06e508 100644 --- a/test/imports/module_qualified_constructor_alias.solc +++ b/test/imports/module_qualified_constructor_alias.solc @@ -1,5 +1,5 @@ -import booldef as b; +import * as b from booldef; -function mk() -> b.Bool { +function mk() returns (b.Bool) { return b.Bool.True; } diff --git a/test/imports/module_qualified_constructor_pattern.solc b/test/imports/module_qualified_constructor_pattern.solc index 84fb72ddb..c9ee8167c 100644 --- a/test/imports/module_qualified_constructor_pattern.solc +++ b/test/imports/module_qualified_constructor_pattern.solc @@ -1,8 +1,8 @@ import booldef; -function main(x: booldef.Bool) -> word { - match x { - | booldef.Bool.True => return 1; - | _ => return 0; - } +function main(x: booldef.Bool) returns (word) { + match (x ) { + case booldef.Bool.True { return 1; + } default { return 0; + } } } diff --git a/test/imports/module_unqualified_constr_fail.solc b/test/imports/module_unqualified_constr_fail.solc index cc250ccfd..9e0d64d22 100644 --- a/test/imports/module_unqualified_constr_fail.solc +++ b/test/imports/module_unqualified_constr_fail.solc @@ -1,5 +1,5 @@ import booldef; -function mkTrue() -> booldef.Bool { +function mkTrue() returns (booldef.Bool) { return True; } diff --git a/test/imports/module_unqualified_fun_fail.solc b/test/imports/module_unqualified_fun_fail.solc index 9a4b36110..ff8ddb46f 100644 --- a/test/imports/module_unqualified_fun_fail.solc +++ b/test/imports/module_unqualified_fun_fail.solc @@ -1,5 +1,5 @@ import foo; -function main() -> word { +function main() returns (word) { return base(); } diff --git a/test/imports/module_unqualified_type_fail.solc b/test/imports/module_unqualified_type_fail.solc index f8ddc777c..0cd983cbd 100644 --- a/test/imports/module_unqualified_type_fail.solc +++ b/test/imports/module_unqualified_type_fail.solc @@ -1,5 +1,5 @@ import booldef; -function idBool(b: Bool) -> Bool { +function idBool(b: Bool) returns (Bool) { return b; } diff --git a/test/imports/nested_alias.solc b/test/imports/nested_alias.solc index 0e8f0059d..1e207504a 100644 --- a/test/imports/nested_alias.solc +++ b/test/imports/nested_alias.solc @@ -1,5 +1,5 @@ -import foo.bar as FB; +import * as FB from foo.bar; -function main() -> word { +function main() returns (word) { return FB.value(); } diff --git a/test/imports/nested_deep_qualifier.solc b/test/imports/nested_deep_qualifier.solc index 8c8d43b7b..49770daad 100644 --- a/test/imports/nested_deep_qualifier.solc +++ b/test/imports/nested_deep_qualifier.solc @@ -1,5 +1,5 @@ import foo.bar.baz; -function main() -> word { +function main() returns (word) { return foo.bar.baz.deep(); } diff --git a/test/imports/nested_direct_qualifier.solc b/test/imports/nested_direct_qualifier.solc index 8d1b89fd8..81a902b08 100644 --- a/test/imports/nested_direct_qualifier.solc +++ b/test/imports/nested_direct_qualifier.solc @@ -1,5 +1,5 @@ import foo.bar; -function main() -> word { +function main() returns (word) { return foo.bar.value(); } diff --git a/test/imports/nested_foo_and_bar.solc b/test/imports/nested_foo_and_bar.solc index abe3fe99e..72b986472 100644 --- a/test/imports/nested_foo_and_bar.solc +++ b/test/imports/nested_foo_and_bar.solc @@ -1,7 +1,7 @@ import foo; -import foo.bar as Bar; +import * as Bar from foo.bar; -function main() -> word { +function main() returns (word) { let x: word = foo.base(); let y: word = Bar.value(); return y; diff --git a/test/imports/nested_select.solc b/test/imports/nested_select.solc index 62047c36f..b90ecff11 100644 --- a/test/imports/nested_select.solc +++ b/test/imports/nested_select.solc @@ -1,5 +1,5 @@ -import foo.bar.{value}; +import {value} from foo.bar; -function main() -> word { +function main() returns (word) { return value(); } diff --git a/test/imports/ns_constr_dup.solc b/test/imports/ns_constr_dup.solc index 8dbef8db4..6099075c2 100644 --- a/test/imports/ns_constr_dup.solc +++ b/test/imports/ns_constr_dup.solc @@ -1,6 +1,6 @@ -data Foo = Same; -data Bar = Same; +enum Foo { Same } +enum Bar { Same } -function main() -> word { +function main() returns (word) { return 0; } diff --git a/test/imports/ns_cross_ok.solc b/test/imports/ns_cross_ok.solc index 34b8a18f6..ca4d7e96d 100644 --- a/test/imports/ns_cross_ok.solc +++ b/test/imports/ns_cross_ok.solc @@ -1,5 +1,5 @@ -data Foo = Foo; +enum Foo { Foo } -function main() -> Foo { +function main() returns (Foo) { return Foo.Foo; } diff --git a/test/imports/opaque_alias_leak_fail.solc b/test/imports/opaque_alias_leak_fail.solc index c6f221a43..2db0ff032 100644 --- a/test/imports/opaque_alias_leak_fail.solc +++ b/test/imports/opaque_alias_leak_fail.solc @@ -1,5 +1,5 @@ -import opaque_alias_mid as M; +import * as M from opaque_alias_mid; -function bad(x: word) -> T { +function bad(x: word) returns (T) { return M.make(x); } diff --git a/test/imports/opaque_alias_main.solc b/test/imports/opaque_alias_main.solc index a21644c32..d0c8f9f22 100644 --- a/test/imports/opaque_alias_main.solc +++ b/test/imports/opaque_alias_main.solc @@ -1,6 +1,6 @@ -import opaque_alias_mid as M; +import * as M from opaque_alias_mid; -function main(x: word) -> word { +function main(x: word) returns (word) { let t = M.make(x); return x; } diff --git a/test/imports/opaque_alias_mid.solc b/test/imports/opaque_alias_mid.solc index 755233515..2c0716308 100644 --- a/test/imports/opaque_alias_mid.solc +++ b/test/imports/opaque_alias_mid.solc @@ -1,7 +1,7 @@ -import opaque_dep_base as Base; +import * as Base from opaque_dep_base; export { make }; -function make(x: word) -> Base.T { +function make(x: word) returns (Base.T) { return Base.mkT(x); } diff --git a/test/imports/opaque_alias_qualifier_leak_fail.solc b/test/imports/opaque_alias_qualifier_leak_fail.solc index fd0e05184..2646b7b2f 100644 --- a/test/imports/opaque_alias_qualifier_leak_fail.solc +++ b/test/imports/opaque_alias_qualifier_leak_fail.solc @@ -1,5 +1,5 @@ -import opaque_alias_mid as M; +import * as M from opaque_alias_mid; -function bad(x: word) -> Base.T { +function bad(x: word) returns (Base.T) { return M.make(x); } diff --git a/test/imports/opaque_dep_base.solc b/test/imports/opaque_dep_base.solc index 95a10f3e0..390f62d16 100644 --- a/test/imports/opaque_dep_base.solc +++ b/test/imports/opaque_dep_base.solc @@ -1,7 +1,7 @@ export { T(*), mkT }; -data T = T(word); +enum T { T(word) } -function mkT(x: word) -> T { +function mkT(x: word) returns (T) { return T.T(x); } diff --git a/test/imports/opaque_select_alias_main.solc b/test/imports/opaque_select_alias_main.solc index 8ec20765b..51e23026f 100644 --- a/test/imports/opaque_select_alias_main.solc +++ b/test/imports/opaque_select_alias_main.solc @@ -1,6 +1,6 @@ -import opaque_select_alias_mid as M; +import * as M from opaque_select_alias_mid; -function main(x: word) -> word { +function main(x: word) returns (word) { let t = M.make(x); return x; } diff --git a/test/imports/opaque_select_alias_mid.solc b/test/imports/opaque_select_alias_mid.solc index b8f71be78..9ca5df84c 100644 --- a/test/imports/opaque_select_alias_mid.solc +++ b/test/imports/opaque_select_alias_mid.solc @@ -1,7 +1,7 @@ -import opaque_dep_base.{T as U, mkT}; +import {T as U, mkT} from opaque_dep_base; export { make }; -function make(x: word) -> U { +function make(x: word) returns (U) { return mkT(x); } diff --git a/test/imports/opaque_select_direct_leak_fail.solc b/test/imports/opaque_select_direct_leak_fail.solc index 47a953ca5..af1c13438 100644 --- a/test/imports/opaque_select_direct_leak_fail.solc +++ b/test/imports/opaque_select_direct_leak_fail.solc @@ -1,5 +1,5 @@ -import opaque_select_direct_mid as M; +import * as M from opaque_select_direct_mid; -function bad(x: word) -> T { +function bad(x: word) returns (T) { return M.make(x); } diff --git a/test/imports/opaque_select_direct_mid.solc b/test/imports/opaque_select_direct_mid.solc index bdb833a79..dce75db02 100644 --- a/test/imports/opaque_select_direct_mid.solc +++ b/test/imports/opaque_select_direct_mid.solc @@ -1,7 +1,7 @@ -import opaque_dep_base.{T, mkT}; +import {T, mkT} from opaque_dep_base; export { make }; -function make(x: word) -> T { +function make(x: word) returns (T) { return mkT(x); } diff --git a/test/imports/pragma_scope_lib.solc b/test/imports/pragma_scope_lib.solc index 035f940a5..70140bbe5 100644 --- a/test/imports/pragma_scope_lib.solc +++ b/test/imports/pragma_scope_lib.solc @@ -1,7 +1,7 @@ export { helper }; -pragma no-patterson-condition C; +pragma solcore noPattersonCondition C; -function helper() -> word { +function helper() returns (word) { return 1; } diff --git a/test/imports/pragma_scope_main.solc b/test/imports/pragma_scope_main.solc index 0d4f0b222..ebaccf718 100644 --- a/test/imports/pragma_scope_main.solc +++ b/test/imports/pragma_scope_main.solc @@ -1,7 +1,7 @@ import pragma_scope_lib; -data List(a) = Nil | Cons(a, List(a)); +enum List { Nil, Cons(a, List) } -forall a b c . class a : C(b, c) {} +trait C {} -forall a b . instance List(b) : C(a, List(a)) {} +impl C, a, List> {} diff --git a/test/imports/private_bad_lib.solc b/test/imports/private_bad_lib.solc index f7f1d0728..fd65036a4 100644 --- a/test/imports/private_bad_lib.solc +++ b/test/imports/private_bad_lib.solc @@ -1,9 +1,9 @@ export {ok}; -function ok() -> word { +function ok() returns (word) { return 1; } -function broken() -> word { +function broken() returns (word) { return true; } diff --git a/test/imports/private_bad_main.solc b/test/imports/private_bad_main.solc index 79e69d91c..7a32366f9 100644 --- a/test/imports/private_bad_main.solc +++ b/test/imports/private_bad_main.solc @@ -1,5 +1,5 @@ import private_bad_lib; -function main() -> word { +function main() returns (word) { return private_bad_lib.ok(); } diff --git a/test/imports/private_helper_a.solc b/test/imports/private_helper_a.solc index 9bfb52161..e002f7f4c 100644 --- a/test/imports/private_helper_a.solc +++ b/test/imports/private_helper_a.solc @@ -1,9 +1,9 @@ export { foo }; -function helper(x: word) -> word { +function helper(x: word) returns (word) { return x; } -function foo(x: word) -> word { +function foo(x: word) returns (word) { return helper(x); } diff --git a/test/imports/private_helper_main.solc b/test/imports/private_helper_main.solc index b6eee902e..050cf3fba 100644 --- a/test/imports/private_helper_main.solc +++ b/test/imports/private_helper_main.solc @@ -1,5 +1,5 @@ import private_helper_a; -function main(x: word) -> word { +function main(x: word) returns (word) { return private_helper_a.foo(x); } diff --git a/test/imports/reexport_ctor_expr_hidden_fail.solc b/test/imports/reexport_ctor_expr_hidden_fail.solc index 77bf9dd41..47cf70c66 100644 --- a/test/imports/reexport_ctor_expr_hidden_fail.solc +++ b/test/imports/reexport_ctor_expr_hidden_fail.solc @@ -1,5 +1,5 @@ import reexport_ctor_mid; -function main() -> reexport_ctor_mid.Token { +function main() returns (reexport_ctor_mid.Token) { return reexport_ctor_mid.Token.Err(1); } diff --git a/test/imports/reexport_ctor_expr_ok.solc b/test/imports/reexport_ctor_expr_ok.solc index 774ffd237..16fcaaaf9 100644 --- a/test/imports/reexport_ctor_expr_ok.solc +++ b/test/imports/reexport_ctor_expr_ok.solc @@ -1,5 +1,5 @@ import reexport_ctor_mid; -function main() -> reexport_ctor_mid.Token { +function main() returns (reexport_ctor_mid.Token) { return reexport_ctor_mid.Token.Ok(1); } diff --git a/test/imports/reexport_ctor_pattern.solc b/test/imports/reexport_ctor_pattern.solc index 3e474451b..3ba20f453 100644 --- a/test/imports/reexport_ctor_pattern.solc +++ b/test/imports/reexport_ctor_pattern.solc @@ -1,8 +1,8 @@ import reexport_ctor_mid; -function main() -> word { - match reexport_ctor_mid.mkErr(1) { - | reexport_ctor_mid.Token.Ok(v) => return v; - | _ => return 0; - } +function main() returns (word) { + match (reexport_ctor_mid.mkErr(1) ) { + case reexport_ctor_mid.Token.Ok(v) { return v; + } default { return 0; + } } } diff --git a/test/imports/reexport_items/pkg/util.solc b/test/imports/reexport_items/pkg/util.solc index af8af05db..3d5bc7229 100644 --- a/test/imports/reexport_items/pkg/util.solc +++ b/test/imports/reexport_items/pkg/util.solc @@ -1,19 +1,19 @@ export {Wrap(*), unwrap, Unbox}; -data Wrap = Mk(word); +enum Wrap { Mk(word) } -forall self . class self:Unbox { - function unbox(x:self) -> word; +trait Unbox { + function unbox(x:self) returns (word); } -instance Wrap:Unbox { - function unbox(x:Wrap) -> word { - match x { - | Wrap.Mk(w) => return w; - } +impl Unbox { + function unbox(x:Wrap) returns (word) { + match (x ) { + case Wrap.Mk(w) { return w; + } } } } -function unwrap(x:Wrap) -> word { +function unwrap(x:Wrap) returns (word) { return Unbox.unbox(x); } diff --git a/test/imports/reexport_items_main.solc b/test/imports/reexport_items_main.solc index 54befbc38..c8bc5b42c 100644 --- a/test/imports/reexport_items_main.solc +++ b/test/imports/reexport_items_main.solc @@ -1,5 +1,5 @@ -import reexport_items.pkg.api.{unwrap, Wrap}; +import {unwrap, Wrap} from reexport_items.pkg.api; -function main() -> word { +function main() returns (word) { return unwrap(Wrap.Mk(1)); } diff --git a/test/imports/reexport_module/pkg/util.solc b/test/imports/reexport_module/pkg/util.solc index af8af05db..3d5bc7229 100644 --- a/test/imports/reexport_module/pkg/util.solc +++ b/test/imports/reexport_module/pkg/util.solc @@ -1,19 +1,19 @@ export {Wrap(*), unwrap, Unbox}; -data Wrap = Mk(word); +enum Wrap { Mk(word) } -forall self . class self:Unbox { - function unbox(x:self) -> word; +trait Unbox { + function unbox(x:self) returns (word); } -instance Wrap:Unbox { - function unbox(x:Wrap) -> word { - match x { - | Wrap.Mk(w) => return w; - } +impl Unbox { + function unbox(x:Wrap) returns (word) { + match (x ) { + case Wrap.Mk(w) { return w; + } } } } -function unwrap(x:Wrap) -> word { +function unwrap(x:Wrap) returns (word) { return Unbox.unbox(x); } diff --git a/test/imports/reexport_module_alias_main.solc b/test/imports/reexport_module_alias_main.solc index 55900f24c..648e2e787 100644 --- a/test/imports/reexport_module_alias_main.solc +++ b/test/imports/reexport_module_alias_main.solc @@ -1,5 +1,5 @@ import reexport_module.pkg.api_alias; -function main() -> word { +function main() returns (word) { return api_alias.Utils.unwrap(api_alias.Utils.Wrap.Mk(1)); } diff --git a/test/imports/reexport_module_main.solc b/test/imports/reexport_module_main.solc index 396eccaa1..96c1e546e 100644 --- a/test/imports/reexport_module_main.solc +++ b/test/imports/reexport_module_main.solc @@ -1,5 +1,5 @@ import reexport_module.pkg.api; -function main() -> word { +function main() returns (word) { return api.util.unwrap(api.util.Wrap.Mk(1)); } diff --git a/test/imports/reexport_select_alias_main.solc b/test/imports/reexport_select_alias_main.solc index b2754ef19..970c881d3 100644 --- a/test/imports/reexport_select_alias_main.solc +++ b/test/imports/reexport_select_alias_main.solc @@ -1,5 +1,5 @@ -import reexport_select_alias_wrapper.{keep_}; +import {keep_} from reexport_select_alias_wrapper; -function main(x: word) -> word { +function main(x: word) returns (word) { return keep_(x); } diff --git a/test/imports/reexport_select_alias_wrapper.solc b/test/imports/reexport_select_alias_wrapper.solc index c3b5dc9d5..fac4bf0fd 100644 --- a/test/imports/reexport_select_alias_wrapper.solc +++ b/test/imports/reexport_select_alias_wrapper.solc @@ -1,3 +1,3 @@ -import selectlib.{keep as keep_}; +import {keep as keep_} from selectlib; export { keep_ }; diff --git a/test/imports/reexport_select_base.solc b/test/imports/reexport_select_base.solc index 3bafbc63f..310722d5d 100644 --- a/test/imports/reexport_select_base.solc +++ b/test/imports/reexport_select_base.solc @@ -1,5 +1,5 @@ export { mstore }; -function mstore(x: word) -> word { +function mstore(x: word) returns (word) { return x; } diff --git a/test/imports/reexport_select_main.solc b/test/imports/reexport_select_main.solc index 484766776..9a0330219 100644 --- a/test/imports/reexport_select_main.solc +++ b/test/imports/reexport_select_main.solc @@ -1,5 +1,5 @@ -import reexport_select_wrapper.{mstore}; +import {mstore} from reexport_select_wrapper; -function main(x: word) -> word { +function main(x: word) returns (word) { return mstore(x); } diff --git a/test/imports/reexport_select_wrapper.solc b/test/imports/reexport_select_wrapper.solc index a6ea114e7..097fb4bd0 100644 --- a/test/imports/reexport_select_wrapper.solc +++ b/test/imports/reexport_select_wrapper.solc @@ -1,3 +1,3 @@ -import reexport_select_base.{mstore}; +import {mstore} from reexport_select_base; export { mstore }; diff --git a/test/imports/rootcheck/nested/main.solc b/test/imports/rootcheck/nested/main.solc index be1d26534..e525eaf1b 100644 --- a/test/imports/rootcheck/nested/main.solc +++ b/test/imports/rootcheck/nested/main.solc @@ -1,5 +1,5 @@ import lib.rootcheck.provider; -function main() -> word { +function main() returns (word) { return provider.value(); } diff --git a/test/imports/rootcheck/nested/provider.solc b/test/imports/rootcheck/nested/provider.solc index a269930d8..c49577b8c 100644 --- a/test/imports/rootcheck/nested/provider.solc +++ b/test/imports/rootcheck/nested/provider.solc @@ -1,5 +1,5 @@ export {value}; -function value() -> word { +function value() returns (word) { return 11; } diff --git a/test/imports/rootcheck/nested/relative_and_lib_main.solc b/test/imports/rootcheck/nested/relative_and_lib_main.solc index 37e0223be..f224a512d 100644 --- a/test/imports/rootcheck/nested/relative_and_lib_main.solc +++ b/test/imports/rootcheck/nested/relative_and_lib_main.solc @@ -1,7 +1,7 @@ import provider; -import lib.rootcheck.provider as RootProvider; +import * as RootProvider from lib.rootcheck.provider; -function main() -> word { +function main() returns (word) { let rootValue: word = RootProvider.value(); return provider.value(); } diff --git a/test/imports/rootcheck/provider.solc b/test/imports/rootcheck/provider.solc index 46073d4d5..aaa998653 100644 --- a/test/imports/rootcheck/provider.solc +++ b/test/imports/rootcheck/provider.solc @@ -1,5 +1,5 @@ export {value}; -function value() -> word { +function value() returns (word) { return 7; } diff --git a/test/imports/select_alias_item_ok.solc b/test/imports/select_alias_item_ok.solc index 7a264705d..38ab250e3 100644 --- a/test/imports/select_alias_item_ok.solc +++ b/test/imports/select_alias_item_ok.solc @@ -1,5 +1,5 @@ -import selectlib.{keep as keep_}; +import {keep as keep_} from selectlib; -function main(x: word) -> word { +function main(x: word) returns (word) { return keep_(x); } diff --git a/test/imports/select_alias_multi_ok.solc b/test/imports/select_alias_multi_ok.solc index f3bc28b4c..33de2fb74 100644 --- a/test/imports/select_alias_multi_ok.solc +++ b/test/imports/select_alias_multi_ok.solc @@ -1,5 +1,5 @@ -import selectlib.{keep as keep_, drop as drop_}; +import {keep as keep_, drop as drop_} from selectlib; -function main(x: word) -> word { +function main(x: word) returns (word) { return drop_(keep_(x)); } diff --git a/test/imports/select_alias_tail_fail.solc b/test/imports/select_alias_tail_fail.solc index ca1765fb1..db1f87aea 100644 --- a/test/imports/select_alias_tail_fail.solc +++ b/test/imports/select_alias_tail_fail.solc @@ -1,5 +1,5 @@ -import selectlib.{keep} as keep_; +import {keep} from selectlib as keep_; -function main(x: word) -> word { +function main(x: word) returns (word) { return keep_(x); } diff --git a/test/imports/select_dup_item.solc b/test/imports/select_dup_item.solc index c61b16547..e87f1e4d9 100644 --- a/test/imports/select_dup_item.solc +++ b/test/imports/select_dup_item.solc @@ -1,5 +1,5 @@ -import selectlib.{keep, keep}; +import {keep, keep} from selectlib; -function main(x: word) -> word { +function main(x: word) returns (word) { return keep(x); } diff --git a/test/imports/select_fail.solc b/test/imports/select_fail.solc index 02a1c4b7b..a718ca675 100644 --- a/test/imports/select_fail.solc +++ b/test/imports/select_fail.solc @@ -1,5 +1,5 @@ -import selectlib.{keep}; +import {keep} from selectlib; -function main(x: word) -> word { +function main(x: word) returns (word) { return drop(x); } diff --git a/test/imports/select_hiding_fail.solc b/test/imports/select_hiding_fail.solc index 901f71860..53f229665 100644 --- a/test/imports/select_hiding_fail.solc +++ b/test/imports/select_hiding_fail.solc @@ -1,5 +1,5 @@ -import selectlib.{keep, drop} hiding {drop}; +import {keep, drop} from selectlib hiding {drop}; -function main(x: word) -> word { +function main(x: word) returns (word) { return drop(x); } diff --git a/test/imports/select_hiding_ok.solc b/test/imports/select_hiding_ok.solc index 806aa1257..bfc57cfba 100644 --- a/test/imports/select_hiding_ok.solc +++ b/test/imports/select_hiding_ok.solc @@ -1,5 +1,5 @@ -import selectlib.{keep, drop} hiding {drop}; +import {keep, drop} from selectlib hiding {drop}; -function main(x: word) -> word { +function main(x: word) returns (word) { return keep(x); } diff --git a/test/imports/select_ok.solc b/test/imports/select_ok.solc index 8d0ae999d..131523523 100644 --- a/test/imports/select_ok.solc +++ b/test/imports/select_ok.solc @@ -1,5 +1,5 @@ -import selectlib.{keep}; +import {keep} from selectlib; -function main(x: word) -> word { +function main(x: word) returns (word) { return keep(x); } diff --git a/test/imports/select_shadow_local.solc b/test/imports/select_shadow_local.solc index 4c854e333..276a16643 100644 --- a/test/imports/select_shadow_local.solc +++ b/test/imports/select_shadow_local.solc @@ -1,9 +1,9 @@ -import selectlib.{keep}; +import {keep} from selectlib; -function keep() -> word { +function keep() returns (word) { return 10; } -function main() -> word { +function main() returns (word) { return keep(); } diff --git a/test/imports/select_shadow_param_ok.solc b/test/imports/select_shadow_param_ok.solc index d619f498f..e12cbea2c 100644 --- a/test/imports/select_shadow_param_ok.solc +++ b/test/imports/select_shadow_param_ok.solc @@ -1,5 +1,5 @@ -import selectlib.{keep}; +import {keep} from selectlib; -function main(keep: word) -> word { +function main(keep: word) returns (word) { return keep; } diff --git a/test/imports/select_unknown.solc b/test/imports/select_unknown.solc index c4ed6b150..677074db4 100644 --- a/test/imports/select_unknown.solc +++ b/test/imports/select_unknown.solc @@ -1,5 +1,5 @@ -import selectlib.{missing}; +import {missing} from selectlib; -function main(x: word) -> word { +function main(x: word) returns (word) { return x; } diff --git a/test/imports/selective_unqualified_fun_ok.solc b/test/imports/selective_unqualified_fun_ok.solc index f3b631bca..eb490d135 100644 --- a/test/imports/selective_unqualified_fun_ok.solc +++ b/test/imports/selective_unqualified_fun_ok.solc @@ -1,5 +1,5 @@ -import foo.{base}; +import {base} from foo; -function main() -> word { +function main() returns (word) { return base(); } diff --git a/test/imports/selectlib.solc b/test/imports/selectlib.solc index 60fe6f7c2..0ef4bed91 100644 --- a/test/imports/selectlib.solc +++ b/test/imports/selectlib.solc @@ -1,9 +1,9 @@ export { keep, drop }; -function keep(x: word) -> word { +function keep(x: word) returns (word) { return x; } -function drop(x: word) -> word { +function drop(x: word) returns (word) { return x; } diff --git a/test/imports/selfcycle.solc b/test/imports/selfcycle.solc index 99aff9cc6..69b8fc0fd 100644 --- a/test/imports/selfcycle.solc +++ b/test/imports/selfcycle.solc @@ -1,5 +1,5 @@ import selfcycle; -function main() -> word { +function main() returns (word) { return 0; } diff --git a/test/imports/strict_open_fail.solc b/test/imports/strict_open_fail.solc index 6561a3a16..18bfb93d0 100644 --- a/test/imports/strict_open_fail.solc +++ b/test/imports/strict_open_fail.solc @@ -1,5 +1,5 @@ import booldef; -function bad(b: Bool) -> Bool { +function bad(b: Bool) returns (Bool) { return not(b); } diff --git a/test/imports/symlink_identity_fail.solc b/test/imports/symlink_identity_fail.solc index 56338bb25..eb2f260bd 100644 --- a/test/imports/symlink_identity_fail.solc +++ b/test/imports/symlink_identity_fail.solc @@ -1,6 +1,6 @@ -import vendor.math.api as Vendor; -import mirror.api as Mirror; +import * as Vendor from vendor.math.api; +import * as Mirror from mirror.api; -function bad(x: Vendor.T) -> Mirror.T { +function bad(x: Vendor.T) returns (Mirror.T) { return x; } diff --git a/test/imports/transitive_dep_base.solc b/test/imports/transitive_dep_base.solc index 536900773..7f973e9ad 100644 --- a/test/imports/transitive_dep_base.solc +++ b/test/imports/transitive_dep_base.solc @@ -1,5 +1,5 @@ export { g }; -function g() -> word { +function g() returns (word) { return 1; } diff --git a/test/imports/transitive_dep_main_module.solc b/test/imports/transitive_dep_main_module.solc index 767369272..fbe4ea8b6 100644 --- a/test/imports/transitive_dep_main_module.solc +++ b/test/imports/transitive_dep_main_module.solc @@ -1,5 +1,5 @@ -import transitive_dep_mid as M; +import * as M from transitive_dep_mid; -function main() -> word { +function main() returns (word) { return M.f(); } diff --git a/test/imports/transitive_dep_main_select.solc b/test/imports/transitive_dep_main_select.solc index 87deb02b6..cb0010df0 100644 --- a/test/imports/transitive_dep_main_select.solc +++ b/test/imports/transitive_dep_main_select.solc @@ -1,5 +1,5 @@ -import transitive_dep_mid.{f}; +import {f} from transitive_dep_mid; -function main() -> word { +function main() returns (word) { return f(); } diff --git a/test/imports/transitive_dep_mid.solc b/test/imports/transitive_dep_mid.solc index 1164443e8..8b1f863fa 100644 --- a/test/imports/transitive_dep_mid.solc +++ b/test/imports/transitive_dep_mid.solc @@ -1,7 +1,7 @@ -import transitive_dep_base.{g}; +import {g} from transitive_dep_base; export { f }; -function f() -> word { +function f() returns (word) { return g(); } diff --git a/test/imports/type_collision_a.solc b/test/imports/type_collision_a.solc index cf8fc3056..49de8fea5 100644 --- a/test/imports/type_collision_a.solc +++ b/test/imports/type_collision_a.solc @@ -1,7 +1,7 @@ export { T(A), mk }; -data T = A; +enum T { A } -function mk() -> T { +function mk() returns (T) { return T.A; } diff --git a/test/imports/type_collision_b.solc b/test/imports/type_collision_b.solc index 9a4857a1a..26cdca896 100644 --- a/test/imports/type_collision_b.solc +++ b/test/imports/type_collision_b.solc @@ -1,7 +1,7 @@ export { T(B), mk }; -data T = B; +enum T { B } -function mk() -> T { +function mk() returns (T) { return T.B; } diff --git a/test/imports/type_collision_main.solc b/test/imports/type_collision_main.solc index c190d2c7a..68aaa5ceb 100644 --- a/test/imports/type_collision_main.solc +++ b/test/imports/type_collision_main.solc @@ -1,7 +1,7 @@ import type_collision_a; import type_collision_b; -function main() -> word { +function main() returns (word) { let x = type_collision_a.mk(); let y = type_collision_b.mk(); return 0; diff --git a/test/imports/unordered_imports_lib.solc b/test/imports/unordered_imports_lib.solc index 0b596b691..b2a5039c8 100644 --- a/test/imports/unordered_imports_lib.solc +++ b/test/imports/unordered_imports_lib.solc @@ -1,10 +1,10 @@ export { Bool(*), not }; -data Bool = True | False; +enum Bool { True, False } -function not(b : Bool) -> Bool { - match b { - | Bool.True => return Bool.False; - | Bool.False => return Bool.True; - } +function not(b : Bool) returns (Bool) { + match (b ) { + case Bool.True { return Bool.False; + } case Bool.False { return Bool.True; + } } } diff --git a/test/imports/unordered_imports_main.solc b/test/imports/unordered_imports_main.solc index d9b608fbc..95cf002f2 100644 --- a/test/imports/unordered_imports_main.solc +++ b/test/imports/unordered_imports_main.solc @@ -1,8 +1,8 @@ export { main }; -pragma no-patterson-condition; +pragma solcore noPattersonCondition; -function main(b : unordered_imports_lib.Bool) -> unordered_imports_lib.Bool { +function main(b : unordered_imports_lib.Bool) returns (unordered_imports_lib.Bool) { return unordered_imports_lib.not(b); } diff --git a/test/imports/vendor/math/helper.solc b/test/imports/vendor/math/helper.solc index d2d38ce76..f03838e76 100644 --- a/test/imports/vendor/math/helper.solc +++ b/test/imports/vendor/math/helper.solc @@ -1,3 +1,3 @@ export {T}; -data T = T; +enum T { T } diff --git a/test/imports/wildA.solc b/test/imports/wildA.solc index e9cc46617..eaf999ffd 100644 --- a/test/imports/wildA.solc +++ b/test/imports/wildA.solc @@ -1,6 +1,6 @@ import wildB; export {wildB.*, *}; -function fromWildA() -> word { +function fromWildA() returns (word) { return wildB.fromWildB(); } diff --git a/test/imports/wildB.solc b/test/imports/wildB.solc index 2b4ed1c0f..1f4903f80 100644 --- a/test/imports/wildB.solc +++ b/test/imports/wildB.solc @@ -1,6 +1,6 @@ import wildA; export {wildA.*, *}; -function fromWildB() -> word { +function fromWildB() returns (word) { return 3; } diff --git a/test/imports/wild_main.solc b/test/imports/wild_main.solc index 11bf7e235..70f90751d 100644 --- a/test/imports/wild_main.solc +++ b/test/imports/wild_main.solc @@ -1,5 +1,5 @@ import wildA; -function main() -> word { +function main() returns (word) { return wildA.fromWildB(); } diff --git a/test/imports/wrapper_shadow_success.solc b/test/imports/wrapper_shadow_success.solc index 2e516ded7..809ec8ad3 100644 --- a/test/imports/wrapper_shadow_success.solc +++ b/test/imports/wrapper_shadow_success.solc @@ -1,9 +1,9 @@ import booldef; -function not(x: word) -> word { +function not(x: word) returns (word) { return x; } -function main(b: booldef.Bool) -> booldef.Bool { +function main(b: booldef.Bool) returns (booldef.Bool) { return booldef.not(b); } From 94c37e209d3a497a20872d2f617f6546c90ce458 Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Thu, 23 Jul 2026 18:32:42 +0900 Subject: [PATCH 03/33] Document the new source syntax --- doc/module-system.md | 53 +- doc/railroad/sail.bnf | 389 +++++---- doc/src/sail/README.md | 10 +- doc/src/sail/assembly.md | 34 +- doc/src/sail/builtins.md | 102 +-- doc/src/sail/datatypes.md | 222 +++-- doc/src/sail/functions.md | 162 ++-- doc/src/sail/modules.md | 224 +++-- doc/src/sail/parametric-polymorphism.md | 162 ++-- doc/src/sail/syntax.md | 765 +++++++----------- doc/src/sail/type-inference.md | 151 ++-- doc/src/sail/typeclasses.md | 295 ++++--- .../variable-declaration-and-assignment.md | 104 +-- 13 files changed, 1372 insertions(+), 1301 deletions(-) diff --git a/doc/module-system.md b/doc/module-system.md index 46dfe0288..64403b2ca 100644 --- a/doc/module-system.md +++ b/doc/module-system.md @@ -18,7 +18,8 @@ This document describes the intended Solcore module and namespace system. - the main library - the std library - named external libraries -- `foo.bar` maps to `foo/bar.solc`. +- `foo.bar` maps canonically to `foo/bar.sol`. The prototype may temporarily + resolve `.solc` files as an implementation detail. - Directories are not modules. - `foo` and `foo.bar` therefore refer to different files. - The source file path is stored separately from module identity. @@ -30,11 +31,11 @@ Supported forms: ```solidity import M; -import M as A; -import M.{X, Y}; -import M.{X as Z}; -import M.{*}; -import M.{*} hiding {X}; +import * as A from M; +import {X, Y} from M; +import {X as Z} from M; +import {*} from M; +import {*} from M hiding {X}; import lib.foo.bar; import @ext.foo.bar; ``` @@ -51,7 +52,8 @@ Import path kinds: Current std-specific behavior: - `import std;` resolves to the std library root from any library. -- `import std.dispatch;` resolves to `dispatch.solc` under the std root. +- `import std.dispatch;` resolves canonically to `dispatch.sol` under the std + root (the prototype may still use `dispatch.solc`). - Bare imports do not fall back to the std root. - Imports do not have constructor-specific selector syntax. Exported constructors are accessed through qualified constructor paths such as `T.C`, `M.T.C`, or `alias.T.C`. @@ -59,19 +61,19 @@ Current std-specific behavior: ## 4. Import Visibility and Qualification - `import M;` does not open names into unqualified scope. -- `import M as A;` binds only `A`. -- `import M.{X, Y};` imports selected exported names into unqualified scope. -- `import M.{X as Z};` imports `X` into unqualified scope as `Z`. -- `as` after a selector block, such as `import M.{X} as Z;`, is rejected. -- `import M.{*};` imports all exported item names into unqualified scope. -- `import M.{...} hiding {X, Y};` removes names from the selector result after expansion. +- `import * as A from M;` binds only `A`. +- `import {X, Y} from M;` imports selected exported names into unqualified scope. +- `import {X as Z} from M;` imports `X` into unqualified scope as `Z`. +- `as` after a selector block, such as `import {X} from M as Z;`, is rejected. +- `import {*} from M;` imports all exported item names into unqualified scope. +- `import {...} from M hiding {X, Y};` removes names from the selector result after expansion. - Items inside `{...}` may mix simple item names and `*`. Dotted item paths are not supported there. Default module bindings: - `import foo.bar;` binds `bar`. -- `import foo.bar as B;` binds `B` and does not bind `bar`. +- `import * as B from foo.bar;` binds `B` and does not bind `bar`. - Non-alias module imports also support full-path qualification, so `import foo.bar;` allows both `bar.x` and `foo.bar.x`. - If two imports would bind the same final segment, it is an error. For example, `import foo.bar; import baz.bar;` is rejected. @@ -87,6 +89,10 @@ Validation rules: ## 5. Export Syntax and Public Interfaces +The canonical new syntax deliberately leaves export and re-export spelling +undecided. The forms below document the current compiler extension; they are +not a commitment in the language syntax proposal. + Supported forms: ```solidity @@ -142,10 +148,10 @@ Validation rules: - Re-exporting two different module targets under the same public module name is rejected. - Repeated exports of the same underlying item are normalized, so forms such as `export {main, *};` are accepted. -Instance behavior: +Impl behavior: -- Instances are import-visible whenever their defining module is imported. -- Instances are not named individually in export lists. +- Implementations are import-visible whenever their defining module is imported. +- Implementations are not named individually in export lists. ## 6. Namespaces and Name Resolution @@ -155,7 +161,7 @@ Current duplicate checking is enforced separately for: - contracts - data types - type synonyms - - classes + - traits - the term namespace - functions - constructors @@ -164,7 +170,8 @@ Current duplicate checking is enforced separately for: Unqualified lookup order: 1. Local lexical scope -2. Current module top-level declarations and names introduced by `import M.{...}` / `import M.{*}` are treated at the same priority +2. Current module top-level declarations and names introduced by + `import {...} from M` / `import {*} from M` are treated at the same priority 3. If that combined non-local set contains more than one candidate in the same namespace, validation fails with a hard error 4. Otherwise unresolved @@ -173,7 +180,8 @@ Current behavior: - Local parameters and local variables still shadow non-local names. - Current-module top-level names no longer silently shadow selected or glob-imported names. - Selected or glob-imported names no longer silently shadow current-module top-level names. -- Re-importing the same underlying declaration is normalized, so importing `std.{*}` and then `std.{uint256}` does not fail by itself. +- Re-importing the same underlying declaration is normalized, so importing + `{*}` and then `{uint256}` from `std` does not fail by itself. ## 7. Constructors and Dot Shorthand @@ -197,7 +205,7 @@ Examples of accepted source forms: Current behavior: - Bare constructor names are not resolved by default in the qualified-constructor model. -- `data Foo = Foo` remains valid because type and term namespaces are separate. +- `enum Foo { Foo }` remains valid because type and term namespaces are separate. - A hidden constructor cannot be named from another module in either expressions or patterns. Dot shorthand: @@ -235,7 +243,8 @@ Pattern matching and exhaustiveness: ## 10. External Libraries - External libraries are configured with `--lib NAME=DIR`. -- Source code imports them with `@NAME.module.path`. +- Source code imports them with `import @NAME.module.path;` (or another new + import form with that dotted path). - The external library name is part of module identity. - Relative imports inside an external library stay within that external library. - `lib.*` inside an external library resolves from that external library's root. diff --git a/doc/railroad/sail.bnf b/doc/railroad/sail.bnf index 52339d29b..8276bba8f 100644 --- a/doc/railroad/sail.bnf +++ b/doc/railroad/sail.bnf @@ -1,70 +1,67 @@ # SAIL Language Grammar +# +# This file describes the Solidity-style Core surface. Export productions, +# glob/hiding imports, default impls, contextual constructors, and lambdas are +# current compiler extensions. ## Lexical Tokens -Identifier = letter { letter | digit | "_" } +Identifier = ( letter | "_" ) { letter | digit | "_" } Integer = digit { digit } | "0x" hexDigit { hexDigit } -StringLiteral = '"' { char } '"' +StringLiteral = '"' { char | EscapeSequence } '"' + +EscapeSequence = "\" ( "\" | '"' | "n" | "t" | "r" ) + ## Names -TypeName = Identifier - | TypeName "." Identifier +QualifiedName = Identifier { "." Identifier } + +ModulePath = [ "@" Identifier "." ] Identifier { "." Identifier } ## Compilation Unit -# Imports and top-level declarations may be interleaved in any order. -CompilationUnit = { Import | TopDecl } +# Imports, pragmas, and top-level declarations may be interleaved. +CompilationUnit = { Import | Pragma | TopDecl } TopDecl = Contract + | Interface + | Library | Function - | ClassDef - | InstDef - | DataDef - | TypeSynonym + | TraitDef + | ImplDef + | StructDef + | EnumDef + | TypeDef | ExportDecl - | Pragma - | OperatorDecl ## Module System Import = "import" ModulePath ";" - | "import" ModulePath "as" Identifier ";" - | "import" ModulePath "." "{" ImportItems "}" [ "hiding" "{" HidingList "}" ] ";" - | "import" "@" Identifier "." ModulePath ";" - | "import" "@" Identifier "." ModulePath "as" Identifier ";" - | "import" "@" Identifier "." ModulePath "." "{" ImportItems "}" [ "hiding" "{" HidingList "}" ] ";" - -ModulePath = Identifier { "." Identifier } + | "import" "*" "as" Identifier "from" ModulePath ";" + | "import" "{" ImportItems "}" "from" ModulePath + [ "hiding" "{" IdentifierList "}" ] ";" ImportItems = ImportItem { "," ImportItem } ImportItem = "*" | Identifier | Identifier "as" Identifier - | "(" OperatorSymbol ")" - -# OperatorSymbol is a non-empty sequence of operator characters: -# ASCII: + - * / % < > = ! & | ^ ~ # ? -# Unicode: U+2200..U+22FF (mathematical operators) and U+2300..U+23FF -OperatorSymbol = opChar { opChar } -HidingList = Identifier { "," Identifier } +IdentifierList = Identifier { "," Identifier } +# Export spelling remains an implementation extension pending a final +# Classic/Core interoperability model. ExportDecl = "export" "{" [ ExportItems ] "}" ";" | "export" ModulePath ";" | "export" ModulePath "as" Identifier ";" | "export" ModulePath "." "{" [ ExportFromItems ] "}" ";" | "export" ModulePath "." "*" ";" - | "export" "@" Identifier "." ModulePath ";" - | "export" "@" Identifier "." ModulePath "as" Identifier ";" - | "export" "@" Identifier "." ModulePath "." "{" [ ExportFromItems ] "}" ";" - | "export" "@" Identifier "." ModulePath "." "*" ";" ExportItems = ExportItem { "," ExportItem } @@ -72,8 +69,6 @@ ExportItem = "*" | Identifier | Identifier "(" ExportConstructors ")" | ModulePath "." "*" - | "@" Identifier "." ModulePath "." "*" - | "(" OperatorSymbol ")" ExportConstructors = "*" | Identifier { "," Identifier } @@ -87,49 +82,76 @@ ExportFromItem = "*" ## Pragmas -Pragma = "pragma" PragmaKind [ PragmaTargets ] ";" +Pragma = "pragma" "solidity" PragmaValue ";" + | "pragma" "abicoder" PragmaValue ";" + | "pragma" "solcore" SolcorePragma [ PragmaTargets ] ";" + +# Version ranges and ABI coder versions are preserved as pragma payload text. +PragmaValue = PragmaToken { PragmaToken } -PragmaKind = "no-coverage-condition" - | "no-patterson-condition" - | "no-bounded-variable-condition" +PragmaToken = Identifier | Integer | "." | "^" | "~" + | "<" | ">" | "<=" | ">=" | "=" | "-" + +SolcorePragma = "noCoverageCondition" + | "noPattersonCondition" + | "noBoundVariableCondition" + | "noGenericInstanceFor" PragmaTargets = Identifier { "," Identifier } ## Types -Type = TypeName [ "(" TypeList ")" ] - | "(" TypeList ")" "->" Type - | "(" TypeList ")" - | "@" Type +Type = TypeAtom { ArraySuffix } [ DataLocation ] + +TypeAtom = QualifiedName [ GenericArguments ] + | "mapping" "(" Type "=>" Type ")" + | "(" ")" + | "(" Type ")" + | "(" Type "," TypeList ")" + | FunctionType + +GenericArguments = "<" TypeList ">" TypeList = Type { "," Type } -# Space-separated type variable names in forall quantifiers (e.g. "forall a b c ."). -TypeVarSeq = { Identifier } +ArraySuffix = "[" [ ArraySize ] "]" + +ArraySize = Integer | Type + +DataLocation = "memory" | "storage" | "calldata" + +FunctionType = "function" "(" [ TypeList ] ")" + [ FunctionVisibility ] + [ "returns" "(" [ TypeList ] ")" ] -# Comma-separated type variable names in parentheses, used in type, data, and -# class declarations (e.g. "(a, b)"). -TypeVarParams = "(" Identifier { "," Identifier } ")" +FunctionVisibility = "internal" | "external" +ReturnItemList = ReturnItem { "," ReturnItem } -# ── Data Types ──────────────────────────────────────────────────────────────── +ReturnItem = [ "comptime" ] [ Identifier ":" ] Type -DataDef = "data" Identifier [ TypeVarParams ] [ "=" DataConstrs ] ";" +GenericParams = "<" IdentifierList ">" -DataConstrs = DataConstr { "|" DataConstr } -DataConstr = Identifier [ "(" TypeList ")" ] +## Structs, Enums, and User-Defined Types +StructDef = "struct" Identifier [ GenericParams ] + "{" { StructField } "}" -# ── Type Synonyms ───────────────────────────────────────────────────────────── +StructField = Identifier ":" Type ";" -TypeSynonym = "type" Identifier [ TypeVarParams ] "=" Type ";" +EnumDef = "enum" Identifier [ GenericParams ] + "{" [ EnumVariant { "," EnumVariant } [ "," ] ] "}" +EnumVariant = Identifier [ "(" TypeList ")" ] -# ── Patterns ────────────────────────────────────────────────────────────────── +TypeDef = "type" Identifier [ GenericParams ] "is" Type ";" -Pattern = TypeName [ "(" PatternList ")" ] + +## Patterns + +Pattern = QualifiedName [ "(" PatternList ")" ] | "." Identifier [ "(" PatternList ")" ] | "_" | Literal @@ -139,164 +161,214 @@ Pattern = TypeName [ "(" PatternList ")" ] PatternList = Pattern { "," Pattern } -# ── Expressions ─────────────────────────────────────────────────────────────── - -# Operators follow standard precedence: arithmetic > comparison > logical. -# Associativity: left for binary operators, right for "if-then-else". - -Expr = Identifier "(" [ ExprList ] ")" - | Expr "." Identifier "(" [ ExprList ] ")" - | Expr "." Identifier - | "." Identifier "(" [ ExprList ] ")" - | "." Identifier - | Identifier - | Literal - | "(" Expr ")" - | "(" ")" - | "(" Expr "," Expr { "," Expr } ")" - | "lam" "(" [ ParamList ] ")" [ "->" Type ] Body - | Expr ":" Type - | Expr "[" Expr "]" - | Expr "+" Expr - | Expr "-" Expr - | Expr "*" Expr - | Expr "/" Expr - | Expr "%" Expr - | Expr "<" Expr - | Expr ">" Expr - | Expr "<=" Expr - | Expr ">=" Expr - | Expr "==" Expr - | Expr "!=" Expr - | Expr "&&" Expr - | Expr "||" Expr - | "!" Expr - | "if" Expr "then" Expr "else" Expr - | "@" Type +## Expressions + +Expr = ConditionalExpr + +ConditionalExpr = LogicalOrExpr + [ "?" Expr ":" ConditionalExpr ] + +LogicalOrExpr = LogicalAndExpr { "||" LogicalAndExpr } + +LogicalAndExpr = EqualityExpr { "&&" EqualityExpr } + +EqualityExpr = BitwiseOrExpr { ( "==" | "!=" ) BitwiseOrExpr } + +BitwiseOrExpr = BitwiseXorExpr { "|" BitwiseXorExpr } + +BitwiseXorExpr = BitwiseAndExpr { "^" BitwiseAndExpr } + +BitwiseAndExpr = RelationalExpr { "&" RelationalExpr } + +RelationalExpr = ShiftExpr + { ( "<" | ">" | "<=" | ">=" ) ShiftExpr } + +ShiftExpr = AdditiveExpr { ( "<<" | ">>" ) AdditiveExpr } + +AdditiveExpr = MultiplicativeExpr + { ( "+" | "-" ) MultiplicativeExpr } + +MultiplicativeExpr = PowerExpr + { ( "*" | "/" | "%" ) PowerExpr } + +# Power is right-associative. +PowerExpr = CastExpr [ "**" PowerExpr ] + +# Conversion is left-associative and accepts the complete type grammar. +CastExpr = UnaryExpr { "as" Type } + +UnaryExpr = "!" UnaryExpr + | PostfixExpr + +PostfixExpr = PrimaryExpr { PostfixSuffix } + +PostfixSuffix = "(" [ ExprList ] ")" + | "." Identifier + | "[" Expr "]" + +PrimaryExpr = QualifiedName + | "." Identifier [ "(" [ ExprList ] ")" ] + | Literal + | "(" ")" + | "(" Expr ")" + | "(" Expr "," ExprList ")" + | LambdaExpr ExprList = Expr { "," Expr } -Literal = Integer - | StringLiteral +LambdaExpr = "lam" "(" [ LambdaParamList ] ")" + [ "returns" "(" [ TypeList ] ")" ] + Body + +LambdaParamList = LambdaParam { "," LambdaParam } + +LambdaParam = [ "comptime" ] Identifier [ ":" Type ] + +Literal = Integer | StringLiteral | "true" | "false" + +## Statements -# ── Statements ──────────────────────────────────────────────────────────────── +Body = "{" { Stmt } "}" -Stmt = Expr "=" Expr ";" - | Expr "+=" Expr ";" - | Expr "-=" Expr ";" - | "let" Identifier ":" Type [ "=" Expr ] ";" - | "let" Identifier [ "=" Expr ] ";" - | Expr ";" - | "return" Expr ";" - | "match" MatchArgs "{" { Equation } "}" +Stmt = SimpleStmt ";" + | "return" [ Expr ] ";" + | MatchStmt | AsmBlock | "if" "(" Expr ")" Body [ "else" Body ] - | "for" "(" ForInitStmt ";" Expr ";" ForPostStmt ")" Body + | "for" "(" [ ForInitClause ] ";" Expr ";" + [ ForPostClause ] ")" Body + | "while" "(" Expr ")" Body + | "unchecked" Body + | "break" ";" + | "continue" ";" + | "revert" ";" + | Body -Body = "{" { Stmt } "}" +SimpleStmt = LetStmt + | Assignment + | Expr -# The initialisation clause of a for-loop. Unlike a regular statement it -# has no trailing semicolon; the semicolons are part of the for(…) syntax. -ForInitStmt = Expr "=" Expr - | Expr "+=" Expr - | Expr "-=" Expr - | "let" Identifier ":" Type [ "=" Expr ] - | "let" Identifier [ "=" Expr ] - | Expr +LetStmt = "let" [ "comptime" ] ( + Identifier [ ":" Type ] [ "=" Expr ] + | BindingTuple [ ":" Type ] "=" Expr + ) -# The post-iteration clause of a for-loop. Let-bindings are allowed here; -# a binding introduced in ForPostStmt is in scope for the body of that -# iteration only. -ForPostStmt = Expr "=" Expr - | Expr "+=" Expr - | Expr "-=" Expr - | "let" Identifier ":" Type [ "=" Expr ] - | "let" Identifier [ "=" Expr ] - | Expr +BindingTuple = "(" BindingPattern "," BindingPatternList ")" -MatchArgs = Expr { "," Expr } +BindingPattern = Identifier | "_" | BindingTuple -# A match equation: each arm binds a list of patterns to a list of statements. -Equation = "|" PatternList "=>" { Stmt } +BindingPatternList = BindingPattern { "," BindingPattern } +Assignment = Expr AssignmentOperator Expr -# ── Parameters ──────────────────────────────────────────────────────────────── +AssignmentOperator = "=" | "+=" | "-=" | "%=" + | "&=" | "|=" | "^=" -Param = Identifier ":" Type - | Identifier +ForInitClause = ForInitItem { "," ForInitItem } -ParamList = Param { "," Param } +ForInitItem = LetStmt | Assignment | Expr +ForPostClause = ForPostItem { "," ForPostItem } -# ── Functions ───────────────────────────────────────────────────────────────── +ForPostItem = Assignment | Expr -# Long form uses a statement block; short form uses a single return expression -# (the expression result is returned implicitly). +MatchStmt = "match" "(" ExprList ")" "{" { MatchArm } "}" -Function = Signature Body - | Signature "{" Expr "}" +MatchArm = "case" MatchPattern Body + | "default" Body -Signature = SigPrefix "function" Identifier "(" [ ParamList ] ")" [ "->" Type ] +MatchPattern = Pattern + | "(" PatternList ")" -# Polymorphic signatures begin with a forall prefix. If there are constraints -# they are listed before the "=>" arrow. -SigPrefix = "forall" TypeVarSeq "." ConstraintList "=>" - | "forall" TypeVarSeq "." - | +## Parameters and Constraints -ConstraintList = Constraint { "," Constraint } +# The parser admits an omitted annotation so semantic analysis can issue a +# focused diagnostic. Top-level and contract function parameters must be +# fully annotated. +Param = [ "comptime" ] Identifier [ ":" Type ] + +ParamList = Param { "," Param } -# A constraint asserts that a type belongs to a type class, optionally with -# additional type parameters (e.g. "a : Functor", "n : Nth(b, c)"). +WhereClause = "where" ConstraintList -Constraint = Type ":" TypeName [ "(" TypeList ")" ] +ConstraintList = Constraint { "," Constraint } +Constraint = Type ":" QualifiedName [ GenericArguments ] -# ── Type Classes ────────────────────────────────────────────────────────────── -# A class declaration introduces a new type class named ClassName. -# The self-variable (e.g. "a") is the main type being constrained. -# Additional type parameters (e.g. "(b, c)") are auxiliary associated types. +## Functions, Traits, and Implementations -ClassDef = SigPrefix "class" Identifier ":" Identifier [ TypeVarParams ] "{" { Signature ";" } "}" +Function = Signature Body +Signature = "function" Identifier [ GenericParams ] + "(" [ ParamList ] ")" + { FunctionAttribute } + [ "returns" "(" [ ReturnItemList ] ")" ] + [ WhereClause ] -# ── Instances ───────────────────────────────────────────────────────────────── +FunctionAttribute = "public" + | "private" + | "external" + | "internal" + | "pure" + | "view" + | "payable" -# An instance declaration provides implementations of class methods for a -# specific type. The optional "default" keyword marks overlappable instances. +TraitDef = "trait" Identifier GenericParams + [ WhereClause ] + "{" { Signature ";" } "}" -InstDef = SigPrefix [ "default" ] "instance" Type ":" TypeName [ "(" TypeList ")" ] "{" { Function } "}" +ImplDef = [ "default" ] "impl" [ GenericParams ] + QualifiedName GenericArguments + [ WhereClause ] + "{" { Function } "}" -# ── Contracts ───────────────────────────────────────────────────────────────── +## Contracts -Contract = "contract" Identifier [ TypeVarParams ] "{" { ContractDecl } "}" +Contract = "contract" Identifier [ GenericParams ] + "{" { ContractDecl } "}" ContractDecl = FieldDecl - | DataDef + | StructDef + | EnumDef | Function | Constructor + | Fallback FieldDecl = Identifier ":" Type [ "=" Expr ] ";" -Constructor = "constructor" "(" [ ParamList ] ")" Body +Constructor = "constructor" "(" [ ParamList ] ")" + [ "payable" ] Body + +# A fallback has exactly one external attribute. Payable is optional. +Fallback = "fallback" "(" ")" "external" [ "payable" ] Body + +Interface = "interface" Identifier [ GenericParams ] + "{" { Signature ";" } "}" + +Library = "library" Identifier [ GenericParams ] + "{" { LibraryDecl } "}" +LibraryDecl = FieldDecl + | StructDef + | EnumDef + | Function -# ── Assembly Blocks (Yul Sublanguage) ───────────────────────────────────────── -# Assembly blocks embed Yul code directly in SAIL. Yul provides low-level EVM -# access: storage operations, arithmetic, and control flow. +## Assembly Blocks (Yul Sublanguage) AsmBlock = "assembly" "{" { YulStmt } "}" YulStmt = YulNames ":=" YulExpr | "let" YulNames [ ":=" YulExpr ] | "if" YulExpr "{" { YulStmt } "}" - | "switch" YulExpr { YulCase } [ "default" "{" { YulStmt } "}" ] - | "for" "{" { YulStmt } "}" YulExpr "{" { YulStmt } "}" "{" { YulStmt } "}" + | "switch" YulExpr { YulCase } + [ "default" "{" { YulStmt } "}" ] + | "for" "{" { YulStmt } "}" YulExpr + "{" { YulStmt } "}" "{" { YulStmt } "}" | "continue" | "break" | "leave" @@ -314,5 +386,4 @@ YulNames = Identifier { "," Identifier } YulExprList = YulExpr { "," YulExpr } -YulLiteral = Integer - | StringLiteral +YulLiteral = Integer | StringLiteral diff --git a/doc/src/sail/README.md b/doc/src/sail/README.md index e9ad82883..3906921f3 100644 --- a/doc/src/sail/README.md +++ b/doc/src/sail/README.md @@ -2,9 +2,9 @@ SAIL (Solidity Advanced Intermediate Language) is the source language of the Core Solidity compiler. It extends Solidity's surface syntax with a -statically-typed functional core: parametric polymorphism via `forall` -quantifiers, type classes for constrained overloading, and algebraic data types -with exhaustive pattern matching. Every SAIL program is compiled to monomorphic +statically-typed functional core: angle-bracketed generic parameters, traits for +constrained overloading, and algebraic data types with exhaustive pattern +matching. Every SAIL program is compiled to monomorphic Core IR, named Hull, through specialization, then translated to Yul and assembled into EVM bytecode. @@ -23,9 +23,9 @@ the most foundational concepts to the most advanced: function body. - **Datatypes** introduces algebraic data type declarations and pattern matching. -- **Parametric Polymorphism** explains `forall` quantifiers, type variable +- **Parametric Polymorphism** explains generic parameter lists, type variable instantiation, and the specialization strategy. -- **Type Classes** covers class declarations, instance declarations, superclass +- **Type Classes** covers trait declarations, impl declarations, superclass constraints, and the three soundness conditions the compiler enforces. - **Modules** describes the import and export system, qualified names, and visibility rules. diff --git a/doc/src/sail/assembly.md b/doc/src/sail/assembly.md index 7e69c6a93..cbbbc7e26 100644 --- a/doc/src/sail/assembly.md +++ b/doc/src/sail/assembly.md @@ -6,8 +6,8 @@ are the primary mechanism for operations that SAIL has no built-in syntax for, such as storage reads and writes, event emission, and ABI encoding helpers. ```solcore -function loadBalance(account : word) -> word { - let bal : word; +function loadBalance(account: word) returns (word) { + let bal: word; assembly { bal := sload(account) } @@ -63,7 +63,7 @@ An assignment in Yul uses `:=`. The left-hand side must be either a Yul variable or a SAIL `word` variable in scope. ```solcore -function storeBalance(account : word, amount : word) -> () { +function storeBalance(account: word, amount: word) { assembly { sstore(account, amount) // EVM opcode: write amount to storage slot account } @@ -73,8 +73,8 @@ function storeBalance(account : word, amount : word) -> () { Assigning to a SAIL variable communicates a result back to the SAIL scope: ```solcore -function getFreeMemPtr() -> word { - let ptr : word; +function getFreeMemPtr() returns (word) { + let ptr: word; assembly { ptr := mload(0x40) } @@ -129,9 +129,9 @@ condition expression, a post-iteration block, and a body block. ```solcore contract ERC20 { - function sumSlots(startSlot : word, count : word) -> word { - let endSlot : word; - let total : word; + function sumSlots(startSlot: word, count: word) returns (word) { + let endSlot: word; + let total: word; assembly { endSlot := add(startSlot, count) } @@ -183,8 +183,8 @@ appears inside Yul must resolve to a variable or parameter whose type is variables are all subject to this rule. ```solcore -function transfer(account : word, amount : word) -> () { - let bal : word; +function transfer(account: word, amount: word) { + let bal: word; assembly { bal := sload(account) // account and bal are SAIL word variables sstore(account, sub(bal, amount)) @@ -202,7 +202,7 @@ type mismatch because Yul has no boolean type and cannot represent the value. ```solcore // Error: bool is not word. -function bad(paused : bool) -> () { +function bad(paused: bool) { assembly { sstore(0, paused) } @@ -211,7 +211,7 @@ function bad(paused : bool) -> () { ``` Types: bool and word do not unify - - in: function bad (paused : bool) -> () { ... } + - in: function bad(paused: bool) { ... } ``` To work with a `bool` value inside an assembly block, convert it to a `word` @@ -219,16 +219,16 @@ first using an explicit conditional in SAIL. ### Rejected: variable of an algebraic data type -A local variable whose type is a user-defined `data` type is equally +A local variable whose type is a user-defined algebraic data type is equally rejected. Sum and product types are not EVM words and have no direct Yul representation. ```solcore -data Result = Ok(word) | Err(word); +enum Result { Ok(word), Err(word) } // Error: Result is not word. -function bad(r : Result) -> word { - let res : word; +function bad(r: Result) returns (word) { + let res: word; assembly { res := r } @@ -238,7 +238,7 @@ function bad(r : Result) -> word { ``` Types: Result and word do not unify - - in: function bad (r : Result) -> word { ... } + - in: function bad(r: Result) returns (word) { ... } ``` To operate on structured values from assembly, extract the relevant `word` diff --git a/doc/src/sail/builtins.md b/doc/src/sail/builtins.md index 1eb5fdb10..135107647 100644 --- a/doc/src/sail/builtins.md +++ b/doc/src/sail/builtins.md @@ -16,13 +16,13 @@ Five types are built into the language kernel. | `word` | 256-bit unsigned integer; the EVM's native machine word | | `bool` | Boolean type with constructors `true` and `false` | | `()` | Unit type; used as the return type of functions that produce no value | -| `pair a b` | Generic product type, also written `(a, b)` in tuple syntax | -| `sum a b` | Generic disjoint union with constructors `inl` and `inr` | +| `pair` | Generic product type, also written `(A, B)` in tuple syntax | +| `sum` | Generic disjoint union with constructors `inl` and `inr` | `pair` and `sum` are the internal representation of all user-defined algebraic data types. A data type with multiple constructors is encoded as a nested `sum`, and a constructor with multiple fields is encoded as a nested `pair`. User code -rarely names `pair` or `sum` directly; they appear implicitly through `data` +rarely names `pair` or `sum` directly; they appear implicitly through `enum` declarations and tuple syntax. ### `word` @@ -33,7 +33,7 @@ SAIL has type `word`. There is no numeric overloading: `42`, `0xff`, and `word` values. ```solidity -function decimals() -> word { +function decimals() returns (word) { return 18; } ``` @@ -44,10 +44,14 @@ function decimals() -> word { always in scope. ```solidity -function isActive(paused : bool) -> bool { - match paused { - | false => return true; - | true => return false; +function isActive(paused: bool) returns (bool) { + match (paused) { + case false { + return true; + } + case true { + return false; + } } } ``` @@ -55,25 +59,29 @@ function isActive(paused : bool) -> bool { ### `()` — Unit The unit type `()` has a single value, also written `()`. Functions that -perform side effects and return nothing use `()` as their return type. +perform side effects and return nothing omit the `returns` clause. ```solidity -function setOwner(newOwner : word) -> () { +function setOwner(newOwner: word) { assembly { sstore(0, newOwner) } } ``` ### Tuple Syntax -The syntax `(a, b)` is shorthand for `pair a b`. Tuples with more than two -elements are right-nested pairs: `(a, b, c)` means `pair a (pair b c)`. +The syntax `(A, B)` is shorthand for `pair`. Tuples with more than two +elements are right-nested pairs: `(A, B, C)` means `pair>`. ```solidity -data Transfer = Transfer(word, word, word); +enum Transfer { + Transfer(word, word, word) +} -function unpack(t : Transfer) -> (word, word, word) { - match t { - | Transfer(from, to, amount) => return (from, to, amount); +function unpack(t: Transfer) returns ((word, word, word)) { + match (t) { + case Transfer(from, to, amount) { + return (from, to, amount); + } } } ``` @@ -87,6 +95,9 @@ calls. The parser rewrites each operator to the corresponding function call before name resolution; the functions themselves must be in scope at the point of use. +The table uses `->` only as compact mathematical notation for inferred type +schemes; source function types use `function(...) ... returns (...)`. + | Operator | Equivalent call | Type | | -------- | --------------- | ---- | | `e1 < e2` | `lt(e1, e2)` | `bool -> bool -> bool` | @@ -108,28 +119,28 @@ kernel; they must be brought into scope before use. > operation is performed. ```solidity -import std.{lt, ge, and}; +import {lt, ge, and} from std; -function isValidAmount(amount : word, balance : word) -> bool { +function isValidAmount(amount: word, balance: word) returns (bool) { return amount > 0 && amount <= balance; } ``` --- -## The `invokable` Class +## The `invokable` Trait The kernel defines one built-in type class: ```solidity -forall self args ret . class self:invokable(args, ret) { - function invoke(self : self, args : args) -> ret; +trait invokable { + function invoke(self: Self, args: Args) returns (Ret); } ``` `invokable` is the compiler's mechanism for encoding higher-order functions. When a function-typed value is passed as an argument or stored in a data -structure, the compiler generates an `invokable` instance that captures the +structure, the compiler generates an `invokable` implementation that captures the closure and implements `invoke`. User code rarely interacts with `invokable` directly. A dedicated chapter covers higher-order functions and the defunctionalization transformation in detail; see @@ -145,7 +156,8 @@ primitives. Each opcode is treated as a function that operates exclusively on by name inside the block. The sections below list every available opcode grouped by category, along with -its Yul type signature. +its Yul type signature. As in the operator table above, `->` is mathematical +type-scheme notation in these tables, not SAIL source syntax. ### Arithmetic @@ -164,9 +176,9 @@ its Yul type signature. | `signextend(b, x)` | `word -> word -> word` | Sign-extend from bit b | ```solidity -function checkedAdd(x : word, y : word) -> word { - let result : word; - let overflow : word; +function checkedAdd(x: word, y: word) returns (word) { + let result: word; + let overflow: word; assembly { result := add(x, y) overflow := lt(result, x) @@ -206,14 +218,14 @@ type is `word`, not `bool`; use `tobool` or a match on the result to convert. | `iszero(x)` | `word -> word` | 1 if x = 0 | ```solidity -function isOwner(account : word) -> bool { - let owner : word; - let result : word; +function isOwner(account: word) returns (bool) { + let owner: word; + let result: word; assembly { owner := sload(0) result := eq(account, owner) } - if (result) { + if (result != 0) { return true; } else { return false; @@ -228,8 +240,8 @@ function isOwner(account : word) -> bool { | `keccak256(offset, size)` | `word -> word -> word` | Keccak-256 hash of `size` bytes starting at memory `offset` | ```solidity -function storageSlot(account : word) -> word { - let slot : word; +function storageSlot(account: word) returns (word) { + let slot: word; assembly { mstore(0, account) slot := keccak256(0, 32) @@ -257,11 +269,11 @@ function storageSlot(account : word) -> word { | `sstore(slot, value)` | `word -> word -> ()` | Store value to storage slot | ```solidity -function transfer(to : word, amount : word) -> () { - let callerSlot : word; - let toSlot : word; - let senderBal : word; - let recipientBal : word; +function transfer(to: word, amount: word) { + let callerSlot: word; + let toSlot: word; + let senderBal: word; + let recipientBal: word; assembly { callerSlot := caller() toSlot := to @@ -354,8 +366,8 @@ the expected type. | `log4(offset, size, topic1, topic2, topic3, topic4)` | `word^6 -> ()` | Emit log with 4 topics | ```solidity -function emitTransfer(from : word, to : word, amount : word) -> () { - let transferTopic : word; +function emitTransfer(from: word, to: word, amount: word) { + let transferTopic: word; assembly { transferTopic := 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef mstore(0x00, amount) @@ -389,17 +401,17 @@ return `word`. | `gaslimit()` | Gas limit of the current block | ```solidity -function onlyOwner(ownerSlot : word) -> () { - let owner : word; - let msgSender : word; - let isAuth : word; +function onlyOwner(ownerSlot: word) { + let owner: word; + let msgSender: word; + let isAuth: word; assembly { owner := sload(ownerSlot) msgSender := caller() isAuth := eq(msgSender, owner) } - if (isAuth) { - return (); + if (isAuth != 0) { + return; } else { assembly { revert(0, 0) } } diff --git a/doc/src/sail/datatypes.md b/doc/src/sail/datatypes.md index f011fa7cb..16b60bd6e 100644 --- a/doc/src/sail/datatypes.md +++ b/doc/src/sail/datatypes.md @@ -6,7 +6,10 @@ constructor describes one way to build a value of that type and may carry zero or more _fields_ of arbitrary types. ```solcore -data Option(a) = None | Some(a); +enum Option { + None, + Some(A) +} ``` Data types may be defined at the top level of a source file or inside a contract @@ -20,7 +23,11 @@ The simplest kind of algebraic data type has only nullary constructors with no fields. Such a type acts as a finite enumeration. ```solcore -data TokenStatus = Active | Paused | Deprecated; +enum TokenStatus { + Active, + Paused, + Deprecated +} ``` Each constructor is a distinct value of the type. Enumerations are commonly used @@ -28,17 +35,27 @@ wherever Solidity uses `enum`. ```solcore contract Registry { - data TokenStatus = Active | Paused | Deprecated; + enum TokenStatus { + Active, + Paused, + Deprecated + } - function statusCode(s : TokenStatus) -> word { - match s { - | TokenStatus.Active => return 1; - | TokenStatus.Paused => return 2; - | TokenStatus.Deprecated => return 0; + function statusCode(s: TokenStatus) returns (word) { + match (s) { + case TokenStatus.Active { + return 1; + } + case TokenStatus.Paused { + return 2; + } + case TokenStatus.Deprecated { + return 0; + } } } - function main() -> word { + function main() returns (word) { return statusCode(TokenStatus.Active); } } @@ -52,8 +69,17 @@ A constructor can carry one or more fields. The field types are listed in parentheses, separated by commas. ```solcore -data TxStatus = Pending | Settled | Failed; -data TxOutcome = Success(TxStatus) | Revert(TxStatus) | Unknown; +enum TxStatus { + Pending, + Settled, + Failed +} + +enum TxOutcome { + Success(TxStatus), + Revert(TxStatus), + Unknown +} ``` A constructor with fields is applied like a function: @@ -64,11 +90,17 @@ Fields are extracted by pattern matching; there is no record-style field access. The pattern mirrors the constructor application: ```solcore -function outcomeCode(x : TxOutcome) -> word { - match x { - | TxOutcome.Success(TxStatus.Settled) => return 1; - | TxOutcome.Revert(TxStatus.Failed) => return 2; - | _ => return 0; +function outcomeCode(x: TxOutcome) returns (word) { + match (x) { + case TxOutcome.Success(TxStatus.Settled) { + return 1; + } + case TxOutcome.Revert(TxStatus.Failed) { + return 2; + } + default { + return 0; + } } } ``` @@ -78,33 +110,43 @@ function outcomeCode(x : TxOutcome) -> word { ## Parametric Data Types A data type can be parameterized by one or more _type variables_, making it a -_generic_ or _parametric_ type. The type variables are listed in parentheses +_generic_ or _parametric_ type. The type variables are listed in angle brackets after the type name. ```solcore -data Option(a) = None | Some(a); +enum Option { + None, + Some(A) +} ``` -Here `a` is a type variable. `Option(word)` is the type of optional words, -`Option(bool)` is the type of optional booleans, and so on. The type variable -`a` may appear in the field types of any constructor. +Here `A` is a type variable. `Option` is the type of optional words, +`Option` is the type of optional booleans, and so on. The type variable +`A` may appear in the field types of any constructor. ```solcore contract Option { - data Option(a) = None | Some(a); + enum Option { + None, + Some(A) + } - function just(x : a) -> Option(a) { + function just(x: A) returns (Option) { return Option.Some(x); } - function maybe(default : word, opt : Option(word)) -> word { - match opt { - | Option.None => return default; - | Option.Some(x) => return x; + function maybe(defaultValue: word, opt: Option) returns (word) { + match (opt) { + case Option.None { + return defaultValue; + } + case Option.Some(x) { + return x; + } } } - function main() -> word { + function main() returns (word) { return maybe(0, Option.Some(42)); } } @@ -122,13 +164,20 @@ Patterns may be nested to arbitrary depth to match inside multiple layers of constructors in a single arm. ```solcore -data Option(a) = None | Some(a); +enum Option { + None, + Some(A) +} // Unwrap an approval amount nested in two Option layers. -function resolveApproval(outer : Option(Option(word))) -> Option(word) { - match outer { - | Option.Some(Option.Some(x)) => return Option.Some(x); - | _ => return Option.None; +function resolveApproval(outer: Option>) returns (Option) { + match (outer) { + case Option.Some(Option.Some(x)) { + return Option.Some(x); + } + default { + return Option.None; + } } } ``` @@ -145,7 +194,9 @@ _distinct_ type that is represented by an existing type at runtime. This is similar to Haskell's `newtype` or Solidity's user-defined value types. ```solcore -data uint256 = uint256(word); +enum uint256 { + uint256(word) +} ``` `uint256` is a type distinct from `word` even though it carries exactly one @@ -156,13 +207,15 @@ occupy exactly one EVM word, just like `word`. Wrapping and unwrapping are done explicitly with the constructor and a pattern: ```solcore -function wrap(x : word) -> uint256 { +function wrap(x: word) returns (uint256) { return uint256(x); } -function unwrap(x : uint256) -> word { - match x { - | uint256(w) => return w; +function unwrap(x: uint256) returns (word) { + match (x) { + case uint256(w) { + return w; + } } } ``` @@ -179,34 +232,36 @@ _phantom_ type parameter. It carries no runtime information but allows the type system to distinguish values that would otherwise be identical. ```solcore -// 'a' is a phantom type parameter: the constructor Proxy carries no field of type 'a'. -data Proxy(a) = Proxy; +// A is phantom: the constructor Proxy carries no field of type A. +enum Proxy { + Proxy +} ``` -`Proxy(word)` and `Proxy(bool)` are distinct types at compile time but produce +`Proxy` and `Proxy` are distinct types at compile time but produce the same runtime value. Phantom types are useful for passing type information to functions without allocating extra memory. ```solcore -forall a . class a:MemoryType { - function size(prx : Proxy(a)) -> word; +trait MemoryType { + function size(prx: Proxy) returns (word); } -instance word:MemoryType { - function size(prx : Proxy(word)) -> word { +impl MemoryType { + function size(prx: Proxy) returns (word) { return 32; } } ``` -The `Proxy(a)` argument lets the caller select which `MemoryType` instance to -use without passing an actual value of type `a`. +The `Proxy` argument lets the caller select which `MemoryType` implementation +to use without passing an actual value of type `A`. > **Note** Because phantom type parameters leave the constructor's result type > partially undetermined, the type checker requires an explicit type annotation > whenever a `Proxy` value is constructed in a context where the type cannot be -> inferred from surrounding expressions. Use the expression annotation form -> `Proxy : Proxy(word)` to resolve the ambiguity. +> inferred from surrounding expressions. Use an explicit conversion, +> `Proxy as Proxy`, to resolve the ambiguity. --- @@ -217,21 +272,23 @@ as a parenthesised, comma-separated list of component types. Tuple values are written the same way. ```solcore -function swap(p : (word, bool)) -> (bool, word) { - match p { - | (x, b) => return (b, x); +function swap(p: (word, bool)) returns ((bool, word)) { + match (p) { + case (x, b) { + return (b, x); + } } } ``` Tuples of more than two elements are right-nested pairs internally. The type -`(word, bool, word)` is represented as `pair(word, pair(bool, word))`. +`(word, bool, word)` is represented as `pair>`. The unit type `()` is the zero-element tuple. It carries no information and is used as the return type of functions that exist only for their side effects. ```solcore -function storeBalance(account : word, amount : word) -> () { +function storeBalance(account: word, amount: word) { assembly { sstore(account, amount) } } ``` @@ -240,10 +297,12 @@ function storeBalance(account : word, amount : word) -> () { > inside constructor patterns: > > ```solcore -> forall a b . instance Zero:Nth((a, b), a) { -> function nth(idx : Proxy(Zero), tup : (a, b)) -> a { -> match tup { -> | (x, _) => return x; +> impl Nth { +> function nth(idx: Proxy, tup: (A, B)) returns (A) { +> match (tup) { +> case (x, _) { +> return x; +> } > } > } > } @@ -258,13 +317,16 @@ omitted from a constructor name by prefixing it with `.`. The compiler resolves the constructor to the appropriate type automatically. ```solcore -data Option(a) = None | Some(a); +enum Option { + None, + Some(A) +} -function just(x : word) -> Option(word) { +function just(x: word) returns (Option) { return .Some(x); // equivalent to Option.Some(x) } -function nothing() -> Option(word) { +function nothing() returns (Option) { return .None; // equivalent to Option.None } ``` @@ -272,10 +334,14 @@ function nothing() -> Option(word) { The same shorthand works in patterns: ```solcore -function isNone(o : Option(word)) -> bool { - match o { - | .None => return true; - | .Some(_) => return false; +function isNone(o: Option) returns (bool) { + match (o) { + case .None { + return true; + } + case .Some(_) { + return false; + } } } ``` @@ -292,16 +358,18 @@ a compile-time device: the compiler expands them before type checking and they leave no trace in the generated code. ```solcore -type Int = word; -type Point = pair(Int, Int); +type Int is word; +type Point is pair; -function makePoint(x : Int, y : Int) -> Point { +function makePoint(x: Int, y: Int) returns (Point) { return (x, y); } -function getX(p : Point) -> Int { - match p { - | (x, _) => return x; +function getX(p: Point) returns (Int) { + match (p) { + case (x, _) { + return x; + } } } ``` @@ -309,12 +377,12 @@ function getX(p : Point) -> Int { Like data types, synonyms can have type parameters: ```solcore -type Map(k, v) = pair(k, v); // toy example +type Map is pair; // toy example ``` > **Warning** Recursive type synonyms are not allowed. A synonym must not refer -> directly or indirectly to itself. Attempting to define `type A = B` and -> `type B = A` simultaneously is a compile-time error. +> directly or indirectly to itself. Attempting to define `type A is B` and +> `type B is A` simultaneously is a compile-time error. --- @@ -326,7 +394,7 @@ Hull/Yul code. **Sum types** (types with more than one constructor) are encoded as nested binary sums using `inl` (left injection) and `inr` (right injection). A type with _n_ constructors becomes a right-nested binary tree of depth ⌈log₂ n⌉. For -example, a three-constructor type `data T = A | B | C` is encoded as: +example, a three-constructor type `enum T { A, B, C }` is encoded as: ``` A → inl () @@ -335,8 +403,8 @@ C → inr (inr ()) ``` **Product types** (constructor fields, tuples) are encoded as right-nested -pairs. The three-field constructor `data T = T(word, bool, word)` becomes -`pair(word, pair(bool, word))`. +pairs. The three-field constructor `enum T { T(word, bool, word) }` becomes +`pair>`. This uniform encoding is what the `match` compiler and the Hull back-end operate on. It is not visible at the SAIL level. diff --git a/doc/src/sail/functions.md b/doc/src/sail/functions.md index 9e69c8295..801d1b915 100644 --- a/doc/src/sail/functions.md +++ b/doc/src/sail/functions.md @@ -6,14 +6,16 @@ defined at the top level of a source file, called _free functions_, or inside a contract body. ```solidity -function name(param1 : Type1, param2 : Type2) -> ReturnType { +function name(param1: Type1, param2: Type2) returns (ReturnType) { // body } ``` Every top-level function must carry a complete type signature: every parameter -must be annotated with its type, and the return type must be provided after -`->`. The compiler rejects any top-level definition that omits an annotation. +must be annotated with its type, and a value-producing function declares its +result in a `returns (...)` clause. A function with no `returns` clause has the +unit result type. The compiler rejects any top-level definition that leaves a +parameter unannotated. > **Note** The complete-annotation requirement applies to free functions and > contract methods. It does not apply to lambda expressions or to local @@ -25,11 +27,11 @@ must be annotated with its type, and the return type must be provided after ## Parameters Parameters are declared as a comma-separated list enclosed in parentheses. -Each parameter has the form `name : Type`. +Each parameter has the name-first form `name: Type`. ```solidity -function transfer(to : word, amount : word) -> () { - let bal : word; +function transfer(to: word, amount: word) { + let bal: word; assembly { bal := sload(caller()) } assembly { sstore(caller(), sub(bal, amount)) } assembly { sstore(to, add(sload(to), amount)) } @@ -39,8 +41,8 @@ function transfer(to : word, amount : word) -> () { A function that takes no arguments is written with an empty parameter list: ```solidity -function sender() -> word { - let s : word; +function sender() returns (word) { + let s: word; assembly { s := caller() } return s; } @@ -50,13 +52,13 @@ function sender() -> word { ## Return Type -The return type follows the parameter list after `->`. Every top-level function -must declare its return type explicitly. +Return types follow the parameter list in a `returns (...)` clause. Multiple +result types are separated by commas. -A function that returns no meaningful value uses the unit type `()`: +A function that returns no meaningful value omits the clause: ```solidity -function emitTransfer(from : word, to : word, amount : word) -> () { +function emitTransfer(from: word, to: word, amount: word) { assembly { mstore(0x00, amount) log3(0x00, 0x20, 0xddf252ad, from, to) @@ -64,8 +66,9 @@ function emitTransfer(from : word, to : word, amount : word) -> () { } ``` -Every execution path through the body must end with a `return` statement whose -expression has the declared return type. +Every execution path through a value-producing body must end with a `return` +statement whose expression has the declared return type. A bare `return;` +returns unit. --- @@ -76,16 +79,16 @@ functions are visible throughout the file in which they are defined and can be imported by other modules. ```solidity -function isContract(addr : word) -> bool { - let size : word; +function isContract(addr: word) returns (bool) { + let size: word; assembly { size := extcodesize(addr) } return gt(size, 0); } contract Token { - function onlyContract(addr : word) -> () { + function onlyContract(addr: word) { if (isContract(addr)) { - return (); + return; } else { assembly { revert(0, 0) } } @@ -98,24 +101,25 @@ contract Token { ## Polymorphic Functions A function that works uniformly over multiple types can be made polymorphic -with a `forall` quantifier placed before the `function` keyword. The quantifier -lists the type variables that appear in the signature. +by listing generic type parameters in angle brackets after the function name. ```solidity -forall a . function identity(x : a) -> a { +function identity(x: A) returns (A) { return x; } -forall a b . function fst(p : (a, b)) -> a { - match p { - | (x, y) => return x; +function fst(p: (A, B)) returns (A) { + match (p) { + case (x, y) { + return x; + } } } ``` -Type variables introduced by `forall` are instantiated at each call site. The -compiler specializes the function for every concrete type combination that -appears in the program. +Generic type parameters are instantiated at each call site. The compiler +specializes the function for every concrete type combination that appears in +the program. > **Note** Polymorphic functions are monomorphized by the specializer before > code generation. Each distinct instantiation produces a separate function in @@ -128,15 +132,18 @@ appears in the program. ## Constrained Functions A function may require that one or more of its type variables satisfy a type -class constraint. Constraints are written after the type variable list, -separated from the `function` keyword by `=>`. +class constraint. Constraints are written after the return clause in a `where` +clause. ```solidity -forall a . class a:Checked { - function checkedAdd(x : a, y : a) -> a; +trait Checked { + function checkedAdd(x: A, y: A) returns (A); } -forall t . t:Checked => function safeTransfer(from : word, to : word, amount : t) -> t { +function safeTransfer(from: word, to: word, amount: T) + returns (T) + where T: Checked +{ return Checked.checkedAdd(amount, amount); } ``` @@ -144,9 +151,14 @@ forall t . t:Checked => function safeTransfer(from : word, to : word, amount : t Multiple constraints on different type variables are separated by commas: ```solidity -forall a b . a:Eq, b:Eq => function transfersEqual(x : (a, b), y : (a, b)) -> bool { - match (x, y) { - | ((xa, xb), (ya, yb)) => return Eq.eq(xa, ya); +function transfersEqual(x: (A, B), y: (A, B)) + returns (bool) + where A: Eq, B: Eq +{ + match ((x, y)) { + case ((xa, xb), (ya, yb)) { + return Eq.eq(xa, ya); + } } } ``` @@ -162,11 +174,11 @@ A function may call itself recursively. The compiler adds the function name to the typing context before checking the body. ```solidity -function sumBalances(slot : word, count : word) -> word { +function sumBalances(slot: word, count: word) returns (word) { if (eq(count, 0)) { return 0; } else { - let bal : word; + let bal: word; assembly { bal := sload(slot) } return add(bal, sumBalances(add(slot, 1), sub(count, 1))); } @@ -179,19 +191,30 @@ type-checks the group as a unit. Both functions must be defined in the same file. ```solidity -data TxStatus = Pending | Confirmed; +enum TxStatus { + Pending, + Confirmed +} -function isPending(s : TxStatus) -> bool { - match s { - | TxStatus.Pending => return isNotConfirmed(s); - | TxStatus.Confirmed => return false; +function isPending(s: TxStatus) returns (bool) { + match (s) { + case TxStatus.Pending { + return isNotConfirmed(s); + } + case TxStatus.Confirmed { + return false; + } } } -function isNotConfirmed(s : TxStatus) -> bool { - match s { - | TxStatus.Confirmed => return isPending(TxStatus.Pending); - | TxStatus.Pending => return true; +function isNotConfirmed(s: TxStatus) returns (bool) { + match (s) { + case TxStatus.Confirmed { + return isPending(TxStatus.Pending); + } + case TxStatus.Pending { + return true; + } } } ``` @@ -205,13 +228,13 @@ variables. They follow the same signature rules as free functions. ```solidity contract ERC20 { - totalSupply : word; + totalSupply: word; - function mint(amount : word) -> () { + function mint(amount: word) { totalSupply = add(totalSupply, amount); } - function getTotalSupply() -> word { + function getTotalSupply() returns (word) { return totalSupply; } } @@ -227,12 +250,19 @@ operate on their parameters and locally declared variables. Functions may use `match` statements to deconstruct algebraic data type values. ```solidity -data Result = Ok(word) | Err(word); +enum Result { + Ok(word), + Err(word) +} -function unwrapOrZero(r : Result) -> word { - match r { - | Result.Ok(v) => return v; - | Result.Err(_) => return 0; +function unwrapOrZero(r: Result) returns (word) { + match (r) { + case Result.Ok(v) { + return v; + } + case Result.Err(_) { + return 0; + } } } ``` @@ -251,8 +281,8 @@ an assembly block, Yul syntax is used. Variables declared in the surrounding SAIL scope are accessible by name inside the block. ```solidity -function loadBalance(account : word) -> word { - let bal : word; +function loadBalance(account: word) returns (word) { + let bal: word; assembly { bal := sload(account) } @@ -273,30 +303,20 @@ enclosing SAIL scope before the block opens. The type of such variables must be ## Missing Annotation Error -Omitting a parameter type or the return type on a top-level function is a -compile-time error. The compiler reports the offending signature and explains -what is missing. +Omitting a parameter type on a top-level function is a compile-time error. The +compiler reports the offending signature and explains what is missing. ```solidity // Error: parameter 'x' has no type annotation. -function bad(x) -> word { +function bad(x) returns (word) { return x; } ``` ``` Top-level function must have complete type annotations: - bad(x) -> word -Annotate every parameter (name : Type) and provide a return type (-> Type). -``` - -Omitting the return type is equally rejected: - -```solidity -// Error: return type is missing. -function alsobad(x : word) { - return x; -} + function bad(x) returns (word) +Annotate every parameter (name: Type). ``` Type inference remains available inside function bodies for local variables and diff --git a/doc/src/sail/modules.md b/doc/src/sail/modules.md index f48a75cc4..3ef705ca5 100644 --- a/doc/src/sail/modules.md +++ b/doc/src/sail/modules.md @@ -39,18 +39,18 @@ qualified prefix `modname`. No names are introduced into the unqualified scope. ```solidity -// token.solc exports: Token, transfer +// token.sol exports: Token, transfer import token; -function doTransfer(t : token.Token, to : word, amount : word) -> () { - return token.transfer(t, to, amount); +function doTransfer(t: token.Token, to: word, amount: word) { + token.transfer(t, to, amount); } ``` ### Module import with alias ```solidity -import modname as Alias; +import * as Alias from modname; ``` Same as a full import but assigns a shorter alias to the module. All @@ -58,10 +58,10 @@ qualified references must use the alias; the original module name is not available as a qualifier. ```solidity -import token as T; +import * as T from token; -function doTransfer(t : T.Token, to : word, amount : word) -> () { - return T.transfer(t, to, amount); +function doTransfer(t: T.Token, to: word, amount: word) { + T.transfer(t, to, amount); } ``` @@ -71,26 +71,26 @@ is not a known qualifier in this file. ### Selective import ```solidity -import modname.{Name1, Name2}; +import {Name1, Name2} from modname; ``` Loads the listed names directly into the unqualified scope. They can be used without any prefix. ```solidity -import token.{Token, transfer}; +import {Token, transfer} from token; -function doTransfer(t : Token, to : word, amount : word) -> () { - return transfer(t, to, amount); +function doTransfer(t: Token, to: word, amount: word) { + transfer(t, to, amount); } ``` Each item in the selector list may optionally be renamed with `as`: ```solidity -import selectlib.{keep as keep_, drop as drop_}; +import {keep as keep_, drop as drop_} from selectlib; -function main(x : word) -> word { +function main(x: word) returns (word) { return drop_(keep_(x)); } ``` @@ -102,14 +102,14 @@ Constructors of an imported type must still be qualified with the type name even when the type itself was selectively imported: ```solidity -import token.{Token}; +import {Token} from token; -function makeActive() -> Token { +function makeActive() returns (Token) { return Token.Active; // correct } // Error: unqualified constructor. -function makeBad() -> Token { +function makeBad() returns (Token) { return Active; } ``` @@ -123,19 +123,21 @@ Use Type.Constructor form. ### Wildcard selective import ```solidity -import modname.{*}; +import {*} from modname; ``` Places every exported name from the module into the unqualified scope. Individual names may be excluded using `hiding`: ```solidity -import globlib.{*} hiding {idWord}; +import {*} from globlib hiding {idWord}; -function main(x : word) -> word { - let y : T = mkT(x); // mkT is in scope; idWord is not - match y { - | T.T(v) => return v; +function main(x: word) returns (word) { + let y: T = mkT(x); // mkT is in scope; idWord is not + match (y) { + case T.T(v) { + return v; + } } } ``` @@ -143,45 +145,13 @@ function main(x : word) -> word { The `hiding` clause accepts a comma-separated list of names to suppress: ```solidity -import selectlib.{keep, drop} hiding {drop}; +import {keep, drop} from selectlib hiding {drop}; -function main(x : word) -> word { +function main(x: word) returns (word) { return keep(x); // drop is not in scope } ``` -### Importing operator symbols - -User-defined operator symbols are exported and imported using parenthesised -syntax `(sym)`, where `sym` is the operator character sequence. Importing an -operator brings both its precedence/fixity declaration and its bound function -into scope. - -```solidity -// math.solc (declares and exports the operator) -infixl 70 (^^) => pow; -export { pow, (^^) }; - -function pow(b : word, e : word) -> word { ... } -``` - -```solidity -// main.solc (imports the operator by symbol) -import math.{pow, (^^)}; - -contract Main { - function main() -> word { - return 2 ^^ 10; // 1024 - } -} -``` - -Operator symbols may be mixed freely with ordinary names in the same selector -list. Importing the function name alone (without the symbol) makes the -implementation callable as a regular function but does not enable infix syntax. - ---- - ## Module Paths A module path identifies the source file of a module relative to a root @@ -193,8 +163,8 @@ A name without a `lib.` prefix is a _relative path_. The compiler resolves it relative to the directory that contains the importing file. ```solidity -import foo.bar; // loads foo/bar.solc -import foo.bar.baz; // loads foo/bar/baz.solc +import foo.bar; // loads foo/bar.sol +import foo.bar.baz; // loads foo/bar/baz.sol ``` After a plain `import foo.bar`, the module is accessible under the full @@ -203,7 +173,7 @@ dotted qualifier: ```solidity import foo.bar; -function main() -> word { +function main() returns (word) { return foo.bar.value(); } ``` @@ -215,7 +185,7 @@ resolved from the root of the current library rather than the current directory. ```solidity -export lib.some.module; // re-exports some/module.solc from the library root +export lib.some.module; // re-exports some/module.sol from the library root ``` Library paths are mainly used in re-export declarations to expose a module @@ -233,7 +203,7 @@ time. import @extlib.math.api; contract External { - function main() -> word { + function main() returns (word) { return math.api.sum(39); } } @@ -242,9 +212,9 @@ contract External { An alias keeps the reference concise: ```solidity -import @extlib.math.api as MathApi; +import * as MathApi from @extlib.math.api; -function main() -> word { +function main() returns (word) { return MathApi.sum(39); } ``` @@ -258,7 +228,7 @@ user libraries. ```solidity import std; -function main() -> word { +function main() returns (word) { return std.addWord(21, 21); } ``` @@ -276,8 +246,8 @@ types, functions, and constructors. ```solidity import token; -function doTransfer(t : token.Token, to : word, amount : word) -> () { - return token.transfer(t, to, amount); +function doTransfer(t: token.Token, to: word, amount: word) { + token.transfer(t, to, amount); } ``` @@ -288,7 +258,7 @@ Constructors are written as `qualifier.TypeName.Constructor`: ```solidity import token; -function makeActive() -> token.Token { +function makeActive() returns (token.Token) { return token.Token.Active; } ``` @@ -300,10 +270,14 @@ The same qualified form is used in pattern matching: ```solidity import token; -function isActive(t : token.Token) -> word { - match t { - | token.Token.Active => return 1; - | token.Token.Paused => return 0; +function isActive(t: token.Token) returns (word) { + match (t) { + case token.Token.Active { + return 1; + } + case token.Token.Paused { + return 0; + } } } ``` @@ -314,9 +288,9 @@ When the import carries an alias, replace the module name with the alias in all qualified references: ```solidity -import token as T; +import * as T from token; -function makeActive() -> T.Token { +function makeActive() returns (T.Token) { return T.Token.Active; } ``` @@ -329,6 +303,11 @@ An export declaration controls which definitions an importing module can see. Definitions that are not listed in an export declaration are private to the file. +> **Implementation extension** The canonical new syntax deliberately leaves +> export and re-export syntax undecided. The forms in this section describe the +> current compiler extension and may change when the interoperability model is +> finalized. + > **Note** A file without any export declaration exports nothing. All > definitions are private unless explicitly exported. @@ -359,20 +338,6 @@ export { Token(Ok, Err) }; // exports both constructors export { Bool(*) }; // exports Bool and all its constructors ``` -### Exporting operator symbols - -Operator symbols are separate exportable entities from the functions they -implement. To make an operator available to importers as infix syntax, both -the function name and the operator symbol must appear in the export list: - -```solidity -infixl 70 (^^) => pow; -export { pow, (^^) }; -``` - -Exporting only `pow` (without `(^^)`) makes the function callable by name -but does not grant importers access to the infix `^^` syntax. - ### Wildcard export ```solidity @@ -390,7 +355,7 @@ came from the re-exporting module. **Re-export a whole module:** ```solidity -// api.solc: makes all of util's exports available under api.util.* +// api.sol: makes all of util's exports available under api.util.* export lib.reexport_module.pkg.util; ``` @@ -400,7 +365,7 @@ chain: ```solidity import reexport_module.pkg.api; -function main() -> word { +function main() returns (word) { return api.util.unwrap(api.util.Wrap.Mk(1)); } ``` @@ -408,14 +373,14 @@ function main() -> word { **Re-export a module under an alias:** ```solidity -// api_alias.solc +// api_alias.sol export lib.reexport_module.pkg.util as Utils; ``` ```solidity import reexport_module.pkg.api_alias; -function main() -> word { +function main() returns (word) { return api_alias.Utils.unwrap(api_alias.Utils.Wrap.Mk(1)); } ``` @@ -440,13 +405,16 @@ create or examine values of an opaque type is through the functions the module chooses to export. ```solidity -// hidden_ctor_lib.solc +// hidden_ctor_lib.sol export {Token(Ok), mkOk, mkErr}; -data Token = Ok(word) | Err(word); +enum Token { + Ok(word), + Err(word) +} -function mkOk(x : word) -> Token { return Token.Ok(x); } -function mkErr(x : word) -> Token { return Token.Err(x); } +function mkOk(x: word) returns (Token) { return Token.Ok(x); } +function mkErr(x: word) returns (Token) { return Token.Err(x); } ``` The module exports `Token` with only the `Ok` constructor visible. The `Err` @@ -455,10 +423,10 @@ constructor is private. An importer that selects only the type cannot use the hidden constructor: ```solidity -import hidden_ctor_lib.{Token}; +import {Token} from hidden_ctor_lib; // Error: Err is not exported. -function bad() -> Token { +function bad() returns (Token) { return .Err(1); } ``` @@ -471,13 +439,17 @@ No matching constructor for shorthand expression: Pattern matching on the hidden constructor is equally rejected: ```solidity -import hidden_ctor_lib.{Token, mkErr}; +import {Token, mkErr} from hidden_ctor_lib; // Error: Token.Err is not in scope. -function bad(x : word) -> word { - match mkErr(x) { - | Token.Err(v) => return v; - | _ => return 0; +function bad(x: word) returns (word) { + match (mkErr(x)) { + case Token.Err(v) { + return v; + } + default { + return 0; + } } } ``` @@ -496,18 +468,18 @@ sees only the names that `A` chose to export. Names that `B` exported to `A` but that `A` did not re-export are not visible in `C`. ```solidity -// transitive_dep_base.solc +// transitive_dep_base.sol export { g }; -function g() -> word { return 1; } +function g() returns (word) { return 1; } -// transitive_dep_mid.solc -import transitive_dep_base.{g}; +// transitive_dep_mid.sol +import {g} from transitive_dep_base; export { f }; -function f() -> word { return g(); } +function f() returns (word) { return g(); } -// transitive_dep_main_select.solc -import transitive_dep_mid.{f}; -function main() -> word { return f(); } // g is not in scope here +// transitive_dep_main_select.sol +import {f} from transitive_dep_mid; +function main() returns (word) { return f(); } // g is not in scope here ``` --- @@ -521,22 +493,22 @@ identifier. The imported name remains accessible through its qualified form. import token; // Local 'transfer' shadows token.transfer for unqualified calls. -function transfer(to : word, amount : word) -> () { return (); } +function transfer(to: word, amount: word) { return; } -function main(to : word, amount : word) -> () { - return token.transfer(to, amount); // uses the imported transfer, not the local one +function main(to: word, amount: word) { + token.transfer(to, amount); // uses the imported transfer, not the local one } ``` A locally defined name also shadows a selectively imported name: ```solidity -import erc20lib.{balanceOf}; +import {balanceOf} from erc20lib; // Local 'balanceOf' shadows the imported one. -function balanceOf(account : word) -> word { return 0; } +function balanceOf(account: word) returns (word) { return 0; } -function main(account : word) -> word { +function main(account: word) returns (word) { return balanceOf(account); // calls the local balanceOf } ``` @@ -554,8 +526,8 @@ an imported name without the qualifier is an error: import token; // Error: 'transfer' is not in scope unqualified. -function main(to : word, amount : word) -> () { - return transfer(to, amount); +function main(to: word, amount: word) { + transfer(to, amount); } ``` @@ -563,7 +535,7 @@ function main(to : word, amount : word) -> () { Undefined name: transfer ``` -The fix is to qualify the call: `return token.transfer(to, amount);`. +The fix is to qualify the call: `token.transfer(to, amount);`. ### Using the original name after aliasing @@ -571,11 +543,11 @@ When an alias replaces the module name, the original name is not a valid qualifier: ```solidity -import erc20.token as T; +import * as T from erc20.token; // Error: erc20 is not a qualifier in this file. -function main(to : word, amount : word) -> () { - return erc20.token.transfer(to, amount); +function main(to: word, amount: word) { + erc20.token.transfer(to, amount); } ``` @@ -583,7 +555,7 @@ function main(to : word, amount : word) -> () { Undefined name: erc20 ``` -The fix is to use the alias: `return T.transfer(to, amount);`. +The fix is to use the alias: `T.transfer(to, amount);`. ### Using a type without qualifying its constructor @@ -591,10 +563,10 @@ Selectively importing a type name does not bring its constructors into the unqualified scope. Constructors must always be written as `TypeName.Constructor`: ```solidity -import token.{Token}; +import {Token} from token; // Error: unqualified constructor. -function makeActive() -> Token { +function makeActive() returns (Token) { return Active; } ``` @@ -614,7 +586,7 @@ module qualifier fails: import token; // Error: Token is not in the unqualified scope. -function bad(t : Token) -> word { +function bad(t: Token) returns (word) { return 0; } ``` diff --git a/doc/src/sail/parametric-polymorphism.md b/doc/src/sail/parametric-polymorphism.md index ee8707d0f..30dd09ad4 100644 --- a/doc/src/sail/parametric-polymorphism.md +++ b/doc/src/sail/parametric-polymorphism.md @@ -3,40 +3,44 @@ A parametric polymorphic function works uniformly over any type. The caller does not need to know which concrete type is used; the function behaves identically for all instantiations. SAIL supports parametric polymorphism -through `forall` quantifiers in function signatures. +through generic parameter lists in function signatures. --- -## Type Variables and `forall` +## Type Variables and Generic Parameters A type variable is a placeholder for any concrete type. To introduce type -variables in a function signature, place a `forall` quantifier before the -`function` keyword. The quantifier lists the type variable names separated by -spaces and terminated by a period. +variables in a function signature, list their names in angle brackets after the +function name. ```solidity -forall a . function id(x : a) -> a { +function id(x: A) returns (A) { return x; } ``` -The signature `forall a . a -> a` states that `id` accepts one argument of any -type `a` and returns a value of the same type `a`. The same type variable `a` +The signature `function id(x: A) returns (A)` states that `id` accepts one +argument of any type `A` and returns a value of the same type `A`. The same +type variable `A` appears in both the parameter and the return position, so the caller knows that the output type equals the input type. -Multiple type variables are listed in the same quantifier: +Multiple type variables are separated by commas: ```solidity -forall a b . function fst(p : (a, b)) -> a { - match p { - | (x, y) => return x; +function fst(p: (A, B)) returns (A) { + match (p) { + case (x, y) { + return x; + } } } -forall a b . function snd(p : (a, b)) -> b { - match p { - | (x, y) => return y; +function snd(p: (A, B)) returns (B) { + match (p) { + case (x, y) { + return y; + } } } ``` @@ -45,16 +49,23 @@ Type variables may appear in parameter types, the return type, and in type arguments to other type constructors: ```solidity -data Option(a) = None | Some(a); +enum Option { + None, + Some(A) +} -forall a . function just(x : a) -> Option(a) { +function just(x: A) returns (Option) { return Option.Some(x); } -forall a . function fromOption(default : a, opt : Option(a)) -> a { - match opt { - | Option.None => return default; - | Option.Some(x) => return x; +function fromOption(defaultValue: A, opt: Option) returns (A) { + match (opt) { + case Option.None { + return defaultValue; + } + case Option.Some(x) { + return x; + } } } ``` @@ -68,13 +79,13 @@ variable from the types of the supplied arguments. No explicit type application is needed; the inference engine handles instantiation automatically. ```solidity -forall a . function id(x : a) -> a { +function id(x: A) returns (A) { return x; } contract C { - function main() -> word { - return id(42); // a is instantiated to word + function main() returns (word) { + return id(42); // A is instantiated to word } } ``` @@ -93,33 +104,39 @@ matching. The match compiler operates on the inferred type at each call site after instantiation. ```solidity -data Pair(a, b) = Pair(a, b); +enum Pair { + Pair(A, B) +} -forall a b . function fst(p : Pair(a, b)) -> a { - match p { - | Pair(x, y) => return x; +function fst(p: Pair) returns (A) { + match (p) { + case Pair(x, y) { + return x; + } } } -forall a b . function snd(p : Pair(a, b)) -> b { - match p { - | Pair(x, y) => return y; +function snd(p: Pair) returns (B) { + match (p) { + case Pair(x, y) { + return y; + } } } -function addAmounts(x : word, y : word) -> word { - let res : word; +function addAmounts(x: word, y: word) returns (word) { + let res: word; assembly { res := add(x, y) } return res; } // A transfer record holds (sender address, amount). -function totalTransferred(p : Pair(word, word)) -> word { +function totalTransferred(p: Pair) returns (word) { return addAmounts(fst(p), snd(p)); } contract ERC20 { - function main() -> word { + function main() returns (word) { return totalTransferred(Pair(100, 200)); } } @@ -135,19 +152,32 @@ all functions in a group together. Both functions must be defined in the same file. ```solidity -data Option(a) = None | Some(a); +enum Option { + None, + Some(A) +} -forall a . function orElse(primary : Option(a), fallback : Option(a)) -> Option(a) { - match primary { - | Option.Some(v) => return Option.Some(v); - | Option.None => return pickFirst(fallback, primary); +function orElse(primary: Option, fallbackValue: Option) + returns (Option) +{ + match (primary) { + case Option.Some(v) { + return Option.Some(v); + } + case Option.None { + return pickFirst(fallbackValue, primary); + } } } -forall a . function pickFirst(x : Option(a), y : Option(a)) -> Option(a) { - match x { - | Option.Some(v) => return orElse(x, y); - | Option.None => return y; +function pickFirst(x: Option, y: Option) returns (Option) { + match (x) { + case Option.Some(v) { + return orElse(x, y); + } + case Option.None { + return y; + } } } ``` @@ -156,7 +186,7 @@ forall a . function pickFirst(x : Option(a), y : Option(a)) -> Option(a) { ## The Subsumption Test -When a function carries a `forall` annotation, the compiler verifies that the +When a function declares generic parameters, the compiler verifies that the body is at least as polymorphic as the declared signature. This check is called the _subsumption test_. It prevents signatures that claim more generality than the body actually provides. @@ -178,7 +208,7 @@ type such as `word`. ```solidity // Error: the body always returns word, but the annotation says a. -forall a . function wrong(x : word) -> a { +function wrong(x: word) returns (A) { return x; } ``` @@ -189,12 +219,14 @@ forall a . word -> a but the infered type is: word -> word in: -forall a . function wrong (x : word) -> a +function wrong(x: word) returns (A) ``` -The body `return x` has type `word -> word` because `x` is declared as -`word`. The skolemised declared type requires the result to be a rigid -variable `a`, which cannot be unified with `word`. The compiler rejects the +The diagnostic renders inferred type schemes with mathematical `forall` and +`->` notation; those tokens are not source syntax. The body `return x` has +scheme `word -> word` because `x` is declared as `word`. The skolemised declared +type requires the result to be a rigid variable `A`, which cannot be unified +with `word`. The compiler rejects the definition. ### Error: wrong type variable in the return position @@ -202,11 +234,13 @@ definition. A function that swaps the return type variable is caught by the same test. ```solidity -// Error: the body returns the first component (type a), -// but the annotation declares the return type as b. -forall a b . function fst(p : (a, b)) -> b { - match p { - | (x, y) => return x; +// Error: the body returns the first component (type A), +// but the annotation declares the return type as B. +function fst(p: (A, B)) returns (B) { + match (p) { + case (x, y) { + return x; + } } } ``` @@ -217,13 +251,13 @@ forall a b . (a, b) -> b but the infered type is: forall $t . ($t, $t) -> $t in: -forall a b . function fst (p : (a, b)) -> b +function fst(p: (A, B)) returns (B) ``` The body returns `x`, which has the type of the first component. The inferred type therefore unifies both components and the return, making them all the same variable `$t`. The skolemised declared type requires the return to be the -rigid variable `b` (the second component), which is distinct from `a`. The +rigid variable `B` (the second component), which is distinct from `A`. The unification fails and the compiler reports the error. ### Error: type variable forced to `word` by an assembly block @@ -234,10 +268,10 @@ forces that variable to `word`, making the function monomorphic in the body while the annotation still declares a type variable. ```solidity -// Error: the assembly block forces a to word, +// Error: the assembly block forces A to word, // so the body is monomorphic. -forall a . function double(x : a) -> a { - let res : word; +function double(x: A) returns (A) { + let res: word; assembly { res := add(x, x) } return res; } @@ -249,15 +283,15 @@ forall a . a -> a but the infered type is: word -> word in: -forall a . function double (x : a) -> a +function double(x: A) returns (A) ``` The correct way to write this function is to restrict the parameter type to -`word` explicitly and drop the `forall`: +`word` explicitly and drop the generic parameter: ```solidity -function double(x : word) -> word { - let res : word; +function double(x: word) returns (word) { + let res: word; assembly { res := add(x, x) } return res; } diff --git a/doc/src/sail/syntax.md b/doc/src/sail/syntax.md index a6e45b524..e4aa5f7d0 100644 --- a/doc/src/sail/syntax.md +++ b/doc/src/sail/syntax.md @@ -1,575 +1,392 @@ # Syntax -This page is a complete grammar reference for SAIL. Every grammar rule appears -as its own section, followed by a railroad diagram. Rounded boxes denote -terminal tokens (keywords and punctuation); rectangular boxes denote -non-terminals and link to the corresponding rule. The notation follows standard -EBNF conventions: `[ … ]` marks optional elements and `{ … }` marks -zero-or-more repetition. +SAIL uses a Solidity-style surface syntax with a statically typed functional +core. This page summarizes the source grammar. The machine-readable EBNF is in +[`doc/railroad/sail.bnf`](../../railroad/sail.bnf). ---- - -## Parser Rules - -### CompilationUnit - -A SAIL source file is a sequence of import declarations and top-level -declarations in any order. Imports are not required to precede declarations. - -![CompilationUnit](diagrams/CompilationUnit.svg) - ---- - -### TopDecl - -A top-level declaration is one of: a contract, a free function, a type class, -an instance, an algebraic data type, a type synonym, an export declaration, a -pragma, or an operator declaration. - -![TopDecl](diagrams/TopDecl.svg) - ---- - -### Import - -An import declaration makes names from another module available in the current -module. The `@package.` prefix selects an external package; the `lib.` prefix -selects a standard library module. - -![Import](diagrams/Import.svg) - ---- - -### ModulePath - -A module path is a dot-separated sequence of identifiers that locates a module -within a package. - -![ModulePath](diagrams/ModulePath.svg) - ---- - -### ImportItems - -The list of names to import from a module, enclosed in braces. - -![ImportItems](diagrams/ImportItems.svg) - ---- - -### ImportItem - -A single item to import. Four forms are available: - -- `*` imports all exported names (wildcard). -- `Name` imports a single name into the unqualified scope. -- `Name as Alias` imports a single name under a local alias. The original name is not placed in scope. -- `(sym)` imports an operator symbol and enables its infix syntax in this file. - -![ImportItem](diagrams/ImportItem.svg) - ---- - -### OperatorSymbol - -A non-empty sequence of operator characters (`+-*/%<>=!&|^~#?` or Unicode -mathematical symbols in the range U+2200 to U+23FF), written between -parentheses in import and export item lists. - -![OperatorSymbol](diagrams/OperatorSymbol.svg) - ---- - -### HidingList - -A comma-separated list of names to exclude from an import. - -![HidingList](diagrams/HidingList.svg) - ---- - -### ExportDecl - -An export declaration controls which names this module exposes to other modules. - -![ExportDecl](diagrams/ExportDecl.svg) - ---- - -### ExportItems - -The list of names to export from the current module, enclosed in braces. - -![ExportItems](diagrams/ExportItems.svg) - ---- - -### ExportItem - -A single export entry. It may be the wildcard `*`, a plain name, a name together -with its constructors, all names from a module, or an operator symbol in -parentheses. - -![ExportItem](diagrams/ExportItem.svg) - ---- - -### ExportConstructors - -Selects which data constructors to re-export alongside a type name: either all -constructors (`*`) or an explicit list. - -![ExportConstructors](diagrams/ExportConstructors.svg) - ---- - -### ExportFromItems - -The list of names re-exported from another module. - -![ExportFromItems](diagrams/ExportFromItems.svg) - ---- - -### ExportFromItem - -A single entry in a re-export list: the wildcard `*`, a plain name, or a name -with explicit constructor re-exports. - -![ExportFromItem](diagrams/ExportFromItem.svg) - ---- - -### Pragma - -A pragma adjusts compiler behaviour for type class constraint checking. Without -targets the pragma applies to all classes; with a list of identifiers it applies -only to those specific classes. - -![Pragma](diagrams/Pragma.svg) - ---- - -### PragmaKind - -The three available pragma kinds relax, respectively, the coverage condition, -the Patterson condition, and the bound-variable condition for type class -instance resolution. - -![PragmaKind](diagrams/PragmaKind.svg) - ---- - -### PragmaTargets - -A comma-separated list of class names to which a pragma applies. - -![PragmaTargets](diagrams/PragmaTargets.svg) - ---- - -### Type - -A type is one of: a named type constructor applied to zero or more type -arguments, a function type of the form `(T₁, …, Tₙ) -> T`, a tuple or unit -type written as a parenthesised comma-separated list, or a proxy type `@T`. - -![Type](diagrams/Type.svg) - ---- - -### TypeList - -A comma-separated (possibly empty) list of types, used as arguments to type -constructors and as the parameter list of function types. - -![TypeList](diagrams/TypeList.svg) - ---- - -### TypeVarSeq - -A space-separated sequence of type-variable names following a `forall` keyword. -All listed names are universally quantified over the scope of the accompanying -signature. - -![TypeVarSeq](diagrams/TypeVarSeq.svg) - ---- - -### TypeVarParams - -A comma-separated list of type-variable names enclosed in parentheses. Used in -`data`, `type`, and `class` declarations to introduce parametric type -arguments. - -![TypeVarParams](diagrams/TypeVarParams.svg) - ---- - -### TypeName - -A possibly qualified type name. Simple names are single identifiers; qualified -names chain module components with `.`. - -![TypeName](diagrams/TypeName.svg) - ---- - -### DataDef - -An algebraic data type declaration. The optional parameter list introduces -type variables. The optional body lists the constructors separated by `|`. - -![DataDef](diagrams/DataDef.svg) - ---- - -### DataConstrs - -One or more data constructor definitions separated by `|`. - -![DataConstrs](diagrams/DataConstrs.svg) - ---- - -### DataConstr - -A single data constructor: a name optionally followed by a -parenthesised, comma-separated list of field types. - -![DataConstr](diagrams/DataConstr.svg) - ---- - -### TypeSynonym - -A type synonym introduces an alias for an existing type. The optional parameter -list introduces type variables that may appear in the right-hand side. - -![TypeSynonym](diagrams/TypeSynonym.svg) - ---- - -### Pattern - -A pattern appears in `match` equations to deconstruct a value by its -constructor. The dot-prefix form (`.Name`) is a contextual shorthand: the -constructor is resolved from the type being matched. - -![Pattern](diagrams/Pattern.svg) - ---- - -### PatternList - -A comma-separated list of patterns used as the argument list of a constructor -pattern or as the simultaneous arguments of a `match` equation. - -![PatternList](diagrams/PatternList.svg) - ---- - -### Expr - -An expression computes a value. Binary operators follow standard precedence: -arithmetic binds tighter than comparison, which binds tighter than logical. All -binary operators are left-associative except `if-then-else`, which is -right-associative. - -![Expr](diagrams/Expr.svg) - ---- - -### ExprList - -A comma-separated (possibly empty) list of expressions used as function -arguments. - -![ExprList](diagrams/ExprList.svg) - ---- - -### Literal - -A literal value: a decimal or hexadecimal integer, or a double-quoted string. - -![Literal](diagrams/Literal.svg) +`[ ... ]` marks an optional element and `{ ... }` marks repetition in grammar +fragments on this page. --- -### Stmt +## Source Files -A statement is an executable step inside a function body. Assignment operators -`=`, `+=`, and `-=` require a terminating `;`. `let` declares a local variable, -optionally with a type annotation and an initialiser. The `for` statement provides -a C-style counted loop; its initialisation and post-iteration clauses obey the -`ForInitStmt` and `ForPostStmt` grammars respectively. +A source file contains imports, pragmas, and top-level declarations in any +order: -![Stmt](diagrams/Stmt.svg) +```text +CompilationUnit = { Import | Pragma | TopDecl } +``` ---- +Both Classic and Core Solidity use the `.sol` extension in the language +specification. The prototype may temporarily accept `.solc` files. -### Body - -A brace-enclosed sequence of zero or more statements forming the body of a -function, branch, or constructor. - -![Body](diagrams/Body.svg) +Identifiers begin with a letter or underscore and may contain letters, decimal +digits, and underscores. Integer literals may be decimal or `0x`-prefixed +hexadecimal values. Strings use double quotes. --- -### ForInitStmt +## Imports -The initialisation clause of a `for` loop. It may be an assignment, a compound -assignment, a `let` binding (typed or untyped, with or without an initialiser), -or a plain expression. Unlike a regular statement, there is no trailing `;` — the -semicolons are written explicitly in the `for(…; …; …)` header. +Module paths are dotted names. An external package path begins with +`@package.`. -![ForInitStmt](diagrams/ForInitStmt.svg) +```solidity +import std; +import std.dispatch; +import * as dispatch from std.dispatch; +import {address, uint256 as U256} from std; +import {foo, bar as baz} from @ext.foo.bar; +``` ---- +Core rejects string paths and selector-after-module ordering; selective names +always precede `from`. -### ForPostStmt +The current compiler also supports `import {*} from M` and an optional +`hiding {X, Y}` clause as module-system extensions. -The post-iteration clause executed after each loop body. It follows the same -grammar as `ForInitStmt`. A `let` binding introduced here is scoped to the body -of that single iteration. +### Exports -![ForPostStmt](diagrams/ForPostStmt.svg) +The canonical new syntax does not yet select an export or re-export spelling. +The compiler currently retains its existing `export` declarations as an +implementation extension. See [Modules](modules.md) for those provisional +forms. --- -### MatchArgs +## Pragmas -One or more comma-separated expressions forming the scrutinees of a `match` -statement. +Solidity and ABI-coder pragmas retain their familiar spelling: -![MatchArgs](diagrams/MatchArgs.svg) +```solidity +pragma solidity ^0.8.23; +pragma abicoder v2; +``` ---- - -### Equation - -A single match arm: a `|`-prefixed list of patterns followed by `=>` and a -sequence of statements. The patterns are matched positionally against the -scrutinee list. +Solcore-specific pragmas use the `solcore` namespace: -![Equation](diagrams/Equation.svg) +```solidity +pragma solcore noCoverageCondition; +pragma solcore noPattersonCondition; +pragma solcore noBoundVariableCondition; +pragma solcore noGenericInstanceFor MyType; +``` --- -### Param +## Types -A single function parameter: a name with an explicit type annotation, or an -untyped name whose type will be inferred. +Named and generic types use dotted names and angle brackets: -![Param](diagrams/Param.svg) +```solidity +word +pkg.Option +collections.Map> +``` ---- - -### ParamList +Other type forms are: -A comma-separated list of function parameters. +```solidity +mapping(address => word) +word[] +word[4] +(word, bool) +() +function(word) internal returns (bool) +bytes memory +bytes calldata +``` -![ParamList](diagrams/ParamList.svg) +Array suffixes and the data locations `memory`, `storage`, and `calldata` +follow the complete element type. Function types use `function(...)` and +`returns (...)`; the former source-level arrow type is not part of the grammar. ---- +Explicit conversion uses `as` with a complete target type: -### Function +```solidity +let n = raw as word; +let callback = value as function(word) internal returns (bool); +let result = value as pkg.Result; +``` -A function definition. The long form uses a brace-enclosed statement block as -the body. The short form uses a single expression whose value is returned -implicitly (Rust-style). +There is no general `expression: Type` annotation form. Use a typed binding +when an expression needs an expected type: -![Function](diagrams/Function.svg) +```solidity +let value: T = expression; +``` --- -### Signature +## Structs, Enums, and Type Declarations -A function signature declares the function name, its parameter list, and the -optional return type. It may be preceded by a polymorphism prefix to introduce -type variables and constraints. +Struct fields use name-first declarations: -![Signature](diagrams/Signature.svg) +```solidity +struct Pair { + x: word; + y: word; +} +``` ---- - -### SigPrefix +Ordinary enums and payload-carrying algebraic data types share one declaration +form: -An optional `forall` quantifier that precedes a function or method signature. It -introduces universally quantified type variables and, optionally, a list of type -class constraints that callers must satisfy. +```solidity +enum Status { + Pending, + Filled, + Cancelled +} -![SigPrefix](diagrams/SigPrefix.svg) +enum Option { + None, + Some(T) +} +``` ---- +Constructors are qualified in expressions and patterns: -### ConstraintList +```solidity +Option.Some(1) +Option.None +``` -A comma-separated list of type class constraints. +A user-defined type uses `is`: -![ConstraintList](diagrams/ConstraintList.svg) +```solidity +type Wad is word; +``` --- -### Constraint +## Traits, Implementations, and Generics -A single type class constraint of the form `Type : ClassName` or -`Type : ClassName(T₁, …, Tₙ)`. It asserts that the given type is an instance -of the named class, possibly with additional type parameters. +Type classes use `trait`; implementations use `impl`. Generic parameters +follow the declared name in angle brackets, and constraints follow the head in +a `where` clause. -![Constraint](diagrams/Constraint.svg) +```solidity +trait Eq { + function eq(x: T, y: T) returns (bool); +} ---- +impl Eq { + function eq(x: word, y: word) returns (bool) { + return x == y; + } +} -### ClassDef +impl Eq> where T: Eq { + function eq(x: Option, y: Option) returns (bool) { + return true; + } +} +``` -A type class declaration. The self-variable (the first identifier after -`class`) is the main type being constrained. The optional comma-separated list -in parentheses introduces auxiliary associated type variables. The body lists -method signatures, each terminated by `;`. - -![ClassDef](diagrams/ClassDef.svg) +The compiler also accepts `default impl` as an implementation-selection +extension. Legacy generic and type-class declaration spellings are not source +syntax. --- -### InstDef +## Contracts and Fields -An instance declaration provides method implementations for a specific type. -The optional `default` keyword marks the instance as an overlappable fallback -when no more specific instance is found. +Contracts, interfaces, and libraries use Solidity-style shells. Every named +field and parameter places the name before its type. -![InstDef](diagrams/InstDef.svg) +```solidity +contract Token { + balances: mapping(address => word); ---- + constructor(initialSupply: word) payable { + balances[msg.sender] = initialSupply; + } -### Contract + function balanceOf(account: address) public returns (word) { + return balances[account]; + } -A contract groups fields, nested data types, methods, and an optional -constructor. The optional parameter list makes the contract generic over type -variables. + fallback() external payable { + // Handle unmatched selectors. + } +} +``` -![Contract](diagrams/Contract.svg) +The initial Core surface has one general `fallback` entry point and no separate +`receive`. A fallback must be `external`; it may also be `payable`. ---- +Interfaces contain semicolon-terminated function signatures, while libraries +contain fields, structs, enums, and function definitions: -### ContractDecl +```solidity +interface Hashable { + function hash(value: word) external returns (word); +} -A single declaration inside a contract body: a field, a data type, a function, -or a constructor. - -![ContractDecl](diagrams/ContractDecl.svg) +library Hashing { + function hash(value: word) internal returns (word) { + return value; + } +} +``` --- -### FieldDecl +## Functions -A contract field declaration. The type annotation is mandatory; the initialiser -expression is optional. +Function parameters are name-first. Attributes follow the parameter list, and +results use `returns (...)`. -![FieldDecl](diagrams/FieldDecl.svg) +```solidity +function addOne(x: word) pure returns (word) { + return x + 1; +} ---- +function pair() returns (word, word) { + return (1, 2); +} -### Constructor +function namedResult() returns (result: word) { + return 1; +} -A contract constructor is invoked exactly once at deployment time. It has an -explicit parameter list and a statement block body. +function nop() { + return; +} +``` -![Constructor](diagrams/Constructor.svg) +Generic parameters follow the function name. Constraints appear after the +return clause. ---- +```solidity +function id(x: T) returns (T) { + return x; +} -### AsmBlock +function eqSelf(x: T) returns (bool) where T: Eq { + return Eq.eq(x, x); +} +``` -An inline assembly block embeds Yul statements directly in SAIL source code, -giving direct access to EVM opcodes. +`comptime` immediately precedes the binding it modifies: -![AsmBlock](diagrams/AsmBlock.svg) +```solidity +function pow(comptime n: word, x: word) returns (word) { + let comptime exponent = n; + return x ** exponent; +} +``` --- -### YulStmt +## Local Bindings and Statements -A statement in the Yul sublanguage. Yul provides low-level control flow (`if`, -`switch`, `for`, `break`, `continue`, `leave`) and variable declarations and -assignments using `:=`. +Local variables use `let`, with or without an explicit type or initializer: -![YulStmt](diagrams/YulStmt.svg) +```solidity +let amount: word = readAmount(); +let owner: address; +let inferred = computeValue(); +let (left, right): (word, bool) = readResult(); +``` ---- - -### YulCase +Statements use semicolon terminators where shown: -A single `case` arm in a Yul `switch` statement: a literal value followed by a -block of Yul statements. +```solidity +return; +return value; +if (condition) { ... } else { ... } +for (let i: word = 0; i < n; i = i + 1) { ... } +while (condition) { ... } +break; +continue; +unchecked { ... } +assembly { ... } +revert; +``` -![YulCase](diagrams/YulCase.svg) +Assignments support `=`, compound assignment operators, field access, and +indexing. A plain call or other expression used as a statement also ends in +`;`. --- -### YulExpr +## Pattern Matching -A Yul expression: a literal, a variable reference, a function call, or a call -to the special `return` built-in. +`match` encloses one or more scrutinees in parentheses. Each arm has its own +block. -![YulExpr](diagrams/YulExpr.svg) +```solidity +match (value) { + case Option.Some(x) { + return x; + } + case Option.None { + return 0; + } +} ---- +match (x, y) { + case (Option.Some(a), Option.Some(b)) { + return a + b; + } + default { + return 0; + } +} +``` + +The compiler extension `.Constructor` is available when an expected type makes +the constructor family unambiguous. + +--- + +## Expressions + +Expressions include literals, names, tuples, calls, field access, indexing, +unary and binary operators, conditional expressions, and conversions: -### YulNames - -A comma-separated list of identifiers used as the left-hand side of a Yul -multi-assignment or the names in a Yul `let` declaration. - -![YulNames](diagrams/YulNames.svg) - ---- - -### YulExprList - -A comma-separated list of Yul expressions used as arguments to a Yul function -call. - -![YulExprList](diagrams/YulExprList.svg) - ---- - -### YulLiteral - -A Yul literal value: a decimal or hexadecimal integer, or a string. - -![YulLiteral](diagrams/YulLiteral.svg) - ---- - -## Lexer Rules - -### Identifier - -An identifier begins with a letter (upper or lower case) and may contain -letters, decimal digits, and underscores. Identifiers are used for variable -names, function names, type names, module components, and constructor names. - -![Identifier](diagrams/Identifier.svg) - ---- - -### Integer - -An integer literal is either a sequence of decimal digits or a hexadecimal -literal prefixed with `0x`. - -![Integer](diagrams/Integer.svg) - ---- +```solidity +f(x, y) +token.balanceOf(account) +values[index] +!ok +x ** exponent +x * y + z +x << bits +x & mask +x == y +condition ? yes : no +expression as T +``` + +Power is right-associative. Multiplication and addition, shifts, comparisons, +equality, bitwise operators, logical operators, and the conditional operator +then follow in decreasing precedence. Conversion with `as` binds more tightly +than power and is left-associative. + +The compiler retains `lam(...) returns (...) { ... }` for lambda expressions as +a Core extension. -### StringLiteral +--- -A string literal is a sequence of characters enclosed in double quotes. -Supported escape sequences are `\n` (newline), `\t` (tab), and `\"` (literal -double quote). +## Assembly + +An `assembly { ... }` block embeds the Yul sublanguage. Yul declarations, +assignment, `if`, `switch`, and `for` retain Yul syntax and do not use SAIL +statement terminators. -![StringLiteral](diagrams/StringLiteral.svg) +```solidity +function load(slot: word) returns (word) { + let value: word; + assembly { + value := sload(slot) + } + return value; +} +``` + +Only surrounding values represented as `word` may be referenced directly from +Yul. diff --git a/doc/src/sail/type-inference.md b/doc/src/sail/type-inference.md index 50a0cb1df..02d3ffb26 100644 --- a/doc/src/sail/type-inference.md +++ b/doc/src/sail/type-inference.md @@ -18,14 +18,15 @@ any annotation. ## What Requires Annotations Every top-level function must carry a complete type signature: every parameter -must be annotated and the return type must be declared. The compiler rejects -any top-level definition that omits an annotation. This rule applies to free -functions and to functions defined inside a contract body. +must be annotated, and a value-producing function must declare its result in a +`returns (...)` clause. Omitting that clause declares a unit-returning function. +This rule applies to free functions and to functions defined inside a contract +body. ```solidity -// Required: every parameter and the return type are annotated. -function transfer(to : word, amount : word) -> () { - let bal : word; +// Required: every parameter is annotated; no returns clause means unit. +function transfer(to: word, amount: word) { + let bal: word; assembly { bal := sload(caller()) } assembly { sstore(caller(), sub(bal, amount)) } assembly { sstore(to, add(sload(to), amount)) } @@ -65,7 +66,7 @@ When a `let` declaration includes an initialiser, the type is taken from the initialiser expression. Integer literals always have type `word`. ```solidity -function demo() -> word { +function demo() returns (word) { let amount = 100; // amount : word, from the integer literal let flag = true; // flag : bool, from the boolean literal return amount; @@ -78,13 +79,13 @@ When no initialiser is present, the type is inferred from the first use of the variable. ```solidity -function loadBalance(account : word) -> word { - let bal : word; // annotated; no inference needed +function loadBalance(account: word) returns (word) { + let bal: word; // annotated; no inference needed assembly { bal := sload(account) } return bal; } -function compute(account : word) -> word { +function compute(account: word) returns (word) { let x; // no annotation, no initialiser x = sload(account); // first assignment: x : word return x; @@ -97,9 +98,12 @@ A variable whose type depends on an algebraic data type can have its type fixed by the expected return type. ```solidity -data Result = Ok(word) | Err(word); +enum Result { + Ok(word), + Err(word) +} -function demo() -> Result { +function demo() returns (Result) { let x = Result.Err(0); // x : Result, inferred from constructor return x; } @@ -118,9 +122,13 @@ compiler uses the context to determine which type the constructor belongs to. The declared return type provides the expected type: ```solidity -data TxStatus = Pending | Confirmed | Reverted; +enum TxStatus { + Pending, + Confirmed, + Reverted +} -function initialStatus() -> TxStatus { +function initialStatus() returns (TxStatus) { return .Pending; // resolved as TxStatus.Pending from the return type } ``` @@ -130,10 +138,14 @@ function initialStatus() -> TxStatus { The declared type of the left-hand side provides the expected type: ```solidity -data TxStatus = Pending | Confirmed | Reverted; +enum TxStatus { + Pending, + Confirmed, + Reverted +} -function demo() -> TxStatus { - let s : TxStatus; +function demo() returns (TxStatus) { + let s: TxStatus; s = .Confirmed; // resolved as TxStatus.Confirmed from the declared type of s return s; } @@ -145,9 +157,13 @@ If no expected type is available, the shorthand cannot be resolved and the compiler reports an error: ```solidity -data TxStatus = Pending | Confirmed | Reverted; +enum TxStatus { + Pending, + Confirmed, + Reverted +} -function bad() -> word { +function bad() returns (word) { let x = .Pending; // no expected type available for x return 0; } @@ -158,7 +174,7 @@ Cannot resolve shorthand constructor expression without expected constructor typ .Pending ``` -The fix is to annotate the variable: `let x : TxStatus = .Pending;`. +The fix is to annotate the variable: `let x: TxStatus = .Pending;`. --- @@ -169,7 +185,7 @@ overloading for integer literals in SAIL. Every integer literal that appears in source code is a 256-bit EVM word value. ```solidity -function demo() -> word { +function demo() returns (word) { let amount = 1000; let decimals = 18; let mask = 0xffffffffffffffffffffffffffffffffffffffff; @@ -186,12 +202,12 @@ variables from the types of the supplied arguments. No explicit type application is needed. ```solidity -forall a . function id(x : a) -> a { +function id(x: A) returns (A) { return x; } -function demo() -> word { - return id(42); // a instantiated to word at this call site +function demo() returns (word) { + return id(42); // A instantiated to word at this call site } ``` @@ -199,16 +215,20 @@ For a pair function with two type variables, both are instantiated independently: ```solidity -data Pair(a, b) = Pair(a, b); +enum Pair { + Pair(A, B) +} -forall a b . function fst(p : Pair(a, b)) -> a { - match p { - | Pair(x, y) => return x; +function fst(p: Pair) returns (A) { + match (p) { + case Pair(x, y) { + return x; + } } } -function demo() -> word { - return fst(Pair(42, true)); // a = word, b = bool +function demo() returns (word) { + return fst(Pair(42, true)); // A = word, B = bool } ``` @@ -229,20 +249,20 @@ found, the compiler reports an unsolved constraint error. ### Constraint resolved at call site ```solidity -forall a . class a:Encodable { - function encode(x : a) -> word; +trait Encodable { + function encode(x: A) returns (word); } -instance word:Encodable { - function encode(x : word) -> word { return x; } +impl Encodable { + function encode(x: word) returns (word) { return x; } } -forall a . a:Encodable => function encodeField(x : a) -> word { +function encodeField(x: A) returns (word) where A: Encodable { return Encodable.encode(x); } contract ERC20 { - function main() -> word { + function main() returns (word) { return encodeField(42); // a = word; word:Encodable resolved } } @@ -255,12 +275,12 @@ reports which constraint could not be satisfied and which instances are defined: ```solidity -forall a . class a:SafeArith { - function safeAdd(x : a, y : a) -> a; +trait SafeArith { + function safeAdd(x: A, y: A) returns (A); } -// No instance for word is declared. -function bad(x : word, y : word) -> word { +// No implementation for word is declared. +function bad(x: word, y: word) returns (word) { return SafeArith.safeAdd(x, y); } ``` @@ -272,12 +292,12 @@ using defined instances: ``` -The fix is either to declare `instance word:SafeArith { ... }` or to add the +The fix is either to declare `impl SafeArith { ... }` or to add the constraint to the calling function's signature so the obligation is propagated to the caller: ```solidity -forall a . a:SafeArith => function bad(x : a, y : a) -> a { +function bad(x: A, y: A) returns (A) where A: SafeArith { return SafeArith.safeAdd(x, y); } ``` @@ -292,9 +312,11 @@ function body and the phantom parameter cannot be determined from the context, the compiler reports an ambiguous type variable error. ```solidity -data TypedSlot(a) = TypedSlot(word); // a is phantom: it appears in no field +enum TypedSlot { + TypedSlot(word) // A is phantom: it appears in no field +} -function bad() -> word { +function bad() returns (word) { let s = TypedSlot.TypedSlot(0); // a is unconstrained; no context fixes it return 0; } @@ -310,8 +332,8 @@ The fix is to annotate the `let` declaration with the full type, giving the phantom parameter a concrete value: ```solidity -function good() -> word { - let s : TypedSlot(word) = TypedSlot.TypedSlot(0); +function good() returns (word) { + let s: TypedSlot = TypedSlot.TypedSlot(0); return 0; } ``` @@ -327,28 +349,36 @@ triggered the failure. ### Return type mismatch ```solidity -function bad(amount : word) -> bool { - return amount; // amount : word; expected bool +function bad(amount: word) returns (bool) { + return amount; // amount: word; expected bool } ``` ``` Types: bool and word do not unify - - in: function bad (amount : word) -> bool { return amount; } + - in: function bad(amount: word) returns (bool) { return amount; } ``` ### Match arm return type mismatch -All arms of a `match` expression must produce the same type. Returning +All arms of a `match` statement must agree with the function's declared result. +Returning different types in different arms is a unification error: ```solidity -data Result = Ok(word) | Err(word); +enum Result { + Ok(word), + Err(word) +} -function bad(r : Result) -> word { - match r { - | Result.Ok(v) => return v; - | Result.Err(_) => return false; // word expected; bool returned +function bad(r: Result) returns (word) { + match (r) { + case Result.Ok(v) { + return v; + } + case Result.Err(_) { + return false; // word expected; bool returned + } } } ``` @@ -356,7 +386,7 @@ function bad(r : Result) -> word { ``` Types: bool and word do not unify - in: false - - in: function bad (r : Result) -> word { ... } + - in: function bad(r: Result) returns (word) { ... } ``` ### Algebraic data type vs primitive mismatch @@ -365,16 +395,19 @@ User-defined types and primitive types such as `word` are never interchangeable: ```solidity -data TxStatus = Pending | Confirmed; +enum TxStatus { + Pending, + Confirmed +} -function bad(n : word) -> TxStatus { +function bad(n: word) returns (TxStatus) { return n; // word is not TxStatus } ``` ``` Types: TxStatus and word do not unify - - in: function bad (n : word) -> TxStatus { return n; } + - in: function bad(n: word) returns (TxStatus) { return n; } ``` --- diff --git a/doc/src/sail/typeclasses.md b/doc/src/sail/typeclasses.md index e6d1991fb..f3d07503d 100644 --- a/doc/src/sail/typeclasses.md +++ b/doc/src/sail/typeclasses.md @@ -8,83 +8,85 @@ that the variable belongs to one or more classes. --- -## Class Declarations +## Trait Declarations -A class declaration introduces a class name, a main type variable, and zero or -more method signatures. The `forall` quantifier is required when the class -declaration introduces type variables. +A trait declaration introduces a type-class name, a main type variable, and +zero or more method signatures. Generic parameters follow the trait name in +angle brackets. ```solidity -forall a . class a:Eq { - function eq(x : a, y : a) -> bool; - function ne(x : a, y : a) -> bool; +trait Eq { + function eq(x: A, y: A) returns (bool); + function ne(x: A, y: A) returns (bool); } ``` -The type variable `a` that appears immediately before the colon is the _main -type argument_ of the class. Every instance must supply a concrete type for this -variable. +The first type variable, `A`, is the _main type argument_ of the type class. +Every implementation must supply a concrete type for this variable. A class with no methods defines a pure marker class: ```solidity -forall a . class a:Serializable {} +trait Serializable {} ``` ### Superclass Constraints -A class may require that its main type argument already belongs to another -class. This constraint is called a _superclass constraint_ and is written before -the `class` keyword with `=>`. +A trait may require that its main type argument already belongs to another type +class. This constraint is called a _superclass constraint_ and follows the +trait head in a `where` clause. ```solidity -forall a . a:Eq => class a:Ord { - function lt(x : a, y : a) -> bool; - function lte(x : a, y : a) -> bool; +trait Ord where A: Eq { + function lt(x: A, y: A) returns (bool); + function lte(x: A, y: A) returns (bool); } ``` -Any instance of `Ord` must also be an instance of `Eq`. The compiler verifies -this at each instance declaration. If a function requires `a:Ord`, the -constraint `a:Eq` is automatically available without listing it explicitly. +Any implementation of `Ord` must also satisfy `Eq`. The compiler verifies this +at each impl declaration. If a function requires `A: Ord`, the constraint +`A: Eq` is automatically available without listing it explicitly. --- -## Instance Declarations +## Impl Declarations -An instance declaration provides implementations for all methods of a class for -a specific type. The instance head names the class and supplies a concrete type -for the main type variable. +An impl declaration provides implementations for all methods of a trait for a +specific type. The impl head names the trait and supplies a concrete type for +the main type variable. ```solidity -instance word:Eq { - function eq(x : word, y : word) -> bool { - let res : word; +impl Eq { + function eq(x: word, y: word) returns (bool) { + let res: word; assembly { res := eq(x, y) } return res; } - function ne(x : word, y : word) -> bool { - let res : word; + function ne(x: word, y: word) returns (bool) { + let res: word; assembly { res := iszero(eq(x, y)) } return res; } } ``` -A polymorphic instance applies to a family of types. The `forall` quantifier -lists the type variables that appear in the instance head: +A polymorphic implementation applies to a family of types. Generic parameters +on `impl` list the type variables that appear in the impl head: ```solidity -data Pair(a, b) = Pair(a, b); +enum Pair { + Pair(A, B) +} -forall a b . a:Eq, b:Eq => instance Pair(a, b):Eq { - function eq(x : Pair(a, b), y : Pair(a, b)) -> bool { - match x, y { - | Pair(xa, xb), Pair(ya, yb) => - return Eq.eq(xa, ya); +impl Eq> where A: Eq, B: Eq { + function eq(x: Pair, y: Pair) returns (bool) { + match (x, y) { + case (Pair(xa, xb), Pair(ya, yb)) { + return Eq.eq(xa, ya); + } } } - function ne(x : Pair(a, b), y : Pair(a, b)) -> bool { + function ne(x: Pair, y: Pair) returns (bool) { return Eq.ne(x, y); } } @@ -92,68 +94,78 @@ forall a b . a:Eq, b:Eq => instance Pair(a, b):Eq { ### Calling Class Methods -Class methods are called with a qualified name of the form `ClassName.method`. -The compiler resolves the correct instance from the types of the arguments: +Trait methods are called with a qualified name of the form `TraitName.method`. +The compiler resolves the correct implementation from the argument types: ```solidity -data Option(a) = None | Some(a); +enum Option { + None, + Some(A) +} -forall a . a:Eq => function senderMatches(sender : a, expected : Option(a)) -> bool { - match expected { - | Option.None => return false; - | Option.Some(e) => return Eq.eq(sender, e); +function senderMatches(sender: A, expected: Option) + returns (bool) + where A: Eq +{ + match (expected) { + case Option.None { + return false; + } + case Option.Some(e) { + return Eq.eq(sender, e); + } } } ``` -### Overlapping Instances +### Overlapping Implementations -SAIL does not support overlapping instances. Two instances overlap when the same -type can match both instance heads. The compiler reports an error at the second +SAIL does not support overlapping implementations. Two impls overlap when the +same type can match both heads. The compiler reports an error at the second declaration: ```solidity -data Box(a) = Box(word); -forall a . class a:C {} +enum Box { + Box(word) +} +trait C {} -forall a . instance Box(a):C {} +impl C> {} -// Error: overlaps with the more general instance above. -instance Box(word):C {} +// Error: overlaps with the more general implementation above. +impl C> {} ``` ``` -Overlapping instances are not supported -instance: -Box(word) : C +Overlapping implementations are not supported +impl C> overlaps with: -Box(?$3) : C +impl C> ``` --- ## Main and Weak Type Arguments -When a class has more than one type parameter, the parameter immediately before -the colon in the class head is called the _main type argument_. The remaining -parameters, listed after the class name in parentheses, are called _weak type +When a trait has more than one type parameter, the first parameter is called +the _main type argument_. The remaining parameters are called _weak type arguments_. ```solidity // main ──┐ ┌── weak -forall a b . class a:Convert(b) { - function convert(x : a) -> b; +trait Convert { + function convert(x: A) returns (B); } ``` The distinction matters for instance resolution and for the three soundness conditions the compiler enforces. -**Main type argument** (`a` in `a:Convert(b)`): used as the primary key for +**Main type argument** (`A` in `Convert`): used as the primary key for instance lookup. The compiler selects an instance by matching the main type first. It must be determinable independently of the weak arguments. -**Weak type arguments** (`b` in `a:Convert(b)`): represent additional types +**Weak type arguments** (`B` in `Convert`): represent additional types involved in the relationship. They may be determined by the main type argument through the coverage condition, but they cannot introduce type variables that are unconstrained at the call site. @@ -165,29 +177,36 @@ type `Ether`. The instance is well formed because the weak type variable is replaced by a concrete type: ```solidity -forall a b . class a:Convert(b) { - function convert(x : a) -> b; +trait Convert { + function convert(x: A) returns (B); } -data Wei = Wei(word); -data Ether = Ether(word); +enum Wei { + Wei(word) +} +enum Ether { + Ether(word) +} -instance Wei:Convert(Ether) { - function convert(x : Wei) -> Ether { - match x { - | Wei.Wei(w) => - let e : word; - assembly { e := div(w, 1000000000000000000) } - return Ether.Ether(e); +impl Convert { + function convert(x: Wei) returns (Ether) { + match (x) { + case Wei.Wei(w) { + let e: word; + assembly { e := div(w, 1000000000000000000) } + return Ether.Ether(e); + } } } } contract C { - function main() -> word { + function main() returns (word) { let result = Convert.convert(Wei.Wei(2000000000000000000)); - match result { - | Ether.Ether(v) => return v; + match (result) { + case Ether.Ether(v) { + return v; + } } } } @@ -211,29 +230,31 @@ main type must cover all type variables bound by the weak types. **Rejected example** ```solidity -data Box(a) = Box(word); -forall a b . class a:MyClass(b) {} +enum Box { + Box(word) +} +trait MyClass {} -// Error: b appears only in the weak position; Box(a) does not determine b. -forall a b . instance Box(a):MyClass(b) {} +// Error: B appears only in the weak position; Box does not determine B. +impl MyClass, B> {} ``` ``` Coverage condition fails for class: MyClass - the type: -Box(a) +Box does not determine: -b +B ``` **Accepted example** -Replacing the unconstrained variable `b` with a concrete type eliminates the +Replacing the unconstrained variable `B` with a concrete type eliminates the violation: ```solidity -forall a . instance Box(a):MyClass(word) {} +impl MyClass, word> {} ``` ### Patterson Condition @@ -250,13 +271,13 @@ same type class is used in both the context and the head. **Rejected example** ```solidity -forall a . class a:C1 {} -forall a . class a:C2 {} +trait C1 {} +trait C2 {} // Context: U:C1 has measure 2, U:C2 has measure 2, total 4. // Head: U:C1 has measure 2. // Context measure (4) is not strictly smaller than head measure (2). -forall U . U:C1, U:C2 => instance U:C1 {} +impl C1 where U: C1, U: C2 {} ``` ``` @@ -271,12 +292,14 @@ Wrapping the main type in a constructor increases the head measure so that each context constraint is strictly smaller: ```solidity -data Wrap(a) = Wrap(a); +enum Wrap { + Wrap(A) +} // Context: U:C1 has measure 2. -// Head: Wrap(U):C1 has measure 3 (Wrap + U + C1 name). +// Head: Wrap: C1 has measure 3 (Wrap + U + C1 name). // 2 < 3, so the Patterson condition holds. -forall U . U:C1 => instance Wrap(U):C1 {} +impl C1> where U: C1 {} ``` ### Bound Variable Condition @@ -288,13 +311,15 @@ from the types at the call site, making instance resolution ambiguous. **Rejected example** ```solidity -data Box(a) = Box(word); -forall a . class a:Eq {} -forall a b . class a:Container(b) {} +enum Box { + Box(word) +} +trait Eq {} +trait Container {} -// Error: c appears in the context constraint c:Eq -// but not in the instance head Box(a):Container(a). -forall a c . c:Eq => instance Box(a):Container(a) {} +// Error: C appears in the context constraint C: Eq +// but not in the impl head Container, A>. +impl Container, A> where C: Eq {} ``` ``` @@ -307,10 +332,10 @@ Remove the unused variable from the context, or include it in the head: ```solidity // No context needed. -forall a . instance Box(a):Container(a) {} +impl Container, A> {} -// Or: bring c into the head through the weak argument. -forall a c . c:Eq => instance Box(a):Container(c) {} +// Or: bring C into the head through the weak argument. +impl Container, C> where C: Eq {} ``` --- @@ -325,18 +350,18 @@ There are three pragmas, one per condition: | Pragma keyword | Condition disabled | | ------------------------------- | ------------------------ | -| `no-coverage-condition` | Coverage condition | -| `no-patterson-condition` | Patterson condition | -| `no-bounded-variable-condition` | Bound variable condition | +| `pragma solcore noCoverageCondition` | Coverage condition | +| `pragma solcore noPattersonCondition` | Patterson condition | +| `pragma solcore noBoundVariableCondition` | Bound variable condition | Each pragma has two forms: ```solidity // Disable for a specific list of classes (comma-separated). -pragma no-coverage-condition ClassName1, ClassName2; +pragma solcore noCoverageCondition ClassName1, ClassName2; // Disable globally for all classes in this file. -pragma no-coverage-condition; +pragma solcore noCoverageCondition; ``` Pragmas apply only to the file in which they appear. Importing a file does not @@ -348,7 +373,7 @@ declarations. > pragmas only when you understand the implications for the specific class and > instance involved. -### `pragma no-coverage-condition` +### `pragma solcore noCoverageCondition` Disables the coverage check for the listed classes. Use this when a weak type argument is deliberately left undetermined by the main type, for example in open @@ -356,13 +381,15 @@ type-indexed families where the relationship is established by context rather than by the instance itself. ```solidity -pragma no-coverage-condition MyClass; +pragma solcore noCoverageCondition MyClass; -data Box(a) = Box(word); -forall a b . class a:MyClass(b) {} +enum Box { + Box(word) +} +trait MyClass {} // Accepted: coverage condition is disabled for MyClass. -forall a b . instance Box(a):MyClass(b) {} +impl MyClass, B> {} ``` Without the pragma, this declaration would produce: @@ -371,25 +398,25 @@ Without the pragma, this declaration would produce: Coverage condition fails for class: MyClass - the type: -Box(a) +Box does not determine: -b +B ``` -### `pragma no-patterson-condition` +### `pragma solcore noPattersonCondition` Disables the Patterson measure check for the listed classes. Use this for class hierarchies where the instance search is known to terminate through structural arguments not captured by the simple measure metric. ```solidity -pragma no-patterson-condition C1; +pragma solcore noPattersonCondition C1; -forall a . class a:C1 {} -forall a . class a:C2 {} +trait C1 {} +trait C2 {} // Accepted: Patterson condition is disabled for C1. -forall U . U:C1, U:C2 => instance U:C1 {} +impl C1 where U: C1, U: C2 {} ``` Without the pragma, this declaration would produce: @@ -400,21 +427,23 @@ U : C1 does not satisfy the Patterson conditions. ``` -### `pragma no-bounded-variable-condition` +### `pragma solcore noBoundVariableCondition` Disables the bound variable check for the listed classes. Use this when a context variable is intentionally existential, meaning it is chosen by the instance rather than derived from the call site. ```solidity -pragma no-bounded-variable-condition Container; +pragma solcore noBoundVariableCondition Container; -data Box(a) = Box(word); -forall a . class a:Eq {} -forall a b . class a:Container(b) {} +enum Box { + Box(word) +} +trait Eq {} +trait Container {} // Accepted: bound variable condition is disabled for Container. -forall a c . c:Eq => instance Box(a):Container(a) {} +impl Container, A> where C: Eq {} ``` Without the pragma, this declaration would produce: @@ -429,15 +458,17 @@ Multiple pragmas may appear in the same file and may target the same class from different directives. All specified conditions are disabled independently: ```solidity -pragma no-coverage-condition MyClass; -pragma no-patterson-condition MyClass; -pragma no-bounded-variable-condition MyClass; +pragma solcore noCoverageCondition MyClass; +pragma solcore noPattersonCondition MyClass; +pragma solcore noBoundVariableCondition MyClass; -data Box(a) = Box(word); -forall a . class a:Eq {} -forall a . class a:C1 {} -forall a b . class a:MyClass(b) {} +enum Box { + Box(word) +} +trait Eq {} +trait C1 {} +trait MyClass {} // Accepted: all three conditions are disabled for MyClass. -forall a b c . c:Eq, (a, b):C1 => instance Box(a):MyClass(b) {} +impl MyClass, B> where C: Eq, (A, B): C1 {} ``` diff --git a/doc/src/sail/variable-declaration-and-assignment.md b/doc/src/sail/variable-declaration-and-assignment.md index 7bdb7b446..5f749d1e5 100644 --- a/doc/src/sail/variable-declaration-and-assignment.md +++ b/doc/src/sail/variable-declaration-and-assignment.md @@ -25,15 +25,15 @@ does not insert a default value. ### Declaration with a type annotation ```solcore -let bal : word; +let bal: word; ``` The type is fixed to `word` at the point of declaration. The variable is still uninitialized; it must be assigned before use. ```solcore -function loadBalance(account : word) -> word { - let bal : word; +function loadBalance(account: word) returns (word) { + let bal: word; assembly { bal := sload(account) } return bal; } @@ -46,18 +46,21 @@ omitted and will be inferred from the initialiser expression. ```solcore let amount = 100; // type inferred as word -let fee : word = 3; // type annotation and initialiser together +let fee: word = 3; // type annotation and initialiser together ``` Initialised declarations are useful when the right-hand side is an expression whose type would otherwise be ambiguous: ```solcore -data Result = Ok(word) | Err(word); +enum Result { + Ok(word), + Err(word) +} -function safeTransfer(from : word, to : word, amount : word) -> Result { +function safeTransfer(from: word, to: word, amount: word) returns (Result) { let result = Result.Err(0); // type inferred as Result from constructor - let bal : word; + let bal: word; assembly { bal := sload(from) } if (gte(bal, amount)) { result = Result.Ok(amount); @@ -84,10 +87,10 @@ The type of `expr` must match the declared type of `x`. ```solcore contract Vault { - balance : word; + balance: word; - function deposit(amount : word) -> () { - let next : word; + function deposit(amount: word) { + let next: word; next = balance; balance = add(next, amount); } @@ -108,10 +111,10 @@ Compound assignment is most commonly used with contract fields: ```solcore contract ERC20 { - totalSupply : word; - feePool : word; + totalSupply: word; + feePool: word; - function mint(amount : word) -> () { + function mint(amount: word) { totalSupply += amount; feePool += div(amount, 100); } @@ -128,9 +131,9 @@ and retain their values between calls. ```solcore contract Token { - owner : word; - supply : word; - paused : bool; + owner: word; + supply: word; + paused: bool; } ``` @@ -139,13 +142,13 @@ contract. A field cannot be accessed from a free function. ```solcore contract Token { - supply : word; + supply: word; - function mint(amount : word) -> () { + function mint(amount: word) { supply += amount; } - function totalSupply() -> word { + function totalSupply() returns (word) { return supply; } } @@ -158,7 +161,7 @@ evaluated once when the contract is deployed. ```solcore contract Token { - supply : word = 0; + supply: word = 0; } ``` @@ -172,10 +175,13 @@ left-hand side, the contextual constructor shorthand `.Constructor` can be used on the right-hand side. ```solcore -data Result = Ok(word) | Err(word); +enum Result { + Ok(word), + Err(word) +} -function main() -> Result { - let r : Result; +function main() returns (Result) { + let r: Result; r = .Ok(0); // equivalent to Result.Ok(0) return r; } @@ -200,15 +206,14 @@ if (condition) { } ``` -Both branches must produce the same type if the `if` statement appears in a -context where a value is expected. When used purely for side effects the -types need only be consistent: +`if` is a statement, so use the conditional expression `condition ? yes : no` +when a value is required: ```solcore contract Token { - paused : bool; + paused: bool; - function transfer(to : word, amount : word) -> () { + function transfer(to: word, amount: word) { if (paused) { assembly { revert(0, 0) } } @@ -242,13 +247,13 @@ The init clause runs once before the first iteration. It may: ```solcore for (let i = 0; i < 10; i = i + 1) { ... } - for (let i : word; i < 10; i = i + 1) { ... } + for (let i: word; i < 10; i = i + 1) { ... } ``` * Assign to an already-declared variable: ```solcore - let i : word; + let i: word; for (i = 0; i < 10; i = i + 1) { ... } ``` @@ -256,14 +261,16 @@ The init clause runs once before the first iteration. It may: ### Post-iteration clause -The post clause runs after each iteration, before the condition is re-tested. It -follows the same grammar as the init clause. A `let` binding introduced here -creates a fresh variable scoped to the body of **that iteration only**: +The post clause runs after each iteration, before the condition is re-tested. +It accepts assignments, compound assignments, and expression statements, but +does not introduce new bindings: ```solcore -for (i = 0; i <= 0; let j = 1) { - s = j; // j is in scope here and rebound on every iteration - i = i + 1; +let i: word; +let s = 0; +let j = 1; +for (i = 0; i <= 0; i = i + 1) { + s = j; } ``` @@ -272,10 +279,10 @@ for (i = 0; i <= 0; let j = 1) { **Accumulate a sum from 1 to 10:** ```solcore -import std.{Num, Add, Sub, Eq, Ord, Bounded, Typedef, le}; +import {Num, Add, Sub, Eq, Ord, Bounded, Typedef, le} from std; contract Sum { - function main() -> word { + function main() returns (word) { let s = 0; for (let i = 1; i <= 10; i = i + 1) { s = s + i; } return s; // 55 @@ -287,8 +294,8 @@ contract Sum { ```solcore contract Sum { - function main() -> word { - let i : word; + function main() returns (word) { + let i: word; let s = 0; for (i = 1; i <= 10; i = i + 1) { s = s + i; } return s; @@ -300,7 +307,7 @@ contract Sum { ```solcore contract Shadow { - function main() -> word { + function main() returns (word) { let i = 100; let s = 0; for (let i = 1; i <= 10; i = i + 1) { s = s + i; } @@ -314,7 +321,7 @@ contract Shadow { ```solcore contract ForInner { - function main() -> word { + function main() returns (word) { let result = 0; for (let height = 0; height < 7; height = height + 1) { if (true) { result = height; } else {} @@ -329,9 +336,6 @@ contract ForInner { A variable declared in the **init clause** is in scope for the condition expression, the post-iteration clause, and the entire body. -A variable declared in the **post clause** is in scope only for the body of the -current iteration — it is re-bound at the start of each subsequent one. - The loop body is its own block; declarations inside it do not escape to the enclosing function. @@ -348,14 +352,14 @@ its side effects and the result is discarded. This is the standard way to call a function whose return type is `()`. ```solcore -function emitTransfer(from : word, to : word, amount : word) -> () { +function emitTransfer(from: word, to: word, amount: word) { assembly { mstore(0x00, amount) log3(0x00, 0x20, 0xddf252ad, from, to) } } -function main(to : word, amount : word) -> () { +function main(to: word, amount: word) { emitTransfer(caller(), to, amount); // expression statement: result () is discarded } ``` @@ -369,10 +373,10 @@ enclosing block. A variable declared in an inner block shadows an outer declaration of the same name for the duration of that block. ```solcore -function computeFee(amount : word) -> word { - let fee : word = 1; +function computeFee(amount: word) returns (word) { + let fee: word = 1; { - let fee : word = div(amount, 100); // shadows outer fee inside this block + let fee: word = div(amount, 100); // shadows outer fee inside this block } return fee; // refers to the outer fee; returns 1 } From 6d9076d961d8bcb124798222b73939a474c34852 Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Thu, 23 Jul 2026 23:07:18 +0900 Subject: [PATCH 04/33] Validate Yul control-flow syntax --- src/Language/Hull/TypeCheck.hs | 5 ++- src/Language/Yul.hs | 37 +++++++++++++++++++ src/Language/Yul/Parser.hs | 16 +++++--- test/HullCases.hs | 3 +- .../hull/12-err-asm-break-outside-loop.hull | 7 ++++ 5 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 test/examples/hull/12-err-asm-break-outside-loop.hull diff --git a/src/Language/Hull/TypeCheck.hs b/src/Language/Hull/TypeCheck.hs index 8b61eb2a0..070fc2437 100644 --- a/src/Language/Hull/TypeCheck.hs +++ b/src/Language/Hull/TypeCheck.hs @@ -74,7 +74,10 @@ checkStmt (SBlock stmts) = withLocalEnv (checkBody stmts) checkStmt (SExpr e) = checkExpr e >> pure () -checkStmt (SAssembly stmts) = +checkStmt (SAssembly stmts) = do + case validateYulControlFlow stmts of + Left err -> hullError err + Right () -> pure () withLocalEnv (checkAsmBlock stmts) checkStmt (SFor initStmt cond post body) = -- Variables declared in the init block are scoped over the entire for loop diff --git a/src/Language/Yul.hs b/src/Language/Yul.hs index 13583506e..ca3a40991 100644 --- a/src/Language/Yul.hs +++ b/src/Language/Yul.hs @@ -97,6 +97,43 @@ data YulExp | YMeta String deriving (Eq, Ord, Data, Typeable) +-- | Check the lexical placement rules for Yul control-transfer statements. +-- A Yul function starts a fresh loop context: @break@/@continue@ cannot target +-- a loop outside that function, and @leave@ is only meaningful in its body. +validateYulControlFlow :: YulBlock -> Either String () +validateYulControlFlow = validateBlock 0 False + where + validateBlock :: Int -> Bool -> YulBlock -> Either String () + validateBlock loopDepth inFunction = mapM_ (validateStmt loopDepth inFunction) + + validateStmt :: Int -> Bool -> YulStmt -> Either String () + validateStmt loopDepth inFunction stmt = + case stmt of + YBlock body -> + validateBlock loopDepth inFunction body + YFun _ _ _ body -> + validateBlock 0 True body + YIf _ body -> + validateBlock loopDepth inFunction body + YSwitch _ cases defaultBody -> do + mapM_ (validateBlock loopDepth inFunction . snd) cases + mapM_ (validateBlock loopDepth inFunction) defaultBody + YFor pre _ post body -> do + validateBlock loopDepth inFunction pre + validateBlock loopDepth inFunction post + validateBlock (loopDepth + 1) inFunction body + YBreak + | loopDepth == 0 -> + Left "Yul break is only valid inside a for-loop body" + YContinue + | loopDepth == 0 -> + Left "Yul continue is only valid inside a for-loop body" + YLeave + | not inFunction -> + Left "Yul leave is only valid inside a Yul function" + _ -> + Right () + data YLiteral = YulNumber Integer | YulString String diff --git a/src/Language/Yul/Parser.hs b/src/Language/Yul/Parser.hs index d47e679c0..f729af46d 100644 --- a/src/Language/Yul/Parser.hs +++ b/src/Language/Yul/Parser.hs @@ -45,8 +45,11 @@ parens = between (symbol "(") (symbol ")") commaSep :: Parser a -> Parser [a] commaSep p = p `sepBy` symbol "," +commaSep1 :: Parser a -> Parser [a] +commaSep1 p = p `sepBy1` symbol "," + pKeyword :: String -> Parser String -pKeyword w = lexeme (string w <* notFollowedBy identChar) +pKeyword w = try $ lexeme (string w <* notFollowedBy identChar) pMeta :: Parser String pMeta = @@ -80,14 +83,17 @@ yulStmt = *> choice [ YBlock <$> yulBlock, yulFun, - YLet <$> (pKeyword "let" *> commaSep pName) <*> optional (symbol ":=" *> yulExp), + YLet <$> (pKeyword "let" *> commaSep1 pName) <*> optional (symbol ":=" *> yulExp), YIf <$> (pKeyword "if" *> yulExp) <*> yulBlock, YFor <$> (pKeyword "for" *> yulBlock) <*> yulExp <*> yulBlock <*> yulBlock, YSwitch <$> (pKeyword "switch" *> yulExp) <*> many yulCase <*> optional (pKeyword "default" *> yulBlock), - try (YAssign <$> commaSep pName <*> (symbol ":=" *> yulExp)), + YContinue <$ pKeyword "continue", + YBreak <$ pKeyword "break", + YLeave <$ pKeyword "leave", + try (YAssign <$> commaSep1 pName <*> (symbol ":=" *> yulExp)), YExp <$> yulExp ] @@ -103,10 +109,10 @@ yulCase = do yulFun :: Parser YulStmt yulFun = do - _ <- symbol "function" + _ <- pKeyword "function" name <- pName args <- parens (commaSep pName) - rets <- optional (symbol "->" *> commaSep pName) + rets <- optional (symbol "->" *> commaSep1 pName) YFun name args rets <$> yulBlock yulProgram :: Parser Yul diff --git a/test/HullCases.hs b/test/HullCases.hs index 86a1cdf98..4f1216a71 100644 --- a/test/HullCases.hs +++ b/test/HullCases.hs @@ -30,7 +30,8 @@ hullTests = runHullTestExpectingFailure "08-err-arity.hull", runHullTestExpectingFailure "09-err-sum-payload.hull", runHullTestExpectingFailure "10-err-fst-non-pair.hull", - runHullTestExpectingFailure "11-err-asm-sum-return.hull" + runHullTestExpectingFailure "11-err-asm-sum-return.hull", + runHullTestExpectingFailure "12-err-asm-break-outside-loop.hull" ] ] diff --git a/test/examples/hull/12-err-asm-break-outside-loop.hull b/test/examples/hull/12-err-asm-break-outside-loop.hull new file mode 100644 index 000000000..fb1eabc46 --- /dev/null +++ b/test/examples/hull/12-err-asm-break-outside-loop.hull @@ -0,0 +1,7 @@ +object InvalidAsmControl { + code { + assembly { + break + } + } +} From 05b47bb4a9a6a57ee680f400e728f52c7fd8c596 Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Thu, 23 Jul 2026 23:07:49 +0900 Subject: [PATCH 05/33] Preserve new syntax semantics through the frontend --- concept-art/has-field.sol | 14 +- scripts/gen-std-opcodes.py | 2 +- src/Solcore/Frontend/ComptimeCheck.hs | 99 +- src/Solcore/Frontend/Lexer/SolcoreLexer.hs | 7 +- src/Solcore/Frontend/Module/Loader.hs | 36 +- src/Solcore/Frontend/Parser/Decl.hs | 25 +- src/Solcore/Frontend/Parser/Expr.hs | 22 +- src/Solcore/Frontend/Parser/Patterns.hs | 13 +- src/Solcore/Frontend/Parser/SolcoreTypes.hs | 15 +- src/Solcore/Frontend/Parser/Stmt.hs | 24 +- src/Solcore/Frontend/Pretty/ShortName.hs | 2 +- src/Solcore/Frontend/Pretty/SolcorePretty.hs | 124 ++- src/Solcore/Frontend/Pretty/TreePretty.hs | 47 +- src/Solcore/Frontend/Syntax/Contract.hs | 175 ++- src/Solcore/Frontend/Syntax/NameResolution.hs | 755 ++++++++++--- src/Solcore/Frontend/Syntax/SyntaxTree.hs | 38 +- src/Solcore/Frontend/TypeInference/Erase.hs | 8 +- .../Frontend/TypeInference/SccAnalysis.hs | 8 +- .../Frontend/TypeInference/TcContract.hs | 69 +- src/Solcore/Frontend/TypeInference/TcEnv.hs | 15 + .../Frontend/TypeInference/TcModule.hs | 140 ++- src/Solcore/Frontend/TypeInference/TcMonad.hs | 196 +++- src/Solcore/Frontend/TypeInference/TcStmt.hs | 77 +- src/Solcore/Frontend/TypeInference/TcSubst.hs | 28 +- src/Solcore/Primitives/Primitives.solc | 4 +- std/opcodes.solc | 4 +- std/std.solc | 2 +- test/DiagnosticCliTests.hs | 6 +- test/LocationTests.hs | 2 +- test/ModuleTypeCheckTests.hs | 994 +++++++++++++++++- test/ParserTests.hs | 906 ++++++++++++++-- test/diagnostics/missing-signature.solc | 2 +- test/examples/cases/bare-revert.solc | 3 + .../examples/cases/fresh-pat-arg-synonym.solc | 2 +- test/examples/cases/instance-synonym-int.solc | 2 +- test/examples/cases/instance-synonym.solc | 2 +- .../cases/overlap-synonym-detected.solc | 2 +- .../cases/overlap-synonym-missed-order.solc | 2 +- .../overlap-synonym-missed-two-synonyms.solc | 4 +- .../cases/synonym-arity-mismatch.solc | 2 +- test/examples/cases/synonym-basic.solc | 6 +- test/examples/cases/synonym-in-function.solc | 6 +- test/examples/cases/synonym-long-cycle.solc | 8 +- test/examples/cases/synonym-nested.solc | 12 +- test/examples/cases/synonym-param.solc | 4 +- test/examples/cases/synonym-recursive.solc | 6 +- .../cases/synonym-self-recursive.solc | 4 +- test/examples/cases/type-synonym-arg.solc | 2 +- test/examples/comptime/ct_named_return.solc | 10 + test/examples/comptime/fromInt.solc | 2 +- test/imports/bare_revert_import_main.solc | 6 + test/imports/bare_revert_lib.solc | 6 + 52 files changed, 3434 insertions(+), 516 deletions(-) create mode 100644 test/examples/cases/bare-revert.solc create mode 100644 test/examples/comptime/ct_named_return.solc create mode 100644 test/imports/bare_revert_import_main.solc create mode 100644 test/imports/bare_revert_lib.solc diff --git a/concept-art/has-field.sol b/concept-art/has-field.sol index 12a7af263..f24da971c 100644 --- a/concept-art/has-field.sol +++ b/concept-art/has-field.sol @@ -1,9 +1,9 @@ enum Unit { Unit } enum Pair { Pair(a, b) } -type uint is word; -type string is word; -type bool is word; +alias uint = word; +alias string = word; +alias bool = word; enum Memory { Memory(Word) } @@ -19,12 +19,12 @@ trait Field {} //} // a type abstraction over tuples -type s is Pair>; +alias s = Pair>; // unique types identifying each field -type sf1 is Unit; -type sf2 is Unit; -type sf3 is Unit; +alias sf1 = Unit; +alias sf2 = Unit; +alias sf3 = Unit; // Field instances linking each field to it's position in the underlying tuple impl Field, Unit, uint> {} diff --git a/scripts/gen-std-opcodes.py b/scripts/gen-std-opcodes.py index 91e7c2a40..cac2e32f5 100755 --- a/scripts/gen-std-opcodes.py +++ b/scripts/gen-std-opcodes.py @@ -100,7 +100,7 @@ # Opcodes whose names clash with solcore keywords get a trailing underscore # in the wrapper function name, while the inner assembly call still uses the # real EVM mnemonic. -RESERVED_NAMES = {"return": "return_"} +RESERVED_NAMES = {"return": "return_", "revert": "revert_"} def wrapper_name(op): diff --git a/src/Solcore/Frontend/ComptimeCheck.hs b/src/Solcore/Frontend/ComptimeCheck.hs index f1b91262e..834793d05 100644 --- a/src/Solcore/Frontend/ComptimeCheck.hs +++ b/src/Solcore/Frontend/ComptimeCheck.hs @@ -55,10 +55,20 @@ buildSigTable (CompUnit _ topDecls) = Map.fromList $ concatMap fromTopDecl topDe fromContrDecl _ = [] ----------------------------------------------------------------------- --- Comptime environment: variable name -> Ctness +-- Comptime environment: variable name -> current classification plus whether +-- the binding was explicitly declared comptime. Keeping the declaration bit +-- separate matters for mutable locals: a runtime assignment updates an +-- ordinary binding, but must be rejected for a comptime binding. ----------------------------------------------------------------------- -type CtEnv = Map.Map Name Ctness +data CtBinding + = CtBinding + { bindingCtness :: Ctness, + bindingRequiresComptime :: Bool + } + deriving (Eq, Show) + +type CtEnv = Map.Map Name CtBinding ----------------------------------------------------------------------- -- Entry point @@ -102,7 +112,11 @@ checkFunDef st ctx fd = checkBody st (sigRetComptime sig) ctx initEnv (funDefBod -- For other functions, non-comptime params are CTRuntime. initEnv = Map.fromList - [ (idName (paramName p), if paramComptime p || sigRetComptime sig then CTComptime else CTRuntime) + [ ( idName (paramName p), + CtBinding + (if paramComptime p || sigRetComptime sig then CTComptime else CTRuntime) + (paramComptime p) + ) | p <- sigParams sig ] @@ -134,7 +148,12 @@ checkStmt :: SigTable -> Bool -> String -> CtEnv -> Stmt Id -> Either String CtE checkStmt st retCt ctx env stmt = case stmt of Let ct x _ mInit -> do case mInit of - Nothing -> return env + Nothing -> + return $ + Map.insert + (idName x) + (CtBinding (if ct then CTComptime else CTDeferred) ct) + env Just e -> do checkExp st env e let ct' = classifyExp st env e @@ -142,14 +161,30 @@ checkStmt st retCt ctx env stmt = case stmt of "comptime let '" ++ show (idName x) ++ "' is bound to a runtime expression" - return $ Map.insert (idName x) (letCtness ct ct') env + return $ + Map.insert + (idName x) + (CtBinding (letCtness ct ct') ct) + env LetPattern ct pat _ value -> do checkExp st env value let valueCtness = classifyExp st env value when_ (ct && valueCtness == CTRuntime) $ "comptime tuple binding is bound to a runtime expression" - return (bindPatternCtness (letCtness ct valueCtness) pat env) - (_ := e) -> checkExp st env e >> return env + return (bindPatternCtness ct (letCtness ct valueCtness) pat env) + (lhs := rhs) -> do + checkExp st env rhs + let rhsCtness = classifyExp st env rhs + case assignedVariable lhs >>= \variable -> Map.lookup (idName variable) env of + Just binding -> + when_ + (bindingRequiresComptime binding && rhsCtness == CTRuntime) + ( "comptime variable '" + ++ maybe "" (show . idName) (assignedVariable lhs) + ++ "' is assigned a runtime expression" + ) + Nothing -> Right () + return (updateAssignedVariable env lhs rhsCtness) StmtExp e -> checkExp st env e >> return env Return e -> do checkExp st env e @@ -184,25 +219,50 @@ letCtness :: Bool -> Ctness -> Ctness letCtness True _ = CTComptime letCtness False ct' = ct' +assignedVariable :: Exp Id -> Maybe Id +assignedVariable (Var variable) = Just variable +assignedVariable (TyExp expression _) = assignedVariable expression +assignedVariable _ = Nothing + +updateAssignedVariable :: CtEnv -> Exp Id -> Ctness -> CtEnv +updateAssignedVariable env lhs rhsCtness = + case assignedVariable lhs of + Nothing -> env + Just variable -> + Map.adjust + ( \binding -> + binding + { bindingCtness = + letCtness + (bindingRequiresComptime binding) + rhsCtness + } + ) + (idName variable) + env + checkEq :: SigTable -> Bool -> String -> CtEnv -> [Ctness] -> ([Pat Id], Body Id) -> Either String () checkEq st retCt ctx env scrutineeCtness (pats, body) = checkBody st retCt ctx patternEnv body where patternEnv = foldl - (\current (pat, ctness) -> bindPatternCtness ctness pat current) + (\current (pat, ctness) -> bindPatternCtness False ctness pat current) env (zip pats scrutineeCtness) -bindPatternCtness :: Ctness -> Pat Id -> CtEnv -> CtEnv -bindPatternCtness ctness (PVar variable) env = - Map.insert (idName variable) ctness env -bindPatternCtness ctness (PCon _ pats) env = +bindPatternCtness :: Bool -> Ctness -> Pat Id -> CtEnv -> CtEnv +bindPatternCtness requiresComptime ctness (PVar variable) env = + Map.insert + (idName variable) + (CtBinding ctness requiresComptime) + env +bindPatternCtness requiresComptime ctness (PCon _ pats) env = foldl - (\current pat -> bindPatternCtness ctness pat current) + (\current pat -> bindPatternCtness requiresComptime ctness pat current) env pats -bindPatternCtness _ _ env = +bindPatternCtness _ _ _ env = env ----------------------------------------------------------------------- @@ -220,7 +280,13 @@ checkExp st env (Lam ps body _) = checkBody st False "lambda" lamEnv body where lamEnv = Map.fromList - [(idName (paramName p), if paramComptime p then CTComptime else CTRuntime) | p <- ps] + [ ( idName (paramName p), + CtBinding + (if paramComptime p then CTComptime else CTRuntime) + (paramComptime p) + ) + | p <- ps + ] `Map.union` env checkExp _ _ _ = Right () @@ -260,7 +326,8 @@ hasTypeVar (TyCon _ ts) = any hasTypeVar ts classifyExp :: SigTable -> CtEnv -> Exp Id -> Ctness classifyExp _ _ (Lit _) = CTComptime -classifyExp _ env (Var x) = Map.findWithDefault CTDeferred (idName x) env +classifyExp _ env (Var x) = + maybe CTDeferred bindingCtness (Map.lookup (idName x) env) classifyExp st env (TyExp e _) = classifyExp st env e classifyExp st env (Call _ f args) = classifyCall st env f args classifyExp st env (Con _ args) = combineCt (map (classifyExp st env) args) diff --git a/src/Solcore/Frontend/Lexer/SolcoreLexer.hs b/src/Solcore/Frontend/Lexer/SolcoreLexer.hs index 9eea81c41..a9efd181b 100644 --- a/src/Solcore/Frontend/Lexer/SolcoreLexer.hs +++ b/src/Solcore/Frontend/Lexer/SolcoreLexer.hs @@ -80,7 +80,11 @@ reservedWords = "view", "constructor", "return", + "revert", + "true", + "false", "lam", + "alias", "type", "pragma", "solcore", @@ -92,7 +96,7 @@ identifier :: Parser String identifier = lexeme go "identifier" where go = do - h <- letterChar + h <- letterChar <|> char '_' t <- many identChar let w = h : t if w `elem` reservedWords @@ -115,6 +119,7 @@ stringLit = choice [ char 'n' *> pure '\n', char 't' *> pure '\t', + char 'r' *> pure '\r', char '"' *> pure '"', char '\\' *> pure '\\' ] diff --git a/src/Solcore/Frontend/Module/Loader.hs b/src/Solcore/Frontend/Module/Loader.hs index 685cf34c0..3742dab22 100644 --- a/src/Solcore/Frontend/Module/Loader.hs +++ b/src/Solcore/Frontend/Module/Loader.hs @@ -7,6 +7,7 @@ module Solcore.Frontend.Module.Loader moduleValidationTopDeclSegments, moduleSourcePath, moduleLocalTypeCheckSurface, + selectedImportBindingsForModule, ) where @@ -621,6 +622,17 @@ selectedImportBindingsFromAvailable available (SelectItems items hidden) = expand (SelectItem itemName) = [(itemName, itemName)] expand (SelectItemAs itemName aliasName) = [(itemName, aliasName)] +selectedImportBindingsForModule :: + ModuleGraph -> + Mod.ModuleId -> + ItemSelector -> + Either String [(Name, Name)] +selectedImportBindingsForModule graph modulePath selector = do + publicDecls <- publicTopDeclsForModule graph modulePath + selectedImportBindingsFromAvailable + (uniqueNames (concatMap topDeclNames publicDecls)) + selector + uniqueBindingsByLocal :: [(Name, Name)] -> [(Name, Name)] uniqueBindingsByLocal = reverse . fst . foldl step ([], Map.empty) @@ -1445,6 +1457,7 @@ renameStmtTypeRefs renameMap (StmtExp e) = StmtExp (renameExpTypeRefs renameMap e) renameStmtTypeRefs renameMap (Return e) = Return (renameExpTypeRefs renameMap e) +renameStmtTypeRefs _ BareReturn = BareReturn renameStmtTypeRefs renameMap (Match es eqns) = Match (map (renameExpTypeRefs renameMap) es) @@ -1471,6 +1484,7 @@ renameStmtTypeRefs renameMap (For initStmt cond postStmt body) = (renameBodyTypeRefs renameMap body) renameStmtTypeRefs _ Break = Break renameStmtTypeRefs _ Continue = Continue +renameStmtTypeRefs _ Revert = Revert renameStmtTypeRefs _ EmptyStmt = EmptyStmt renameEquationTypeRefs :: Map Name Name -> Equation -> Equation @@ -1507,6 +1521,10 @@ renameExpTypeRefs renameMap (ExpName me n es) = (renameMemberQualifierTypeRefs renameMap <$> me) n (map (renameExpTypeRefs renameMap) es) +renameExpTypeRefs renameMap (ExpApply callee args) = + ExpApply + (renameExpTypeRefs renameMap callee) + (map (renameExpTypeRefs renameMap) args) renameExpTypeRefs renameMap (ExpVar Nothing n) = ExpVar (sameNameConstructorQualifier renameMap n) @@ -1598,8 +1616,22 @@ renameContractTypeRefs renameMap (ContractShell kind n ts ds) = ContractShell kind n - (map (renameTyTypeRefs renameMap) ts) - (map (renameContractDeclTypeRefs renameMap) ds) + (map (renameTyTypeRefs scopedRenameMap) ts) + (map (renameContractDeclTypeRefs scopedRenameMap) ds) + where + -- A shell-local type shadows a same-spelled imported or top-level type + -- throughout the whole shell, including declarations that precede it. + scopedRenameMap = + foldr + Map.delete + renameMap + ( [ typeParamName + | TyCon typeParamName _ <- ts + ] + ++ [ dataName dataTy + | CDataDecl dataTy <- ds + ] + ) renameContractDeclTypeRefs :: Map Name Name -> ContractDecl -> ContractDecl renameContractDeclTypeRefs renameMap (CDataDecl d) = diff --git a/src/Solcore/Frontend/Parser/Decl.hs b/src/Solcore/Frontend/Parser/Decl.hs index 6f2f0f96b..f4c5c7661 100644 --- a/src/Solcore/Frontend/Parser/Decl.hs +++ b/src/Solcore/Frontend/Parser/Decl.hs @@ -14,8 +14,8 @@ import Solcore.Frontend.Parser.SolcoreTypes ( paramP, qualifiedName, simpleNameP, - typeParamsP, typeP, + typeParamsP, whereClauseP, ) import Solcore.Frontend.Parser.Stmt (bodyP) @@ -47,7 +47,7 @@ importP = do _ <- semicolon pure (ImportAlias path aliasName), try $ do - entries <- braces (itemEntryP `sepBy` comma) + entries <- braces (itemEntryP `sepBy1` comma) keyword "from" path <- importPathP hiddenNames <- option [] hidingP @@ -58,7 +58,7 @@ importP = do ImportModule path <$ semicolon ] where - hidingP = keyword "hiding" *> braces (simpleNameP `sepBy` comma) + hidingP = keyword "hiding" *> braces (simpleNameP `sepBy1` comma) importPathP :: Parser ModulePath importPathP = try externalPathP <|> modulePathP @@ -244,14 +244,22 @@ constrP = do tySymP :: Parser TySym tySymP = do - keyword "type" + keyword "alias" n <- simpleNameP params <- typeParamsP - keyword "is" + equalsP t <- typeP _ <- semicolon return (TySym n params t) +unsupportedUserDefinedValueTypeP :: Parser TopDecl +unsupportedUserDefinedValueTypeP = do + keyword "type" + fail + ( "user-defined value types declared with `type ... is ...` are not yet " + ++ "implemented; use `alias Name = Type;` only for transparent type synonyms" + ) + functionModifierP :: Parser FunctionModifier functionModifierP = choice @@ -414,6 +422,12 @@ contractDeclP = interfaceDeclP :: Parser ContractDecl interfaceDeclP = do (isPublic, sig) <- signatureP True + let visibility = + [ modifierVisibility + | VisibilityModifier modifierVisibility <- sigModifiers sig + ] + when (visibility /= [VisibilityExternal]) $ + fail "interface functions must declare exactly one `external` visibility modifier" _ <- semicolon "';' after interface function signature" pure (CSignatureDecl isPublic sig) @@ -453,6 +467,7 @@ topDeclP = TDataDef <$> structP, TDataDef <$> enumP, TSym <$> tySymP, + unsupportedUserDefinedValueTypeP, TContr <$> (contractP <|> interfaceP <|> libraryP), contractOnlyDeclP, TFunDef <$> try funDefP, diff --git a/src/Solcore/Frontend/Parser/Expr.hs b/src/Solcore/Frontend/Parser/Expr.hs index eb0fc1b53..9c1877cb4 100644 --- a/src/Solcore/Frontend/Parser/Expr.hs +++ b/src/Solcore/Frontend/Parser/Expr.hs @@ -7,7 +7,7 @@ import Common.LightYear import Control.Monad.Combinators.Expr import Solcore.Diagnostics (SourceSpan) import Solcore.Frontend.Lexer.SolcoreLexer -import Solcore.Frontend.Parser.SolcoreTypes (locatedFromSpans, locatedP, paramP, simpleNameP, typeP) +import Solcore.Frontend.Parser.SolcoreTypes (booleanNameP, locatedFromSpans, locatedP, paramP, simpleNameP, typeP) import Solcore.Frontend.Syntax.Location (sourceSpanOf) import Solcore.Frontend.Syntax.Name import Solcore.Frontend.Syntax.SyntaxTree @@ -112,7 +112,7 @@ postfixP bp = do return (foldl (\acc f -> f acc) e0 ops) postfixOp :: BodyP -> Parser (Exp -> Exp) -postfixOp bp = dotOp bp <|> idxOp bp +postfixOp bp = dotOp bp <|> idxOp bp <|> callOp bp dotOp :: BodyP -> Parser (Exp -> Exp) dotOp bp = do @@ -129,13 +129,27 @@ idxOp bp = do idx <- brackets (exprP bp) return (\e -> locatedExpFrom [sourceSpanOf e, sourceSpanOf idx] (ExpIndexed e idx)) +callOp :: BodyP -> Parser (Exp -> Exp) +callOp bp = do + args <- parens (exprP bp `sepBy` comma) + pure $ \callee -> + locatedExpFrom [sourceSpanOf callee, sourceSpanOf args] $ + case callee of + -- Keep the established source shape for direct and member calls, + -- including redundant parentheses such as `(f)(x)`. + ExpVar receiver memberName -> ExpName receiver memberName args + _ -> ExpApply callee args + atomP :: BodyP -> Parser Exp atomP bp = litP <|> try (lamP bp) <|> try (dotNameP bp) <|> parenP bp <|> nameP bp litP :: Parser Exp litP = locatedP locatedExp $ - Lit . IntLit + ExpVar Nothing + <$> booleanNameP + <|> Lit + . IntLit <$> integer <|> Lit . StrLit @@ -159,7 +173,7 @@ dotNameP :: BodyP -> Parser Exp dotNameP bp = locatedP locatedExp $ do _ <- char '.' sc - n <- simpleNameP + n <- booleanNameP <|> simpleNameP args <- option [] (parens (exprP bp `sepBy` comma)) return (ExpDotName n args) diff --git a/src/Solcore/Frontend/Parser/Patterns.hs b/src/Solcore/Frontend/Parser/Patterns.hs index 2200a091f..a597c8723 100644 --- a/src/Solcore/Frontend/Parser/Patterns.hs +++ b/src/Solcore/Frontend/Parser/Patterns.hs @@ -10,7 +10,7 @@ import Control.Monad (when) import Data.Set qualified as Set import Solcore.Frontend.Lexer.SolcoreLexer import Solcore.Frontend.Parser.Expr (exprP) -import Solcore.Frontend.Parser.SolcoreTypes (locatedP, qualifiedName, simpleNameP) +import Solcore.Frontend.Parser.SolcoreTypes (booleanNameP, locatedP, qualifiedName, simpleNameP) import Solcore.Frontend.Syntax.Name import Solcore.Frontend.Syntax.SyntaxTree @@ -55,11 +55,16 @@ bindingNameP = do wildcardP :: Parser Pat wildcardP = - PWildcard <$ lexeme (string "_" <* notFollowedBy (alphaNumChar <|> char '_')) + PWildcard + <$ lexeme + (try (string "_" <* notFollowedBy (alphaNumChar <|> char '_'))) litP :: Parser Pat litP = - PLit . IntLit + (\n -> Pat n []) + <$> booleanNameP + <|> PLit + . IntLit <$> integer <|> PLit . StrLit @@ -69,7 +74,7 @@ dotPatP :: Parser Pat dotPatP = do _ <- char '.' sc - n <- simpleNameP + n <- booleanNameP <|> simpleNameP args <- option [] (parens (patP `sepBy1` comma)) return (PatDot n args) diff --git a/src/Solcore/Frontend/Parser/SolcoreTypes.hs b/src/Solcore/Frontend/Parser/SolcoreTypes.hs index 42c291271..95bcbebe1 100644 --- a/src/Solcore/Frontend/Parser/SolcoreTypes.hs +++ b/src/Solcore/Frontend/Parser/SolcoreTypes.hs @@ -8,6 +8,7 @@ module Solcore.Frontend.Parser.SolcoreTypes typeParamsP, whereClauseP, simpleNameP, + booleanNameP, locatedP, locatedFromSpans, ) @@ -33,6 +34,14 @@ simpleNameP :: Parser Name simpleNameP = uncurry (\sourceSpan identifierText -> locatedName sourceSpan (Name identifierText)) <$> locatedIdentifierP +booleanNameP :: Parser Name +booleanNameP = + locatedP locatedName $ + Name "true" + <$ keyword "true" + <|> Name "false" + <$ keyword "false" + locatedIdentifierP :: Parser (SourceSpan, String) locatedIdentifierP = do startPos <- getSourcePos @@ -99,8 +108,10 @@ functionTypeP = do args <- parens (typeP `sepBy` comma) visibility <- optional - ( FunctionTypeInternal <$ keyword "internal" - <|> FunctionTypeExternal <$ keyword "external" + ( FunctionTypeInternal + <$ keyword "internal" + <|> FunctionTypeExternal + <$ keyword "external" ) results <- optional returnsTypeP pure (FunctionTy args visibility results) diff --git a/src/Solcore/Frontend/Parser/Stmt.hs b/src/Solcore/Frontend/Parser/Stmt.hs index e0ec41a71..6b913a535 100644 --- a/src/Solcore/Frontend/Parser/Stmt.hs +++ b/src/Solcore/Frontend/Parser/Stmt.hs @@ -45,17 +45,21 @@ letP :: Parser Stmt letP = locatedP locatedStmt $ do keyword "let" ct <- option False (True <$ keyword "comptime") - stmt <- try (tupleLetRemainder ct) <|> simpleLetRemainder ct + stmt <- letRemainderP ct _ <- semicolon pure stmt + +letRemainderP :: Bool -> Parser Stmt +letRemainderP ct = + try tupleLetRemainder <|> simpleLetRemainder where - simpleLetRemainder ct = do + simpleLetRemainder = do n <- simpleNameP mt <- optional (colon *> typeP) me <- optional (equalsP *> expP) pure (Let ct n mt me) - tupleLetRemainder ct = do + tupleLetRemainder = do pat <- bindingTuplePatP mt <- optional (colon *> typeP) value <- equalsP *> expP @@ -64,9 +68,9 @@ letP = locatedP locatedStmt $ do returnP :: Parser Stmt returnP = locatedP locatedStmt $ do keyword "return" - value <- option (ExpName Nothing "()" []) expP + value <- optional expP _ <- semicolon - pure (Return value) + pure (maybe BareReturn Return value) ifP :: Parser Stmt ifP = locatedP locatedStmt $ do @@ -112,8 +116,9 @@ uncheckedP = revertP :: Parser Stmt revertP = - locatedP locatedStmt - (StmtExp (ExpName Nothing "revert" []) <$ (keyword "revert" *> semicolon)) + locatedP + locatedStmt + (Revert <$ (keyword "revert" *> semicolon)) blockP :: Parser Stmt blockP = locatedP locatedStmt (Block <$> braces bodyP) @@ -152,10 +157,7 @@ forLetP :: Parser Stmt forLetP = locatedP locatedStmt $ do keyword "let" ct <- option False (True <$ keyword "comptime") - n <- simpleNameP - mt <- optional (colon *> typeP) - me <- optional (equalsP *> expP) - return (Let ct n mt me) + letRemainderP ct forAssignP :: Parser Stmt forAssignP = locatedP locatedStmt $ do diff --git a/src/Solcore/Frontend/Pretty/ShortName.hs b/src/Solcore/Frontend/Pretty/ShortName.hs index d83ec8338..93b0d9052 100644 --- a/src/Solcore/Frontend/Pretty/ShortName.hs +++ b/src/Solcore/Frontend/Pretty/ShortName.hs @@ -19,7 +19,7 @@ instance HasShortName Name instance HasShortName Id instance (HasShortName a) => HasShortName (Contract a) where - shortName (Contract n _ _) = shortName n + shortName (ContractWithKind _ n _ _) = shortName n instance (HasShortName a) => HasShortName (Signature a) where shortName sig = shortName (sigName sig) diff --git a/src/Solcore/Frontend/Pretty/SolcorePretty.hs b/src/Solcore/Frontend/Pretty/SolcorePretty.hs index c331d33c2..6867a6eec 100644 --- a/src/Solcore/Frontend/Pretty/SolcorePretty.hs +++ b/src/Solcore/Frontend/Pretty/SolcorePretty.hs @@ -153,13 +153,18 @@ instance Pretty PragmaStatus where ppr _ = empty instance (Pretty a) => Pretty (Contract a) where - ppr (Contract n ts ds) = - text "contract" + ppr (ContractWithKind kind n ts ds) = + pprContractKind kind <+> (ppr n <> pprTyParams (map TyVar ts)) <+> lbrace $$ nest 3 (vcat (map ppr ds)) $$ rbrace +pprContractKind :: ContractKind -> Doc +pprContractKind ContractKind = text "contract" +pprContractKind InterfaceKind = text "interface" +pprContractKind LibraryKind = text "library" + instance (Pretty a) => Pretty (ContractDecl a) where ppr (CDataDecl dt) = ppr dt @@ -183,18 +188,28 @@ instance (Pretty a) => Pretty (Constructor a) where $$ rbrace instance Pretty DataTy where + ppr (StructTy n ps fieldNames fieldTypes) = + text "struct" + <+> (ppr (constructorLeafName n) <> pprTyParams (map TyVar ps)) + <+> lbrace + $$ nest 3 (vcat (zipWith pprStructField fieldNames fieldTypes)) + $$ rbrace ppr (DataTy n ps cs) = text "enum" - <+> (ppr n <> pprTyParams (map TyVar ps)) + <+> (ppr (constructorLeafName n) <> pprTyParams (map TyVar ps)) <+> lbrace $$ nest 3 (vcat (punctuate comma (map ppr cs))) $$ rbrace +pprStructField :: Name -> Ty -> Doc +pprStructField fieldName' fieldType = + ((ppr fieldName' <> colon) <+> ppr fieldType) <> semi + instance Pretty TySym where ppr (TySym n vs t) = - ( text "type" + ( text "alias" <+> (ppr n <> pprTyParams (map TyVar vs)) - <+> text "is" + <+> equals <+> ppr t ) <> semi @@ -266,39 +281,88 @@ instance (Pretty a) => Pretty (FunDef a) where ppr (FunDef isPub sig bd) = pprSignature isPub sig <+> lbrace - $$ nest 3 (vcat (map ppr bd)) + $$ nest 3 (vcat (map ppr (dropResolvedReturnLocals sig bd))) $$ rbrace pprSignature :: (Pretty a) => Bool -> Signature a -> Doc -pprSignature isPub (Signature vs ctx n ps rc ty pay) +pprSignature isPub sig@(Signature vs ctx n ps rc ty _) | n == Name "fallback" = (text "fallback" <> pprParams ps) - <+> text "external" - <+> pprPayable pay + <+> pprResolvedFunctionModifiers (Just VisibilityExternal) sig | otherwise = text "function" <+> (ppr n <> pprTyParams (map TyVar vs) <> pprParams ps) - <+> pprFunctionModifiers isPub pay - <+> pprRetTy rc ty + <+> pprResolvedFunctionModifiers + (if isPub then Just VisibilityPublic else Nothing) + sig + <+> pprResolvedReturns sig rc ty <+> pprWhere ctx pprContractSignature :: (Pretty a) => Bool -> Signature a -> Doc -pprContractSignature isExternal (Signature vs ctx n ps rc ty pay) = +pprContractSignature isExternal sig@(Signature vs ctx n ps rc ty _) = text "function" <+> (ppr n <> pprTyParams (map TyVar vs) <> pprParams ps) - <+> hsep - ( [text "external" | isExternal] - ++ [text "payable" | pay] - ) - <+> pprRetTy rc ty + <+> pprResolvedFunctionModifiers + (if isExternal then Just VisibilityExternal else Nothing) + sig + <+> pprResolvedReturns sig rc ty <+> pprWhere ctx -pprFunctionModifiers :: Bool -> Bool -> Doc -pprFunctionModifiers isPub payable = - hsep - ( [text "public" | isPub] - ++ [text "payable" | payable] - ) +pprResolvedReturns :: Signature a -> Bool -> Maybe Ty -> Doc +pprResolvedReturns sig returnComptime returnTy = + case sigReturnItems sig of + [] -> pprRetTy returnComptime returnTy + items -> + text "returns" + <+> parens (commaSep (map pprResolvedReturnItem items)) + +pprResolvedReturnItem :: SignatureReturnItem -> Doc +pprResolvedReturnItem returnItem = + pprConst (signatureReturnItemComptime returnItem) + <> case signatureReturnItemName returnItem of + Nothing -> ppr (signatureReturnItemType returnItem) + Just returnName -> + (ppr returnName <> colon) + <+> ppr (signatureReturnItemType returnItem) + +-- Name resolution materializes named return slots as uninitialized leading +-- lets. They are an internal representation detail; printing them alongside +-- the restored @returns (name: type)@ clause would create duplicate bindings +-- when the output is parsed again. +dropResolvedReturnLocals :: Signature a -> Body a -> Body a +dropResolvedReturnLocals sig = + dropLeadingReturnLocals namedReturnCount + where + namedReturnCount = + length + [ () + | returnItem <- sigReturnItems sig, + signatureReturnItemName returnItem /= Nothing + ] + + dropLeadingReturnLocals 0 body = body + dropLeadingReturnLocals count (Let _ _ _ Nothing : body) = + dropLeadingReturnLocals (count - 1) body + dropLeadingReturnLocals _ body = body + +pprResolvedFunctionModifiers :: Maybe FunctionVisibility -> Signature a -> Doc +pprResolvedFunctionModifiers fallbackVisibility sig = + hsep (map pprResolvedFunctionModifier modifiers) + where + modifiers + | Just _ <- sigVisibility sig = sigModifiers sig + | Just visibility <- fallbackVisibility = + VisibilityModifier visibility : sigModifiers sig + | otherwise = sigModifiers sig + +pprResolvedFunctionModifier :: FunctionModifier -> Doc +pprResolvedFunctionModifier (VisibilityModifier VisibilityPublic) = text "public" +pprResolvedFunctionModifier (VisibilityModifier VisibilityExternal) = text "external" +pprResolvedFunctionModifier (VisibilityModifier VisibilityInternal) = text "internal" +pprResolvedFunctionModifier (VisibilityModifier VisibilityPrivate) = text "private" +pprResolvedFunctionModifier (MutabilityModifier MutabilityPure) = text "pure" +pprResolvedFunctionModifier (MutabilityModifier MutabilityView) = text "view" +pprResolvedFunctionModifier (MutabilityModifier MutabilityPayable) = text "payable" pprPayable :: Bool -> Doc pprPayable True = text "payable" @@ -459,7 +523,9 @@ postfixTypedExpPrec, atomTypedExpPrec :: Int lowestTypedExpPrec = 0 ternaryTypedExpPrec = 10 castTypedExpPrec = 110 + postfixTypedExpPrec = 130 + atomTypedExpPrec = 140 pprTypedExpPrec :: (Pretty a) => Int -> Exp a -> Doc @@ -495,15 +561,15 @@ pprTypedExpNode (Call (Just receiver) n es) = <> parens (nest 1 $ commaSep $ map (pprTypedExpPrec lowestTypedExpPrec) es) pprTypedExpNode (Lam args bd lambdaRetTy) = - (text "lam" <> pprParams args) - <+> pprRetTy False lambdaRetTy - <+> lbrace - $$ nest 3 (vcat (map ppr bd)) - $$ rbrace + (text "lam" <> pprParams args) + <+> pprRetTy False lambdaRetTy + <+> lbrace + $$ nest 3 (vcat (map ppr bd)) + $$ rbrace pprTypedExpNode (TyExp e ty) = pprTypedExpPrec castTypedExpPrec e <+> text "as" <+> ppr ty pprTypedExpNode (FieldAccess Nothing n) = - text "this" <> char '.' <> ppr n + ppr n pprTypedExpNode (FieldAccess (Just receiver) n) = pprTypedExpPrec postfixTypedExpPrec receiver <> char '.' <> ppr n pprTypedExpNode (Cond condition thenExpression elseExpression) = diff --git a/src/Solcore/Frontend/Pretty/TreePretty.hs b/src/Solcore/Frontend/Pretty/TreePretty.hs index 99b006a97..4f34cd04c 100644 --- a/src/Solcore/Frontend/Pretty/TreePretty.hs +++ b/src/Solcore/Frontend/Pretty/TreePretty.hs @@ -182,9 +182,9 @@ pprStructField fieldName' fieldType = instance Pretty TySym where ppr (TySym n vs t) = - ( text "type" + ( text "alias" <+> (ppr n <> pprTyParams vs) - <+> text "is" + <+> equals <+> ppr t ) <> semi @@ -377,9 +377,8 @@ instance Pretty Stmt where ppr (StmtExp e) | isBareRevert e = text "revert" <> semi | otherwise = ppr e <> semi - ppr (Return e) - | isUnitExp e = text "return" <> semi - | otherwise = text "return" <+> (ppr e <> semi) + ppr (Return e) = text "return" <+> (ppr e <> semi) + ppr BareReturn = text "return" <> semi ppr (Match e eqns) = text "match" <+> (parens $ commaSep $ map ppr e) @@ -420,6 +419,7 @@ instance Pretty Stmt where $$ rbrace ppr Break = text "break" <> semi ppr Continue = text "continue" <> semi + ppr Revert = text "revert" <> semi ppr EmptyStmt = empty pprForClause :: Stmt -> Doc @@ -499,18 +499,31 @@ lowestExpPrec = 0 ternaryExpPrec = 10 logicalOrExpPrec = 20 logicalAndExpPrec = 30 + equalityExpPrec = 40 + relationalExpPrec = 50 + bitOrExpPrec = 60 + bitXorExpPrec = 70 + bitAndExpPrec = 80 + shiftExpPrec = 85 + additiveExpPrec = 90 + multiplicativeExpPrec = 100 + powerExpPrec = 105 + castExpPrec = 110 + unaryExpPrec = 120 + postfixExpPrec = 130 + atomExpPrec = 140 pprExpPrec :: Int -> Exp -> Doc @@ -534,6 +547,9 @@ pprExpNode (ExpName (Just receiver) n es) = <> char '.' <> ppr n <> parens (commaSep (map (pprExpPrec lowestExpPrec) es)) +pprExpNode (ExpApply callee args) = + pprExpPrec postfixExpPrec callee + <> parens (commaSep (map (pprExpPrec lowestExpPrec) args)) pprExpNode (ExpVar Nothing v) = ppr v pprExpNode (ExpVar (Just receiver) v) = pprExpPrec postfixExpPrec receiver <> char '.' <> ppr v @@ -544,11 +560,11 @@ pprExpNode (ExpDotName n es) = <> ppr n <> parens (commaSep (map (pprExpPrec lowestExpPrec) es)) pprExpNode (Lam args bd lambdaRetTy) = - (text "lam" <> pprParams args) - <+> pprRetTy False lambdaRetTy - <+> lbrace - $$ nest 3 (vcat (map ppr bd)) - $$ rbrace + (text "lam" <> pprParams args) + <+> pprRetTy False lambdaRetTy + <+> lbrace + $$ nest 3 (vcat (map ppr bd)) + $$ rbrace pprExpNode (TyExp e ty) = pprExpPrec castExpPrec e <+> text "as" <+> ppr ty pprExpNode (ExpIndexed collection index) = @@ -603,9 +619,9 @@ pprExpNode (ExpCond condition thenExpression elseExpression) = pprExpPrec ternaryExpPrec elseExpression ] pprExpNode (ExpAt t) = - text "Proxy" - <+> text "as" - <+> ppr (TyCon (Name "Proxy") [t]) + text "Proxy" + <+> text "as" + <+> ppr (TyCon (Name "Proxy") [t]) pprLeftAssocBinary :: Int -> String -> Exp -> Exp -> Doc pprLeftAssocBinary precedence operator left right = @@ -656,6 +672,7 @@ expPrecedence (TyExp _ _) = castExpPrec expPrecedence (ExpAt _) = castExpPrec expPrecedence (ExpLNot _) = unaryExpPrec expPrecedence (ExpName (Just _) _ _) = postfixExpPrec +expPrecedence (ExpApply _ _) = postfixExpPrec expPrecedence (ExpVar (Just _) _) = postfixExpPrec expPrecedence (ExpIndexed _ _) = postfixExpPrec expPrecedence _ = atomExpPrec @@ -762,10 +779,6 @@ constructorLeafName :: Name -> Name constructorLeafName (QualName _ leaf) = Name leaf constructorLeafName n = n -isUnitExp :: Exp -> Bool -isUnitExp (ExpName Nothing n []) = isUnit n -isUnitExp _ = False - isBareRevert :: Exp -> Bool isBareRevert (ExpName Nothing n []) = n == Name "revert" isBareRevert _ = False diff --git a/src/Solcore/Frontend/Syntax/Contract.hs b/src/Solcore/Frontend/Syntax/Contract.hs index 6b21ef873..80572c54f 100644 --- a/src/Solcore/Frontend/Syntax/Contract.hs +++ b/src/Solcore/Frontend/Syntax/Contract.hs @@ -1,3 +1,5 @@ +{-# LANGUAGE PatternSynonyms #-} + module Solcore.Frontend.Syntax.Contract where import Data.Generics (Data, Typeable) @@ -105,24 +107,70 @@ data ItemSelectorEntry -- definition of the contract structure +data ContractKind + = ContractKind + | InterfaceKind + | LibraryKind + deriving (Eq, Ord, Show, Data, Typeable) + data Contract a - = Contract - { name :: Name, + = ContractWithKind + { contractKind :: ContractKind, + name :: Name, tyParams :: [Tyvar], decls :: [ContractDecl a] } deriving (Eq, Ord, Show, Data, Typeable) +-- Keep the historical three-argument semantic-AST constructor available to +-- external callers. Compiler passes use 'ContractWithKind' explicitly whenever +-- they reconstruct a declaration, so a preserved interface or library kind +-- cannot silently become an ordinary contract. +pattern Contract :: Name -> [Tyvar] -> [ContractDecl a] -> Contract a +pattern Contract n ts ds <- ContractWithKind _ n ts ds + where + Contract n ts ds = ContractWithKind ContractKind n ts ds + +pattern ContractShell :: ContractKind -> Name -> [Tyvar] -> [ContractDecl a] -> Contract a +pattern ContractShell k n ts ds = ContractWithKind k n ts ds + +{-# COMPLETE Contract #-} + +{-# COMPLETE ContractShell #-} + -- definition of a algebraic data type data DataTy - = DataTy - { dataName :: Name, + = DataTyWithKind + { dataTyKind :: DataTyKind, + dataName :: Name, dataParams :: [Tyvar], dataConstrs :: [Constr] } deriving (Eq, Ord, Show, Data, Typeable) +data DataTyKind + = EnumKind + | StructKind [Name] + deriving (Eq, Ord, Show, Data, Typeable) + +-- Keep the historical three-argument constructor available to compiler passes. +-- Synthetic declarations are enums by default, while declarations originating +-- from source use 'DataTyWithKind' to retain their exact syntax kind. +pattern DataTy :: Name -> [Tyvar] -> [Constr] -> DataTy +pattern DataTy n ts cs <- DataTyWithKind _ n ts cs + where + DataTy n ts cs = DataTyWithKind EnumKind n ts cs + +pattern StructTy :: Name -> [Tyvar] -> [Name] -> [Ty] -> DataTy +pattern StructTy n ts fieldNames fieldTypes <- + DataTyWithKind (StructKind fieldNames) n ts [Constr _ fieldTypes] + where + StructTy n ts fieldNames fieldTypes = + DataTyWithKind (StructKind fieldNames) n ts [Constr n fieldTypes] + +{-# COMPLETE DataTy #-} + data Constr = Constr { constrName :: Name, @@ -163,18 +211,123 @@ data Class a } deriving (Eq, Ord, Show, Data, Typeable) +-- | One source-level item in a function's @returns (...)@ clause. +-- +-- The aggregate 'sigReturn' type deliberately remains available because the +-- type checker and backend operate on a single result type. This metadata +-- preserves the item boundaries that aggregation loses: in particular, +-- @returns (result: (word, bool))@ is one tuple-valued ABI output, whereas +-- @returns (word, bool)@ is two outputs. +data SignatureReturnItem + = SignatureReturnItem + { signatureReturnItemComptime :: Bool, + signatureReturnItemName :: Maybe Name, + signatureReturnItemType :: Ty + } + deriving (Eq, Ord, Show, Data, Typeable) + +-- | Solidity-style source visibility retained after name resolution. +-- +-- 'Nothing' in 'sigVisibility' is meaningful: module-level functions and +-- compiler-generated functions do not carry a contract visibility modifier. +data FunctionVisibility + = VisibilityPublic + | VisibilityExternal + | VisibilityInternal + | VisibilityPrivate + deriving (Eq, Ord, Show, Data, Typeable) + +-- | Solidity-style state mutability retained after name resolution. +-- +-- A signature with no mutability modifier is nonpayable. Keeping the source +-- modifier itself lets ABI generation distinguish @pure@ and @view@ from that +-- default, instead of collapsing all three to a legacy payability bit. +data FunctionMutability + = MutabilityPure + | MutabilityView + | MutabilityPayable + deriving (Eq, Ord, Show, Data, Typeable) + +data FunctionModifier + = VisibilityModifier FunctionVisibility + | MutabilityModifier FunctionMutability + deriving (Eq, Ord, Show, Data, Typeable) + data Signature a - = Signature + = SignatureWithReturnNames { sigVars :: [Tyvar], sigContext :: [Pred], sigName :: Name, sigParams :: [Param a], sigRetComptime :: Bool, sigReturn :: Maybe Ty, - sigPayable :: Bool + sigPayable :: Bool, + -- | Solidity ABI names for return values. An empty list is the compact + -- representation used by legacy/compiler-generated signatures whose + -- returns are all unnamed. + sigReturnNames :: [Maybe Name], + -- | Exact source return-item boundaries and per-item comptime metadata. + -- Legacy and compiler-generated signatures leave this empty and continue + -- to use 'sigReturn', 'sigRetComptime', and 'sigReturnNames'. + sigReturnItems :: [SignatureReturnItem], + -- | Exact contract visibility and mutability modifiers from the source. + -- Legacy/compiler-generated signatures leave this empty. + sigModifiers :: [FunctionModifier] } deriving (Eq, Ord, Show, Data, Typeable) +-- Keep the historical seven-argument constructor available throughout the +-- compiler. Source name resolution uses the richer constructor for explicit +-- return clauses, while generated and legacy signatures can stay compact. +pattern Signature :: + [Tyvar] -> + [Pred] -> + Name -> + [Param a] -> + Bool -> + Maybe Ty -> + Bool -> + Signature a +pattern Signature vars context funName params returnComptime returnTy payable <- + SignatureWithReturnNames + vars + context + funName + params + returnComptime + returnTy + payable + _ + _ + _ + where + Signature vars context funName params returnComptime returnTy payable = + SignatureWithReturnNames + vars + context + funName + params + returnComptime + returnTy + payable + [] + [] + [MutabilityModifier MutabilityPayable | payable] + +{-# COMPLETE Signature #-} + +sigVisibility :: Signature a -> Maybe FunctionVisibility +sigVisibility sig = + case [visibility | VisibilityModifier visibility <- sigModifiers sig] of + visibility : _ -> Just visibility + [] -> Nothing + +sigMutability :: Signature a -> Maybe FunctionMutability +sigMutability sig = + case [mutability | MutabilityModifier mutability <- sigModifiers sig] of + mutability : _ -> Just mutability + [] -> Nothing + data Instance a = Instance { instDefault :: Bool, @@ -315,12 +468,16 @@ instance HasSourceSpan ItemSelectorEntry where firstSourceSpan [sourceSpanOf n, sourceSpanOf aliasName] instance (HasSourceSpan a) => HasSourceSpan (Contract a) where - sourceSpanOf (Contract n tyVars contractDecls) = + sourceSpanOf (ContractWithKind _ n tyVars contractDecls) = firstSourceSpan [sourceSpanOf n, sourceSpanOf tyVars, sourceSpanOf contractDecls] instance HasSourceSpan DataTy where - sourceSpanOf (DataTy n tyVars constrs) = - firstSourceSpan [sourceSpanOf n, sourceSpanOf tyVars, sourceSpanOf constrs] + sourceSpanOf (DataTyWithKind kind n tyVars constrs) = + firstSourceSpan [sourceSpanOf n, sourceSpanOf tyVars, sourceSpanOf kind, sourceSpanOf constrs] + +instance HasSourceSpan DataTyKind where + sourceSpanOf EnumKind = Nothing + sourceSpanOf (StructKind fieldNames) = sourceSpanOf fieldNames instance HasSourceSpan Constr where sourceSpanOf (Constr n tys) = diff --git a/src/Solcore/Frontend/Syntax/NameResolution.hs b/src/Solcore/Frontend/Syntax/NameResolution.hs index 9787afbd1..8b5700cd7 100644 --- a/src/Solcore/Frontend/Syntax/NameResolution.hs +++ b/src/Solcore/Frontend/Syntax/NameResolution.hs @@ -10,8 +10,9 @@ import Data.Generics (Data, everything, extQ, mkQ) import Data.List ((\\)) import Data.Map (Map) import Data.Map qualified as Map -import Data.Maybe (mapMaybe) +import Data.Maybe (catMaybes, fromMaybe, isJust, mapMaybe) import Data.Monoid (First (..)) +import Language.Yul (YulExp (YCall), YulStmt (YExp), yulInt) import Solcore.Diagnostics (CompilerError (..), Diagnostic (..), DiagnosticCode (..), Label (..), LabelStyle (..), Severity (..), SourceSpan, addDiagnosticNote, diagnosticCompilerError) import Solcore.Frontend.Pretty.TreePretty import Solcore.Frontend.Syntax.Contract hiding (contracts, decls) @@ -20,6 +21,7 @@ import Solcore.Frontend.Syntax.Name import Solcore.Frontend.Syntax.Stmt import Solcore.Frontend.Syntax.SyntaxTree qualified as S import Solcore.Frontend.Syntax.Ty +import Solcore.Primitives.Primitives (invokableName, tupleExpFromList) -- name resolution @@ -97,12 +99,14 @@ validateDuplicateNamespacesInTopDeclSegments segments = do ensureNoDuplicateNames "type namespace" (concatMap topLevelTypeNames segments) ensureNoDuplicateNames "term namespace" (concatMap topLevelTermNames segments) mapM_ validateContractDuplicates [c | segment <- segments, S.TContr c <- segment] + mapM_ validateDataTyDuplicates [d | segment <- segments, S.TDataDef d <- segment] validateDuplicateNamespaces :: [S.TopDecl] -> Either CompilerError () validateDuplicateNamespaces ds = do ensureNoDuplicateNames "type namespace" (topLevelTypeNames ds) ensureNoDuplicateNames "term namespace" (topLevelTermNames ds) mapM_ validateContractDuplicates [c | S.TContr c <- ds] + mapM_ validateDataTyDuplicates [d | S.TDataDef d <- ds] validateContractDuplicates :: S.Contract -> Either CompilerError () validateContractDuplicates (S.Contract cname _ decls) = do @@ -113,6 +117,15 @@ validateContractDuplicates (S.Contract cname _ decls) = do ensureNoDuplicateNamesIn context "type namespace" typeNames ensureNoDuplicateNamesIn context "field namespace" fieldNames ensureNoDuplicateNamesIn context "term namespace" termNames + mapM_ validateDataTyDuplicates [d | S.CDataDecl d <- decls] + +validateDataTyDuplicates :: S.DataTy -> Either CompilerError () +validateDataTyDuplicates (S.DataTyWithKind (S.StructKind fieldNames) typeName _ _) = + ensureNoDuplicateNamesIn + ("struct " ++ pretty typeName) + "field namespace" + fieldNames +validateDataTyDuplicates _ = pure () topLevelTypeNames :: [S.TopDecl] -> [Name] topLevelTypeNames = concatMap collect @@ -222,25 +235,33 @@ resolveExport (S.ExportItemsFrom path items) = instance Resolve S.Contract where type Result S.Contract = Contract Name - resolve c@(S.Contract n vs decls) = + resolve c@(S.ContractShell sourceKind n vs decls) = do let ns = map tyconName vs mapM_ addTyVar ns - mapM_ addContractDecl decls - Contract n (map TVar ns) <$> resolve decls `wrapError` c - -addContractDecl :: S.ContractDecl -> ResolveM () -addContractDecl (S.CDataDecl (S.DataTy n _ cons)) = + mapM_ (addContractDecl n) decls + ContractWithKind (resolveContractKind sourceKind) n (map TVar ns) + <$> resolve decls + `wrapError` c + +resolveContractKind :: S.ContractKind -> ContractKind +resolveContractKind S.ContractKind = ContractKind +resolveContractKind S.InterfaceKind = InterfaceKind +resolveContractKind S.LibraryKind = LibraryKind + +addContractDecl :: Name -> S.ContractDecl -> ResolveM () +addContractDecl contractName (S.CDataDecl (S.DataTy n _ cons)) = do - addTyCon n - mapM_ (addDataCon n . S.constrName) cons -addContractDecl (S.CFieldDecl (S.Field n _ _)) = + let qualifiedTypeName = qualifyName contractName n + addTyConAs n qualifiedTypeName + mapM_ (addDataCon qualifiedTypeName . S.constrName) cons +addContractDecl _ (S.CFieldDecl (S.Field n _ _)) = addField n -addContractDecl (S.CFunDecl (S.FunDef _ sig _)) = +addContractDecl _ (S.CFunDecl (S.FunDef _ sig _)) = addFunctionName (S.sigName sig) -addContractDecl (S.CSignatureDecl _ sig) = +addContractDecl _ (S.CSignatureDecl _ sig) = addFunctionName (S.sigName sig) -addContractDecl _ = pure () +addContractDecl _ _ = pure () instance Resolve S.ContractDecl where type Result S.ContractDecl = ContractDecl Name @@ -265,7 +286,7 @@ instance Resolve S.Constructor where ps' <- resolve ps `wrapError` c let args = map paramName ps' mapM_ addParameter args - bdy' <- resolve bdy `wrapError` c + bdy' <- withBareReturnValue Nothing (resolve bdy) `wrapError` c pure (Constructor ps' bdy' payable) instance Resolve S.Field where @@ -297,6 +318,90 @@ instance Resolve S.Class where ts' = map TVar nts pure (Class vs' ps' n ts' t' sigs') +data ResolvedReturns + = ResolvedReturns + { resolvedReturnType :: Ty, + resolvedReturnNames :: [Maybe Name], + resolvedReturnItems :: [SignatureReturnItem], + resolvedReturnComptime :: Bool, + resolvedReturnBindings :: [(Bool, Name, Ty)], + resolvedBareReturnNames :: Maybe [Name] + } + +resolveSignatureReturns :: S.Signature -> ResolveM ResolvedReturns +resolveSignatureReturns sig = + case S.sigReturnItems sig of + Nothing -> + pure + ResolvedReturns + { resolvedReturnType = unitReturnType, + resolvedReturnNames = [], + resolvedReturnItems = [], + resolvedReturnComptime = False, + resolvedReturnBindings = [], + resolvedBareReturnNames = Nothing + } + Just sourceItems -> do + rejectMixedReturnComptime sig sourceItems + resolvedItems <- + forM sourceItems $ \(S.ReturnItem isComptime returnName returnTy) -> do + returnTy' <- resolve returnTy + pure (isComptime, returnName, returnTy') + let itemTypes = [returnTy | (_, _, returnTy) <- resolvedItems] + itemNames = [returnName | (_, returnName, _) <- resolvedItems] + returnNames + | any isJust itemNames = itemNames + | otherwise = [] + returnItems = + [ SignatureReturnItem isComptime returnName returnTy + | (isComptime, returnName, returnTy) <- resolvedItems + ] + returnComptime = + case resolvedItems of + (isComptime, _, _) : _ -> isComptime + [] -> False + bindings = + [ (isComptime, returnName, returnTy) + | (isComptime, Just returnName, returnTy) <- resolvedItems + ] + bareReturnNames + | not (null itemNames) && all isJust itemNames = + Just (catMaybes itemNames) + | otherwise = Nothing + pure + ResolvedReturns + { resolvedReturnType = tupleReturnType itemTypes, + resolvedReturnNames = returnNames, + resolvedReturnItems = returnItems, + resolvedReturnComptime = returnComptime, + resolvedReturnBindings = bindings, + resolvedBareReturnNames = bareReturnNames + } + where + unitReturnType = TyCon (Name "()") [] + +rejectMixedReturnComptime :: S.Signature -> [S.ReturnItem] -> ResolveM () +rejectMixedReturnComptime sig returnItems = + unless (allSame (map S.returnItemComptime returnItems)) $ + diagnosticErrorAtName + "SC0123" + "mixed comptime and runtime return items are not supported" + (S.sigName sig) + "return items must use one comptime mode" + [ "the backend currently represents result comptime-ness once per function", + "function: " ++ pretty (S.sigName sig) + ] + ["mark either every return item or no return item as comptime"] + where + allSame [] = True + allSame (x : xs) = all (== x) xs + +tupleReturnType :: [Ty] -> Ty +tupleReturnType [] = TyCon (Name "()") [] +tupleReturnType [returnTy] = returnTy +tupleReturnType (returnTy : returnTys) = + TyCon (Name "pair") [returnTy, tupleReturnType returnTys] + instance Resolve S.Signature where type Result S.Signature = Signature Name @@ -306,19 +411,39 @@ instance Resolve S.Signature where mapM_ addTyVar ns ctx' <- resolve ctx `wrapError` s ps' <- resolve ps `wrapError` s - mt' <- resolve (S.sigReturn s) `wrapError` s + returns <- resolveSignatureReturns s `wrapError` s let vs' = map TVar ns pure - ( Signature + ( SignatureWithReturnNames vs' ctx' n ps' - (S.sigRetComptime s) - mt' + (resolvedReturnComptime returns) + (Just (resolvedReturnType returns)) (S.sigPayable s) + (resolvedReturnNames returns) + (resolvedReturnItems returns) + (map resolveFunctionModifier (S.sigModifiers s)) ) +resolveFunctionModifier :: S.FunctionModifier -> FunctionModifier +resolveFunctionModifier (S.VisibilityModifier visibility) = + VisibilityModifier (resolveFunctionVisibility visibility) +resolveFunctionModifier (S.MutabilityModifier mutability) = + MutabilityModifier (resolveFunctionMutability mutability) + +resolveFunctionVisibility :: S.FunctionVisibility -> FunctionVisibility +resolveFunctionVisibility S.VisibilityPublic = VisibilityPublic +resolveFunctionVisibility S.VisibilityExternal = VisibilityExternal +resolveFunctionVisibility S.VisibilityInternal = VisibilityInternal +resolveFunctionVisibility S.VisibilityPrivate = VisibilityPrivate + +resolveFunctionMutability :: S.FunctionMutability -> FunctionMutability +resolveFunctionMutability S.MutabilityPure = MutabilityPure +resolveFunctionMutability S.MutabilityView = MutabilityView +resolveFunctionMutability S.MutabilityPayable = MutabilityPayable + instance Resolve S.Instance where type Result S.Instance = Instance Name @@ -377,27 +502,116 @@ instance Resolve S.FunDef where resolve f@(S.FunDef legacyIsPub sourceSig@(S.SignatureWithSyntax vs ctx n ps _ _) bds) = do + validateNamedReturnBindings sourceSig bds let ns = map tyconName vs withLocalCtx $ do mapM_ addTyVar ns ctx' <- resolve ctx `wrapError` f ps' <- resolve ps `wrapError` f - mt' <- resolve (S.sigReturn sourceSig) `wrapError` f + returns <- resolveSignatureReturns sourceSig `wrapError` f let args = map paramName ps' mapM_ addParameter args - bds' <- resolve bds `wrapError` f + mapM_ (addLocalVar . namedReturnName) (resolvedReturnBindings returns) + resolvedBody <- + withBareReturnValue + (tupleReturnExp . map Var <$> resolvedBareReturnNames returns) + (resolve bds) + `wrapError` f let vs' = map TVar ns sig = - Signature + SignatureWithReturnNames vs' ctx' n ps' - (S.sigRetComptime sourceSig) - mt' + (resolvedReturnComptime returns) + (Just (resolvedReturnType returns)) (S.sigPayable sourceSig) + (resolvedReturnNames returns) + (resolvedReturnItems returns) + (map resolveFunctionModifier (S.sigModifiers sourceSig)) isPublic = legacyIsPub || S.sigIsPublic sourceSig - pure (FunDef isPublic sig bds') + returnLocals = + [ Let isComptime returnName (Just returnTy) Nothing + | (isComptime, returnName, returnTy) <- resolvedReturnBindings returns + ] + pure (FunDef isPublic sig (returnLocals ++ resolvedBody)) + where + namedReturnName (_, returnName, _) = returnName + +validateNamedReturnBindings :: S.Signature -> S.Body -> ResolveM () +validateNamedReturnBindings sig body = + unless (null returnNames) $ do + validate + "parameter and named return namespace" + (parameterNames ++ returnNames) + unless (null bodyCollisions) $ + validate + "named return namespace" + (returnNames ++ bodyCollisions) + where + context = "function " ++ pretty (S.sigName sig) + returnNames = + case S.sigReturnItems sig of + Nothing -> [] + Just items -> mapMaybe S.returnItemName items + parameterNames = map sourceParamName (S.sigParams sig) + bodyCollisions = + [ bindingName + | bindingName <- sourceBodyBindingNames body, + bindingName `elem` returnNames + ] + validate namespace names = + either + throwError + pure + (ensureNoDuplicateNamesIn context namespace names) + +sourceParamName :: S.Param -> Name +sourceParamName (S.Typed _ n _) = n +sourceParamName (S.Untyped _ n) = n + +sourceBodyBindingNames :: S.Body -> [Name] +sourceBodyBindingNames = concatMap sourceStmtBindingNames + +sourceStmtBindingNames :: S.Stmt -> [Name] +sourceStmtBindingNames (S.Let _ n _ _) = [n] +sourceStmtBindingNames (S.LetPattern _ pat _ _) = + sourcePatternBindingNames pat +sourceStmtBindingNames (S.Block body) = + sourceBodyBindingNames body +sourceStmtBindingNames (S.Match _ equations) = + concat + [ concatMap sourcePatternBindingNames patterns + ++ sourceBodyBindingNames equationBody + | (patterns, equationBody) <- equations + ] +sourceStmtBindingNames (S.If _ thenBody elseBody) = + sourceBodyBindingNames thenBody + ++ sourceBodyBindingNames elseBody +sourceStmtBindingNames (S.While _ body) = + sourceBodyBindingNames body +sourceStmtBindingNames (S.Unchecked body) = + sourceBodyBindingNames body +sourceStmtBindingNames (S.For initStmt _ postStmt body) = + sourceStmtBindingNames initStmt + ++ sourceStmtBindingNames postStmt + ++ sourceBodyBindingNames body +sourceStmtBindingNames _ = [] + +sourcePatternBindingNames :: S.Pat -> [Name] +sourcePatternBindingNames (S.Pat n []) = [n] +sourcePatternBindingNames (S.Pat _ patterns) = + concatMap sourcePatternBindingNames patterns +sourcePatternBindingNames (S.PatDot _ patterns) = + concatMap sourcePatternBindingNames patterns +sourcePatternBindingNames _ = [] + +tupleReturnExp :: [Exp Name] -> Exp Name +tupleReturnExp [] = Con (Name "()") [] +tupleReturnExp [returnExp] = returnExp +tupleReturnExp (returnExp : returnExps) = + Con (Name "pair") [returnExp, tupleReturnExp returnExps] instance Resolve S.Stmt where type Result S.Stmt = Stmt Name @@ -437,6 +651,13 @@ instance Resolve S.Stmt where locatedLike s locatedStmt <$> (StmtExp <$> resolve e `wrapError` s) resolve s@(S.Return e) = locatedLike s locatedStmt <$> (Return <$> resolve e `wrapError` s) + resolve s@S.BareReturn = do + returnValue <- + gets + ( fromMaybe (Con (Name "()") []) + . functionBareReturnValue + ) + pure (locatedLike s locatedStmt (Return returnValue)) resolve s@(S.Match es eqns) = locatedLike s locatedStmt <$> (Match <$> resolve es <*> resolve eqns) resolve s@(S.Asm blk) = @@ -451,6 +672,13 @@ instance Resolve S.Stmt where locatedLike s locatedStmt <$> (For <$> resolve initStmt <*> resolve cond <*> resolve postStmt <*> resolve body) resolve s@S.Break = pure (locatedLike s locatedStmt Break) resolve s@S.Continue = pure (locatedLike s locatedStmt Continue) + resolve s@S.Revert = + pure + ( locatedLike + s + locatedStmt + (Asm [YExp (YCall "revert" [yulInt 0, yulInt 0])]) + ) resolve s@S.EmptyStmt = pure (locatedLike s locatedStmt EmptyStmt) instance Resolve S.Equation where @@ -579,20 +807,20 @@ hasQualifiedConstructorLeaf _ = isSameNameConstructor :: Name -> ResolveM Bool isSameNameConstructor n = do - let leaf = constructorLeafName n - dt <- lookupType leaf + dt <- lookupType n case dt of Just TTyCon -> do - cdt <- lookupName (qualifiedConstructorName leaf leaf) + resolvedTypeName <- canonicalTypeName n + let constructorName = constructorLeafName resolvedTypeName + cdt <- lookupName (qualifiedConstructorName resolvedTypeName constructorName) pure (cdt == Just TDataCon) _ -> pure False resolveSameNameConstructorName :: Name -> ResolveM Name -resolveSameNameConstructorName n = - resolveQualifiedConstructorName leaf leaf - where - leaf = constructorLeafName n +resolveSameNameConstructorName n = do + resolvedTypeName <- canonicalTypeName n + resolveQualifiedConstructorName resolvedTypeName (constructorLeafName resolvedTypeName) -- A receiver like @Error@ in @Error.Empty@ is first parsed as an expression -- on its own and resolved before the outer member-access context is known. @@ -602,7 +830,7 @@ resolveSameNameConstructorName n = -- qualifier position so the outer qualifier-handling cases still match. unwrapQualifierReceiver :: Maybe (Exp Name) -> Maybe (Exp Name) unwrapQualifierReceiver (Just (Con (QualName d conName) [])) - | pretty d == conName = Just (Var d) + | constructorLeafName d == Name conName = Just (Var d) unwrapQualifierReceiver me = me -- UFCS receiver test. @@ -627,6 +855,15 @@ isUfcsReceiver :: Exp Name -> Bool isUfcsReceiver (FieldAccess Nothing _) = True isUfcsReceiver _ = False +-- Only declaration-like receivers participate in qualified-name lookup. +-- Every other expression is a runtime value whose member access must remain +-- explicit in the semantic AST; otherwise a same-spelled local can capture it. +isValueReceiver :: Exp Name -> ResolveM Bool +isValueReceiver (Var receiverName) = do + receiverKind <- lookupName receiverName + pure (receiverKind `notElem` map Just [TModule, TClass, TTyCon, TContract]) +isValueReceiver _ = pure True + instance Resolve S.Exp where type Result S.Exp = Exp Name @@ -642,97 +879,34 @@ resolveExp e@(S.Lam ps bd mt) = mt' <- resolve mt `wrapError` e let args = map paramName ps' mapM_ addParameter args - bd' <- resolve bd `wrapError` e + bd' <- withBareReturnValue Nothing (resolve bd) `wrapError` e pure (Lam ps' bd' mt') resolveExp (S.TyExp e t) = TyExp <$> resolve e <*> resolve t +resolveExp application@(S.ExpApply callee args) = do + callee' <- resolve callee `wrapError` application + args' <- resolve args `wrapError` application + pure + ( Call + Nothing + (QualName invokableName "invoke") + [callee', tupleExpFromList args'] + ) resolveExp c@(S.ExpVar me n) = do me' <- unwrapQualifierReceiver <$> (resolve me `wrapError` c) - dt <- lookupName n - case (me', dt) of - -- local variables and function parameters (unqualified only) - (Nothing, Just TLocalVar) -> pure (Var n) - (Nothing, Just TParameter) -> pure (Var n) - -- qualified access: qualifier takes precedence over local variable/parameter in scope - (Just (Var d), Just dt') | dt' `elem` [TLocalVar, TParameter] -> do - ct <- lookupName d - let qn = qualifyName d n - case ct of - Just TClass -> pure (Var qn) - Just TModule -> do - qdt <- lookupName qn - case qdt of - Just TFunction -> pure (Var qn) - Just TDataCon -> Con <$> resolveQualifiedConstructorName d n <*> pure [] - _ -> undefinedName qn - _ -> pure (Var n) - -- field access - (Nothing, Just TField) -> - pure (FieldAccess Nothing n) - -- function reference - (_, Just TFunction) -> do - dt1 <- gets (Map.lookup n . fieldEnv) - case dt1 of - Just TField -> pure (FieldAccess Nothing n) - _ -> pure (Var n) - -- data constructor - (Nothing, Just TDataCon) -> do - if isPrimitiveConstructor n - then pure (Con n []) - else case splitQualifiedName n of - Just (qualifier, conName) -> - Con <$> resolveQualifiedConstructorName qualifier conName <*> pure [] - Nothing -> unqualifiedConstructorError n - (Just (Var d), Just TDataCon) -> - Con <$> resolveQualifiedConstructorName d n <*> pure [] - (Just (Var d), Just TTyCon) -> do - let qn = qualifyName d n - qdt <- lookupName qn - case qdt of - Just TFunction -> pure (Var qn) - Just TDataCon -> Con <$> resolveQualifiedConstructorName d n <*> pure [] - Just TTyCon -> pure (Var qn) - Just TModule -> pure (Var qn) - _ -> undefinedName n - -- class name - (_, Just TClass) -> pure (Var n) - -- type constructor used as a constructor qualifier - (Nothing, Just TTyCon) -> do - sameName <- isSameNameConstructor n - if sameName - then Con <$> resolveSameNameConstructorName n <*> pure [] - else pure (Var n) - -- imported module qualifier name - (_, Just TModule) -> pure (Var n) - -- module-qualified function or constructor reference - (Just (Var d), Nothing) -> do - let qn = qualifyName d n - qdt <- lookupName qn - case qdt of - Just TFunction -> pure (Var qn) - Just TDataCon -> Con <$> resolveQualifiedConstructorName d n <*> pure [] - Just TTyCon -> pure (Var qn) - Just TModule -> pure (Var qn) - _ -> do - let fallback = qualifyName (constructorLeafName d) n - fdt <- lookupName fallback - case fdt of - Just TDataCon -> Con <$> resolveQualifiedConstructorName d n <*> pure [] - _ -> undefinedName n - _ -> do - sameName <- isSameNameConstructor n - if sameName - then Con <$> resolveSameNameConstructorName n <*> pure [] - else do - hasQualified <- hasQualifiedConstructorLeaf n - if hasQualified - then unqualifiedConstructorError n - else undefinedName n + valueReceiver <- maybe (pure False) isValueReceiver me' + if valueReceiver + then pure (FieldAccess me' n) + else resolveVariableReference me' n resolveExp x@(S.ExpName me n es) = do me' <- unwrapQualifierReceiver <$> (resolve me `wrapError` x) es' <- resolve es `wrapError` x + forM_ me' $ \receiver -> + unless (isUfcsReceiver receiver) $ do + valueReceiver <- isValueReceiver receiver + when valueReceiver (unsupportedValueMemberCall n) dt <- lookupName n case (me', dt) of -- normal function call @@ -815,6 +989,12 @@ resolveExp x@(S.ExpName me n es) = Just TFunction -> pure (Call Nothing qn es') Just TDataCon -> Con <$> resolveQualifiedConstructorName d n <*> pure es' _ -> undefinedName n + Just TTyCon -> do + qdt <- lookupName qn + case qdt of + Just TFunction -> pure (Call Nothing qn es') + Just TDataCon -> Con <$> resolveQualifiedConstructorName d n <*> pure es' + _ -> undefinedName n _ -> pure (Call Nothing n es') (Just (Var d), Just TParameter) -> do ct <- lookupName d @@ -827,6 +1007,12 @@ resolveExp x@(S.ExpName me n es) = Just TFunction -> pure (Call Nothing qn es') Just TDataCon -> Con <$> resolveQualifiedConstructorName d n <*> pure es' _ -> undefinedName n + Just TTyCon -> do + qdt <- lookupName qn + case qdt of + Just TFunction -> pure (Call Nothing qn es') + Just TDataCon -> Con <$> resolveQualifiedConstructorName d n <*> pure es' + _ -> undefinedName n _ -> pure (Call Nothing n es') -- UFCS-style method call on a contract-field receiver: -- field.method(args) -> Class.method(field, args) when a unique class @@ -964,6 +1150,100 @@ resolveExp (S.ExpAt t) = do (TyCon (Name "Proxy") [t']) ) +resolveVariableReference :: Maybe (Exp Name) -> Name -> ResolveM (Exp Name) +resolveVariableReference me' n = + do + dt <- lookupName n + case (me', dt) of + -- local variables and function parameters (unqualified only) + (Nothing, Just TLocalVar) -> pure (Var n) + (Nothing, Just TParameter) -> pure (Var n) + -- qualified access: qualifier takes precedence over local variable/parameter in scope + (Just (Var d), Just dt') | dt' `elem` [TLocalVar, TParameter] -> do + ct <- lookupName d + let qn = qualifyName d n + case ct of + Just TClass -> pure (Var qn) + Just TModule -> do + qdt <- lookupName qn + case qdt of + Just TFunction -> pure (Var qn) + Just TDataCon -> Con <$> resolveQualifiedConstructorName d n <*> pure [] + _ -> undefinedName qn + Just TTyCon -> do + qdt <- lookupName qn + case qdt of + Just TFunction -> pure (Var qn) + Just TDataCon -> Con <$> resolveQualifiedConstructorName d n <*> pure [] + Just TTyCon -> pure (Var qn) + Just TModule -> pure (Var qn) + _ -> undefinedName qn + _ -> pure (Var n) + -- field access + (Nothing, Just TField) -> + pure (FieldAccess Nothing n) + -- function reference + (_, Just TFunction) -> do + dt1 <- gets (Map.lookup n . fieldEnv) + case dt1 of + Just TField -> pure (FieldAccess Nothing n) + _ -> pure (Var n) + -- data constructor + (Nothing, Just TDataCon) -> do + if isPrimitiveConstructor n + then pure (Con n []) + else case splitQualifiedName n of + Just (qualifier, conName) -> + Con <$> resolveQualifiedConstructorName qualifier conName <*> pure [] + Nothing -> unqualifiedConstructorError n + (Just (Var d), Just TDataCon) -> + Con <$> resolveQualifiedConstructorName d n <*> pure [] + (Just (Var d), Just TTyCon) -> do + let qn = qualifyName d n + qdt <- lookupName qn + case qdt of + Just TFunction -> pure (Var qn) + Just TDataCon -> Con <$> resolveQualifiedConstructorName d n <*> pure [] + Just TTyCon -> pure (Var qn) + Just TModule -> pure (Var qn) + _ -> undefinedName n + -- class name + (_, Just TClass) -> pure (Var n) + -- type constructor used as a constructor qualifier + (Nothing, Just TTyCon) -> do + sameName <- isSameNameConstructor n + if sameName + then Con <$> resolveSameNameConstructorName n <*> pure [] + else Var <$> canonicalTypeName n + -- contract names can qualify their nested types. + (_, Just TContract) -> pure (Var n) + -- imported module qualifier name + (_, Just TModule) -> pure (Var n) + -- module-qualified function or constructor reference + (Just (Var d), Nothing) -> do + let qn = qualifyName d n + qdt <- lookupName qn + case qdt of + Just TFunction -> pure (Var qn) + Just TDataCon -> Con <$> resolveQualifiedConstructorName d n <*> pure [] + Just TTyCon -> pure (Var qn) + Just TModule -> pure (Var qn) + _ -> do + let fallback = qualifyName (constructorLeafName d) n + fdt <- lookupName fallback + case fdt of + Just TDataCon -> Con <$> resolveQualifiedConstructorName d n <*> pure [] + _ -> undefinedName n + _ -> do + sameName <- isSameNameConstructor n + if sameName + then Con <$> resolveSameNameConstructorName n <*> pure [] + else do + hasQualified <- hasQualifiedConstructorLeaf n + if hasQualified + then unqualifiedConstructorError n + else undefinedName n + instance Resolve S.Literal where type Result S.Literal = Literal @@ -986,14 +1266,25 @@ instance Resolve S.Pred where instance Resolve S.DataTy where type Result S.DataTy = DataTy - resolve d@(S.DataTy n vs cons) = + resolve d@(S.DataTyWithKind sourceKind n vs cons) = withLocalCtx $ do mapM_ addTyVar vs' cons' <- resolve cons `wrapError` d - pure (DataTy n (map TVar vs') (map (qualifyConstrName n) cons')) + resolvedName <- canonicalTypeName n + pure + ( DataTyWithKind + (resolveDataTyKind sourceKind) + resolvedName + (map TVar vs') + (map (qualifyConstrName resolvedName) cons') + ) where vs' = map tyconName vs +resolveDataTyKind :: S.DataTyKind -> DataTyKind +resolveDataTyKind S.EnumKind = EnumKind +resolveDataTyKind (S.StructKind fieldNames) = StructKind fieldNames + qualifyConstrName :: Name -> Constr -> Constr qualifyConstrName tyCon (Constr conName tys) = Constr (qualifiedConstructorName tyCon conName) tys @@ -1009,10 +1300,11 @@ instance Resolve S.TySym where resolve d@(S.TySym n ts t) = do let ts1 = map tyconName ts + resolvedName <- canonicalTypeName n t' <- withLocalCtx $ do mapM_ addTyVar ts1 resolve t `wrapError` d - pure (TySym n (map TVar ts1) t') + pure (TySym resolvedName (map TVar ts1) t') tyconName :: S.Ty -> Name tyconName (S.TyCon n _) = n @@ -1020,17 +1312,30 @@ tyconName (S.TyCon n _) = n instance Resolve S.Ty where type Result S.Ty = Ty - resolve functionTy@(S.FunctionTy args _visibility returns) = - locatedLike functionTy locatedTy <$> do - args' <- resolve args `wrapError` functionTy - returns' <- resolveFunctionReturns returns `wrapError` functionTy - pure (funtype args' returns') + -- Internal function values are represented by arrow types throughout type + -- inference and are later rewritten to Invokable constraints by + -- ReplaceFunTypeArgs. Validate the source-only distinctions before that + -- intentional lowering: otherwise an external function type is silently + -- treated as internal, while a nullary function collapses to its result. + resolve functionTy@(S.FunctionTy args visibility returns) = + case visibility of + Just S.FunctionTypeExternal -> + unsupportedExternalFunctionTypeError functionTy + _ + | null args -> + unsupportedNullaryFunctionTypeError functionTy + | otherwise -> + locatedLike functionTy locatedTy <$> do + args' <- resolve args `wrapError` functionTy + returns' <- resolveFunctionReturns returns `wrapError` functionTy + pure (funtype args' returns') resolve tc@(S.TyCon n ts) = locatedLike tc locatedTy <$> do ndt <- lookupType n case ndt of - Just TTyCon -> - TyCon n <$> resolveTypeArguments n ts `wrapError` tc + Just TTyCon -> do + resolvedName <- canonicalTypeName n + TyCon resolvedName <$> resolveTypeArguments n ts `wrapError` tc Just TTyVar -> pure (TyVar (TVar n)) _ -> undefinedTypeConstructor tc @@ -1089,45 +1394,55 @@ data Env fieldEnv :: Map Name DeclType, -- holds names under a specific scope: data constructors, functions -- variables and so on. - scopeEnv :: Map Name DeclType + scopeEnv :: Map Name DeclType, + -- maps source-visible type names to their collision-free semantic names. + -- Contract-local types are visible by their short name only while their + -- shell is being resolved, but are represented as Contract.Type. + canonicalTypeNames :: Map Name Name, + -- named-return value used when lowering a source-level bare return inside + -- the current function. Nested lambdas and constructors reset it. + functionBareReturnValue :: Maybe (Exp Name) } deriving (Show) emptyEnv :: Env emptyEnv = Env - ( Map.fromList - [ (Name "word", TTyCon), - (Name "bool", TTyCon), - (Name "integer", TTyCon), - (Name "()", TTyCon), - (Name "->", TTyCon), - (Name "pair", TTyCon), - (Name "sum", TTyCon) - ] - ) - (Map.fromList [(Name "invokable", TClass), (Name "Int", TClass)]) - Map.empty - ( Map.fromList - [ (Name "true", TDataCon), - (Name "false", TDataCon), - (Name "()", TDataCon), - (Name "pair", TDataCon), - (Name "inl", TDataCon), - (Name "inr", TDataCon), - (Name "invoke", TFunction), - (Name "primAddWord", TFunction), - (Name "primEqWord", TFunction), - (Name "wordToInteger", TFunction), - (Name "wordFromInteger", TFunction), - (Name "integerAdd", TFunction), - (Name "integerSub", TFunction), - (Name "integerMul", TFunction), - (Name "integerLt", TFunction), - (Name "integerEq", TFunction), - (QualName (Name "Int") "fromInteger", TFunction) - ] - ) + { typeEnv = + Map.fromList + [ (Name "word", TTyCon), + (Name "bool", TTyCon), + (Name "integer", TTyCon), + (Name "()", TTyCon), + (Name "->", TTyCon), + (Name "pair", TTyCon), + (Name "sum", TTyCon) + ], + classEnv = Map.fromList [(Name "invokable", TClass), (Name "Int", TClass)], + fieldEnv = Map.empty, + scopeEnv = + Map.fromList + [ (Name "true", TDataCon), + (Name "false", TDataCon), + (Name "()", TDataCon), + (Name "pair", TDataCon), + (Name "inl", TDataCon), + (Name "inr", TDataCon), + (Name "invoke", TFunction), + (Name "primAddWord", TFunction), + (Name "primEqWord", TFunction), + (Name "wordToInteger", TFunction), + (Name "wordFromInteger", TFunction), + (Name "integerAdd", TFunction), + (Name "integerSub", TFunction), + (Name "integerMul", TFunction), + (Name "integerLt", TFunction), + (Name "integerEq", TFunction), + (QualName (Name "Int") "fromInteger", TFunction) + ], + canonicalTypeNames = Map.empty, + functionBareReturnValue = Nothing + } globalEnv :: [S.TopDecl] -> Env globalEnv = foldr addTopDecl emptyEnv @@ -1160,9 +1475,13 @@ moduleLeafName (Name n) = Name n moduleLeafName (QualName _ n) = Name n addTopDecl :: S.TopDecl -> Env -> Env -addTopDecl (S.TContr (S.Contract n _ _)) env = - addQualifiedModules n $ - env {typeEnv = Map.insert n TContract (typeEnv env)} +addTopDecl (S.TContr (S.Contract n _ decls)) env = + foldr + (addNestedContractType n) + ( addQualifiedModules n $ + env {typeEnv = Map.insert n TContract (typeEnv env)} + ) + decls addTopDecl (S.TFunDef (S.FunDef _ sig _)) env = addQualifiedModules (S.sigName sig) $ env {scopeEnv = Map.insert (S.sigName sig) TFunction (scopeEnv env)} @@ -1184,6 +1503,7 @@ addTopDecl (S.TDataDef (S.DataTy n _ cons)) env = addQualifiedModules n $ env { typeEnv = Map.insert n TTyCon (typeEnv env), + canonicalTypeNames = Map.insert n n (canonicalTypeNames env), scopeEnv = foldr ( \d ac -> @@ -1194,10 +1514,34 @@ addTopDecl (S.TDataDef (S.DataTy n _ cons)) env = } addTopDecl (S.TSym (S.TySym n _ _)) env = addQualifiedModules n $ - env {typeEnv = Map.insert n TTyCon (typeEnv env)} + env + { typeEnv = Map.insert n TTyCon (typeEnv env), + canonicalTypeNames = Map.insert n n (canonicalTypeNames env) + } addTopDecl (S.TExportDecl _) env = env addTopDecl _ env = env +addNestedContractType :: Name -> S.ContractDecl -> Env -> Env +addNestedContractType contractName (S.CDataDecl (S.DataTy localName _ constructors)) env = + env + { typeEnv = Map.insert qualifiedTypeName TTyCon (typeEnv env), + canonicalTypeNames = + Map.insert qualifiedTypeName qualifiedTypeName (canonicalTypeNames env), + scopeEnv = + foldr + ( \constructor acc -> + Map.insert + (qualifiedConstructorName qualifiedTypeName (S.constrName constructor)) + TDataCon + acc + ) + (scopeEnv env) + constructors + } + where + qualifiedTypeName = qualifyName contractName localName +addNestedContractType _ _ env = env + addModuleName :: Name -> Env -> Env addModuleName n env = env {scopeEnv = Map.insertWith (\_ old -> old) n TModule (scopeEnv env)} @@ -1228,15 +1572,31 @@ withLocalCtx m = ( \env1 -> env1 { scopeEnv = scopeEnv env, - typeEnv = typeEnv env + typeEnv = typeEnv env, + canonicalTypeNames = canonicalTypeNames env } ) pure r +withBareReturnValue :: Maybe (Exp Name) -> ResolveM a -> ResolveM a +withBareReturnValue returnValue m = do + previous <- gets functionBareReturnValue + modify (\env -> env {functionBareReturnValue = returnValue}) + result <- m + modify (\env -> env {functionBareReturnValue = previous}) + pure result + lookupType :: Name -> ResolveM (Maybe DeclType) lookupType n = gets (Map.lookup n . typeEnv) +canonicalTypeName :: Name -> ResolveM Name +canonicalTypeName sourceName = do + resolvedName <- + gets + (Map.findWithDefault sourceName sourceName . canonicalTypeNames) + pure (copyNameSourceSpan sourceName resolvedName) + lookupClass :: Name -> ResolveM (Maybe DeclType) lookupClass n = gets (Map.lookup n . classEnv) @@ -1309,6 +1669,9 @@ contextLabelMessage diagnostic = Just (DiagnosticCode "SC0105") -> "undefined class" Just (DiagnosticCode "SC0106") -> "unqualified constructor" Just (DiagnosticCode "SC0107") -> "invalid pattern" + Just (DiagnosticCode "SC0122") -> "unsupported function type" + Just (DiagnosticCode "SC0123") -> "unsupported mixed return mode" + Just (DiagnosticCode "SC0124") -> "unsupported member call" _ -> "diagnostic reported here" contextSourceSpan :: (Data a) => a -> Maybe SourceSpan @@ -1349,8 +1712,25 @@ addClass n = modify (\env -> env {classEnv = Map.insert n TClass (classEnv env)}) addTyCon :: Name -> ResolveM () -addTyCon n = - modify (\env -> env {typeEnv = Map.insert n TTyCon (typeEnv env)}) +addTyCon n = addTyConAs n n + +addTyConAs :: Name -> Name -> ResolveM () +addTyConAs sourceName resolvedName = + modify + ( \env -> + env + { typeEnv = + Map.insert + sourceName + TTyCon + (Map.insert resolvedName TTyCon (typeEnv env)), + canonicalTypeNames = + Map.insert + sourceName + resolvedName + (Map.insert resolvedName resolvedName (canonicalTypeNames env)) + } + ) addDataCon :: Name -> Name -> ResolveM () addDataCon typeName conName = @@ -1369,7 +1749,8 @@ addTyVar n = resolveQualifiedConstructorName :: Name -> Name -> ResolveM Name resolveQualifiedConstructorName qualifier conName = do - let qn = qualifyName qualifier conName + resolvedQualifier <- canonicalTypeName qualifier + let qn = qualifiedConstructorName resolvedQualifier conName dt <- lookupName qn case dt of Just TDataCon -> pure qn @@ -1432,6 +1813,16 @@ undefinedName n = [] [] +unsupportedValueMemberCall :: Name -> ResolveM a +unsupportedValueMemberCall n = + diagnosticErrorAtName + "SC0124" + ("member calls on value receivers are not supported: " ++ pretty n) + n + "unsupported member call" + ["value-member dispatch has no runtime representation yet"] + ["use an explicit function call"] + unqualifiedConstructorError :: Name -> ResolveM a unqualifiedConstructorError n = diagnosticErrorAtName @@ -1450,6 +1841,42 @@ invalidPatternSyntax p = [] [] +unsupportedExternalFunctionTypeError :: S.Ty -> ResolveM a +unsupportedExternalFunctionTypeError functionTy = + unsupportedFunctionTypeError + functionTy + "external function types are not supported" + "external function values do not yet have a runtime representation" + [ "use an internal function type", + "or pass the external call target and selector explicitly" + ] + +unsupportedNullaryFunctionTypeError :: S.Ty -> ResolveM a +unsupportedNullaryFunctionTypeError functionTy = + unsupportedFunctionTypeError + functionTy + "zero-parameter function types are not supported" + "lowering a nullary function to its result type would change its meaning" + ["use an explicit unit parameter: function(()) internal returns (...)"] + +unsupportedFunctionTypeError :: S.Ty -> String -> String -> [String] -> ResolveM a +unsupportedFunctionTypeError functionTy message label help = + diagnosticErrorWithLabels + "SC0122" + message + ( case sourceSpanOf functionTy of + Nothing -> [] + Just sourceSpan -> + [ Label + { labelSpan = sourceSpan, + labelStyle = Primary, + labelMessage = Just label + } + ] + ) + [] + help + diagnosticError :: String -> String -> [String] -> [String] -> ResolveM a diagnosticError code message notes help = diagnosticErrorWithLabels code message [] notes help diff --git a/src/Solcore/Frontend/Syntax/SyntaxTree.hs b/src/Solcore/Frontend/Syntax/SyntaxTree.hs index 6a4df53d1..970c63ecb 100644 --- a/src/Solcore/Frontend/Syntax/SyntaxTree.hs +++ b/src/Solcore/Frontend/Syntax/SyntaxTree.hs @@ -136,6 +136,7 @@ pattern ContractShell :: ContractKind -> Name -> [Ty] -> [ContractDecl] -> Contr pattern ContractShell k n ts ds = ContractWithKind k n ts ds {-# COMPLETE Contract #-} + {-# COMPLETE ContractShell #-} -- definition of a algebraic data type @@ -159,8 +160,8 @@ pattern DataTy n ts cs <- DataTyWithKind _ n ts cs where DataTy n ts cs = DataTyWithKind EnumKind n ts cs --- Struct fields remain named in the source AST. Name resolution deliberately --- lowers this shape to a one-constructor algebraic data type. +-- Structs use the existing one-constructor runtime representation, while name +-- resolution retains this kind and ordered field metadata in the semantic AST. pattern StructTy :: Name -> [Ty] -> [Name] -> [Ty] -> DataTy pattern StructTy n ts fieldNames fieldTypes <- DataTyWithKind (StructKind fieldNames) n ts [Constr _ fieldTypes] @@ -397,7 +398,7 @@ legacyReturnItems _ Nothing = Nothing legacyReturnItems returnComptime (Just returnTy) = Just [ ReturnItem returnComptime Nothing itemTy - | itemTy <- legacyTupleElements returnTy + | itemTy <- legacyTupleElements returnTy ] legacyReturnView :: Maybe [ReturnItem] -> (Bool, Maybe Ty) @@ -613,6 +614,7 @@ data Stmt | BlockWithLocation NodeLocation Body -- lexical block | StmtExpWithLocation NodeLocation Exp -- expression level statements | ReturnWithLocation NodeLocation Exp -- return statements + | BareReturnWithLocation NodeLocation -- return statement with no expression | MatchWithLocation NodeLocation [Exp] Equations -- pattern matching | AsmWithLocation NodeLocation YulBlock -- Yul block | IfWithLocation NodeLocation Exp Body Body -- If statement @@ -621,6 +623,7 @@ data Stmt | ForWithLocation NodeLocation Stmt Exp Stmt Body -- for(init; cond; post) { body } | BreakWithLocation NodeLocation -- break out of the innermost enclosing for loop | ContinueWithLocation NodeLocation -- continue to the next iteration of the innermost enclosing for loop + | RevertWithLocation NodeLocation -- abort execution with empty return data | EmptyStmtWithLocation NodeLocation -- empty statement (for empty for init/post) deriving (Eq, Ord, Show, Data, Typeable) @@ -684,6 +687,11 @@ pattern Return exp <- ReturnWithLocation _ exp where Return exp = ReturnWithLocation unlocatedNode exp +pattern BareReturn :: Stmt +pattern BareReturn <- BareReturnWithLocation _ + where + BareReturn = BareReturnWithLocation unlocatedNode + pattern Match :: [Exp] -> Equations -> Stmt pattern Match exps equations <- MatchWithLocation _ exps equations where @@ -724,12 +732,17 @@ pattern Continue <- ContinueWithLocation _ where Continue = ContinueWithLocation unlocatedNode +pattern Revert :: Stmt +pattern Revert <- RevertWithLocation _ + where + Revert = RevertWithLocation unlocatedNode + pattern EmptyStmt :: Stmt pattern EmptyStmt <- EmptyStmtWithLocation _ where EmptyStmt = EmptyStmtWithLocation unlocatedNode -{-# COMPLETE Assign, StmtPlusEq, StmtMinusEq, StmtBXorEq, StmtBAndEq, StmtBOrEq, StmtModEq, Let, LetPattern, Block, StmtExp, Return, Match, Asm, If, While, Unchecked, For, Break, Continue, EmptyStmt #-} +{-# COMPLETE Assign, StmtPlusEq, StmtMinusEq, StmtBXorEq, StmtBAndEq, StmtBOrEq, StmtModEq, Let, LetPattern, Block, StmtExp, Return, BareReturn, Match, Asm, If, While, Unchecked, For, Break, Continue, Revert, EmptyStmt #-} type Body = [Stmt] @@ -763,6 +776,7 @@ locatedStmt sourceSpan (LetPattern ct pat ty value) = locatedStmt sourceSpan (Block body) = BlockWithLocation (locatedNode sourceSpan) body locatedStmt sourceSpan (StmtExp exp) = StmtExpWithLocation (locatedNode sourceSpan) exp locatedStmt sourceSpan (Return exp) = ReturnWithLocation (locatedNode sourceSpan) exp +locatedStmt sourceSpan BareReturn = BareReturnWithLocation (locatedNode sourceSpan) locatedStmt sourceSpan (Match exps equations) = MatchWithLocation (locatedNode sourceSpan) exps equations locatedStmt sourceSpan (Asm block) = AsmWithLocation (locatedNode sourceSpan) block locatedStmt sourceSpan (If cond thenBody elseBody) = IfWithLocation (locatedNode sourceSpan) cond thenBody elseBody @@ -771,6 +785,7 @@ locatedStmt sourceSpan (Unchecked body) = UncheckedWithLocation (locatedNode sou locatedStmt sourceSpan (For initStmt cond postStmt body) = ForWithLocation (locatedNode sourceSpan) initStmt cond postStmt body locatedStmt sourceSpan Break = BreakWithLocation (locatedNode sourceSpan) locatedStmt sourceSpan Continue = ContinueWithLocation (locatedNode sourceSpan) +locatedStmt sourceSpan Revert = RevertWithLocation (locatedNode sourceSpan) locatedStmt sourceSpan EmptyStmt = EmptyStmtWithLocation (locatedNode sourceSpan) instance HasSourceSpan Stmt where @@ -798,6 +813,8 @@ instance HasSourceSpan Stmt where firstSourceSpan [sourceSpanOf location, sourceSpanOf exp] sourceSpanOf (ReturnWithLocation location exp) = firstSourceSpan [sourceSpanOf location, sourceSpanOf exp] + sourceSpanOf (BareReturnWithLocation location) = + sourceSpanOf location sourceSpanOf (MatchWithLocation location exps equations) = firstSourceSpan [sourceSpanOf location, sourceSpanOf exps, sourceSpanOf equations] sourceSpanOf (AsmWithLocation location _) = @@ -814,6 +831,8 @@ instance HasSourceSpan Stmt where sourceSpanOf location sourceSpanOf (ContinueWithLocation location) = sourceSpanOf location + sourceSpanOf (RevertWithLocation location) = + sourceSpanOf location sourceSpanOf (EmptyStmtWithLocation location) = sourceSpanOf location @@ -833,6 +852,7 @@ instance HasSourceSpan Param where data Exp = LitWithLocation NodeLocation Literal -- literal | ExpNameWithLocation NodeLocation (Maybe Exp) Name [Exp] -- function call or constructor + | ExpApplyWithLocation NodeLocation Exp [Exp] -- arbitrary postfix function application | ExpVarWithLocation NodeLocation (Maybe Exp) Name -- variables or field access | ExpDotNameWithLocation NodeLocation Name [Exp] -- contextual constructor shorthand, e.g. .Some(1), .None | LamWithLocation NodeLocation [Param] Body (Maybe Ty) -- lambda-abstraction @@ -872,6 +892,11 @@ pattern ExpName me n es <- ExpNameWithLocation _ me n es where ExpName me n es = ExpNameWithLocation unlocatedNode me n es +pattern ExpApply :: Exp -> [Exp] -> Exp +pattern ExpApply callee args <- ExpApplyWithLocation _ callee args + where + ExpApply callee args = ExpApplyWithLocation unlocatedNode callee args + pattern ExpVar :: Maybe Exp -> Name -> Exp pattern ExpVar me n <- ExpVarWithLocation _ me n where @@ -1007,13 +1032,14 @@ pattern ExpAt ty <- ExpAtWithLocation _ ty where ExpAt ty = ExpAtWithLocation unlocatedNode ty -{-# COMPLETE Lit, ExpName, ExpVar, ExpDotName, Lam, TyExp, ExpIndexed, ExpPlus, ExpMinus, ExpPower, ExpTimes, ExpDivide, ExpModulo, ExpShiftL, ExpShiftR, ExpBXor, ExpBAnd, ExpBOr, ExpLT, ExpGT, ExpLE, ExpGE, ExpEE, ExpNE, ExpLAnd, ExpLOr, ExpLNot, ExpCond, ExpAt #-} +{-# COMPLETE Lit, ExpName, ExpApply, ExpVar, ExpDotName, Lam, TyExp, ExpIndexed, ExpPlus, ExpMinus, ExpPower, ExpTimes, ExpDivide, ExpModulo, ExpShiftL, ExpShiftR, ExpBXor, ExpBAnd, ExpBOr, ExpLT, ExpGT, ExpLE, ExpGE, ExpEE, ExpNE, ExpLAnd, ExpLOr, ExpLNot, ExpCond, ExpAt #-} locatedExp :: SourceSpan -> Exp -> Exp locatedExp sourceSpan (Lit lit) = LitWithLocation location lit where location = locatedNode sourceSpan locatedExp sourceSpan (ExpName me n es) = ExpNameWithLocation (locatedNode sourceSpan) me n es +locatedExp sourceSpan (ExpApply callee args) = ExpApplyWithLocation (locatedNode sourceSpan) callee args locatedExp sourceSpan (ExpVar me n) = ExpVarWithLocation (locatedNode sourceSpan) me n locatedExp sourceSpan (ExpDotName n es) = ExpDotNameWithLocation (locatedNode sourceSpan) n es locatedExp sourceSpan (Lam ps body ty) = LamWithLocation (locatedNode sourceSpan) ps body ty @@ -1046,6 +1072,8 @@ instance HasSourceSpan Exp where sourceSpanOf (LitWithLocation location _) = sourceSpanOf location sourceSpanOf (ExpNameWithLocation location me n es) = firstSourceSpan [sourceSpanOf location, sourceSpanOf me, sourceSpanOf n, sourceSpanOf es] + sourceSpanOf (ExpApplyWithLocation location callee args) = + firstSourceSpan [sourceSpanOf location, sourceSpanOf callee, sourceSpanOf args] sourceSpanOf (ExpVarWithLocation location me n) = firstSourceSpan [sourceSpanOf location, sourceSpanOf me, sourceSpanOf n] sourceSpanOf (ExpDotNameWithLocation location n es) = diff --git a/src/Solcore/Frontend/TypeInference/Erase.hs b/src/Solcore/Frontend/TypeInference/Erase.hs index f1d9f2a24..ac9771c36 100644 --- a/src/Solcore/Frontend/TypeInference/Erase.hs +++ b/src/Solcore/Frontend/TypeInference/Erase.hs @@ -37,8 +37,12 @@ instance Erase (FunDef Id) where instance Erase (Signature Id) where type EraseRes (Signature Id) = Signature Name - erase (Signature n ps t args rc rt pay) = - Signature n ps t (erase args) rc rt pay + erase sig@(Signature n ps t args rc rt pay) = + (Signature n ps t (erase args) rc rt pay) + { sigReturnNames = sigReturnNames sig, + sigReturnItems = sigReturnItems sig, + sigModifiers = sigModifiers sig + } instance Erase (Stmt Id) where type EraseRes (Stmt Id) = Stmt Name diff --git a/src/Solcore/Frontend/TypeInference/SccAnalysis.hs b/src/Solcore/Frontend/TypeInference/SccAnalysis.hs index e7eb023e2..cab8b3a2a 100644 --- a/src/Solcore/Frontend/TypeInference/SccAnalysis.hs +++ b/src/Solcore/Frontend/TypeInference/SccAnalysis.hs @@ -56,8 +56,8 @@ sccTopDecls ds = -- sort inner contract definitions sccContract :: TopDecl Name -> SCC (TopDecl Name) -sccContract (TContr (Contract n vs ds)) = - (TContr . Contract n vs) <$> analysis ds +sccContract (TContr (ContractWithKind kind n vs ds)) = + (TContr . ContractWithKind kind n vs) <$> analysis ds sccContract d = pure d analysis :: (Ord a, Names a, Decl a, Show a, Groupable a) => [a] -> SCC [a] @@ -127,7 +127,7 @@ instance Decl (FunDef Name) where decl (FunDef _ sig _) = decl sig instance Decl (Contract Name) where - decl (Contract n _ ds) = n : concatMap decl ds + decl (ContractWithKind _ n _ ds) = n : concatMap decl ds instance Decl (Field Name) where decl d = [fieldName d] @@ -273,7 +273,7 @@ instance Names (ContractDecl Name) where names (CConstrDecl cd) = names cd instance Names (Contract Name) where - names (Contract _ _ contractDecls) = + names (ContractWithKind _ _ _ contractDecls) = names contractDecls instance Names (TopDecl Name) where diff --git a/src/Solcore/Frontend/TypeInference/TcContract.hs b/src/Solcore/Frontend/TypeInference/TcContract.hs index 488dd4d94..5436c7334 100644 --- a/src/Solcore/Frontend/TypeInference/TcContract.hs +++ b/src/Solcore/Frontend/TypeInference/TcContract.hs @@ -255,6 +255,8 @@ checkTopDecl (TInstDef is) = checkInstance is checkTopDecl (TDataDef dt) = checkDataType dt +checkTopDecl (TContr (ContractWithKind _ _ _ contractDecls)) = + mapM_ checkContractDataDecl contractDecls checkTopDecl (TSym s) = checkSynonym s checkTopDecl (TFunDef (FunDef _ sig _)) = @@ -262,17 +264,29 @@ checkTopDecl (TFunDef (FunDef _ sig _)) = checkTopDecl (TExportDecl _) = pure () checkTopDecl _ = pure () +-- Contract-local data types have collision-free semantic names +-- (Contract.Type), so keep them in the module type/constructor environment. +-- Auto-derived Generic/Storage/ABI instances are top-level declarations and +-- may be checked before the contract body that owns their type. +checkContractDataDecl :: ContractDecl Name -> TcM () +checkContractDataDecl (CDataDecl dt) = + checkDataType dt +checkContractDataDecl (CMutualDecl contractDecls) = + mapM_ checkContractDataDecl contractDecls +checkContractDataDecl _ = + pure () + -- type inference for contracts tcContract :: Contract Name -> TcM (Contract Id, [(Name, Scheme)]) -tcContract c@(Contract n vs cdecls) = +tcContract c@(ContractWithKind kind n vs cdecls) = withLocalEnv $ withContractName n $ do ctx' <- gets ctx initializeEnv c decls' <- mapM tcDecl' cdecls ctx1 <- gets ctx let ctx2 = Map.toList $ Map.difference ctx1 ctx' - pure (Contract n vs decls', ctx2) + pure (ContractWithKind kind n vs decls', ctx2) where tcDecl' d = do @@ -284,7 +298,7 @@ tcContract c@(Contract n vs cdecls) = -- initializing context for a contract initializeEnv :: Contract Name -> TcM () -initializeEnv (Contract _ _ cdecls) = do +initializeEnv (ContractWithKind _ _ _ cdecls) = do mapM_ checkDecl cdecls -- Pre-register annotated function signatures in ctx so that forward references -- (e.g. the dispatch-generated 'main') can resolve user-defined functions @@ -302,8 +316,11 @@ initializeEnv (Contract _ _ cdecls) = do mapM_ (uncurry extEnv) (nmschs ++ signatureSchemes) checkDecl :: ContractDecl Name -> TcM () -checkDecl (CDataDecl dt) = - checkDataType dt +-- Nested data types are registered by the module-wide checkTopDecl pre-pass so +-- generated top-level instances can refer to them. Re-registering here would +-- report the owning declaration as a duplicate. +checkDecl (CDataDecl _) = + pure () checkDecl (CFunDecl (FunDef _ sig _)) = extSignature sig checkDecl (CSignatureDecl _ sig) = @@ -347,19 +364,29 @@ tcContractSignature sig@(Signature vars predicates n params retComptime returnTy checkConstraints predicates `wrapError` sig params' <- mapM tcSignatureParam params returnTy' <- traverse kindCheck returnTy `wrapError` sig - pure (Signature vars predicates n params' retComptime returnTy' payable) + returnItems' <- mapM tcSignatureReturnItem (sigReturnItems sig) `wrapError` sig + pure + ( (Signature vars predicates n params' retComptime returnTy' payable) + { sigReturnNames = sigReturnNames sig, + sigReturnItems = returnItems', + sigModifiers = sigModifiers sig + } + ) where tcSignatureParam p@(Typed comptime paramName' ty) = do ty' <- kindCheck ty `wrapError` p pure (Typed comptime (Id paramName' ty') ty') tcSignatureParam (Untyped _ _) = tcmError "Interface function parameters must have type annotations" + tcSignatureReturnItem returnItem = do + returnItemTy <- kindCheck (signatureReturnItemType returnItem) + pure returnItem {signatureReturnItemType = returnItemTy} -- kind check data declarations tcDataDecl :: DataTy -> TcM DataTy -tcDataDecl (DataTy n vs cs) = - DataTy n vs <$> mapM tcConstr cs +tcDataDecl (DataTyWithKind kind n vs cs) = + DataTyWithKind kind n vs <$> mapM tcConstr cs tcConstr :: Constr -> TcM Constr tcConstr (Constr n ts) = @@ -399,20 +426,30 @@ tcSig :: (Signature Name, Scheme) -> TcM (Signature Id) tcSig (sig, (Forall _ (_ :=> t))) = do t1 <- kindCheck t `wrapError` sig + returnItems' <- mapM tcSignatureReturnItem (sigReturnItems sig) `wrapError` sig let (ts, r) = splitTy t1 param (Typed c n _) t2 = Typed c (Id n t2) t2 param (Untyped c n) t2 = Typed c (Id n t2) t2 params' = zipWith param (sigParams sig) ts pure - ( Signature - (sigVars sig) - (sigContext sig) - (sigName sig) - params' - (sigRetComptime sig) - (Just r) - (sigPayable sig) + ( ( Signature + (sigVars sig) + (sigContext sig) + (sigName sig) + params' + (sigRetComptime sig) + (Just r) + (sigPayable sig) + ) + { sigReturnNames = sigReturnNames sig, + sigReturnItems = returnItems', + sigModifiers = sigModifiers sig + } ) + where + tcSignatureReturnItem returnItem = do + returnItemTy <- kindCheck (signatureReturnItemType returnItem) + pure returnItem {signatureReturnItemType = returnItemTy} -- type checking binding groups diff --git a/src/Solcore/Frontend/TypeInference/TcEnv.hs b/src/Solcore/Frontend/TypeInference/TcEnv.hs index 6b6d3531b..698f5cc7e 100644 --- a/src/Solcore/Frontend/TypeInference/TcEnv.hs +++ b/src/Solcore/Frontend/TypeInference/TcEnv.hs @@ -24,6 +24,17 @@ data TypeInfo } deriving (Eq, Ord, Show) +-- Ordered source-struct metadata used to type and lower value member reads. +-- The field types remain parameterised by 'structParams' until a concrete +-- receiver type supplies their arguments. +data StructInfo + = StructInfo + { structParams :: [Tyvar], + structConstructor :: Maybe Name, + structFields :: [(Name, Ty)] + } + deriving (Eq, Show) + -- type synonym information data SynInfo = SynInfo @@ -78,6 +89,8 @@ type ClassTable = Table ClassInfo type TypeTable = Table TypeInfo +type StructTable = Table StructInfo + type SynTable = Table SynInfo type Inst = Qual Pred @@ -95,6 +108,7 @@ data TcEnv instEnv :: InstTable, -- Instance Environment defaultEnv :: DefTable, -- Default instance environment typeTable :: TypeTable, -- Type information environment + structTable :: StructTable, -- Ordered source-struct metadata synTable :: SynTable, -- Type synonym environment classTable :: ClassTable, -- Class information table contract :: Maybe Name, -- current contract name @@ -128,6 +142,7 @@ initTcEnv opts = instEnv = primInstEnv, defaultEnv = Map.empty, typeTable = primTypeEnv, + structTable = Map.empty, synTable = Map.empty, classTable = primClassEnv, contract = Nothing, diff --git a/src/Solcore/Frontend/TypeInference/TcModule.hs b/src/Solcore/Frontend/TypeInference/TcModule.hs index 0f4d053b7..dddffbbdf 100644 --- a/src/Solcore/Frontend/TypeInference/TcModule.hs +++ b/src/Solcore/Frontend/TypeInference/TcModule.hs @@ -28,6 +28,7 @@ module Solcore.Frontend.TypeInference.TcModule ) where +import Data.Generics (everywhere, mkT) import Data.Map (Map) import Data.Map qualified as Map import Data.Set qualified as Set @@ -324,30 +325,64 @@ assembleCheckedModules graph checkedModules = do assemblyDecls :: [CheckedModule] -> [TopDecl Id] -> [TopDecl Id] assemblyDecls orderedModules extraDecls = - moduleDecls ++ dedupeNewFunctionDecls moduleFunctionNames extraDecls + moduleDecls + ++ missingImportedDataDecls moduleDataNames orderedModules + ++ dedupeNewFunctionDecls moduleFunctionKeys extraDecls where moduleDecls = concatMap (contracts . checkedModuleTyped) orderedModules - moduleFunctionNames = concatMap topDeclFunctionNames moduleDecls + moduleFunctionKeys = concatMap topDeclFunctionKeys moduleDecls + moduleDataNames = + Set.fromList + [ dataName dataTy + | TDataDef dataTy <- moduleDecls + ] + +-- A selectively renamed imported data type is nominally visible under its +-- local name while typechecking the consumer module. Its local declaration is +-- trusted rather than emitted by that module, so the assembled program would +-- otherwise contain selector functions whose patterns mention a constructor +-- that the match compiler cannot see. Keep exactly those additional nominal +-- data declarations whose names are not already supplied by a loaded module. +missingImportedDataDecls :: Set.Set Name -> [CheckedModule] -> [TopDecl Id] +missingImportedDataDecls initialNames = + snd . foldl addModule (initialNames, []) + where + addModule (seen, accumulated) checkedModule = + foldl addDecl (seen, accumulated) candidates + where + candidates = + [ dataTy + | ModuleInferenceDecl segment (TDataDef dataTy) <- + moduleInferenceDecls (checkedModuleInput checkedModule), + segment == ModuleImportedDecl + ] + addDecl (seen, accumulated) dataTy + | dataName dataTy `Set.member` seen = + (seen, accumulated) + | otherwise = + ( Set.insert (dataName dataTy) seen, + accumulated ++ [TDataDef dataTy] + ) -dedupeNewFunctionDecls :: [Name] -> [TopDecl Id] -> [TopDecl Id] -dedupeNewFunctionDecls existingNames = - go (Set.fromList existingNames) +dedupeNewFunctionDecls :: [(Name, Ty)] -> [TopDecl Id] -> [TopDecl Id] +dedupeNewFunctionDecls existingKeys = + go (Set.fromList existingKeys) where go _ [] = [] go seen (decl : rest) - | any (`Set.member` seen) names = + | any (`Set.member` seen) keys = go seen rest | otherwise = - decl : go (foldr Set.insert seen names) rest + decl : go (foldr Set.insert seen keys) rest where - names = topDeclFunctionNames decl - -topDeclFunctionNames :: TopDecl Id -> [Name] -topDeclFunctionNames (TFunDef fd) = - [sigName (funSignature fd)] -topDeclFunctionNames (TMutualDef mutualDecls) = - concatMap topDeclFunctionNames mutualDecls -topDeclFunctionNames _ = + keys = topDeclFunctionKeys decl + +topDeclFunctionKeys :: TopDecl Id -> [(Name, Ty)] +topDeclFunctionKeys (TFunDef fd) = + [(sigName (funSignature fd), typedSignatureType (funSignature fd))] +topDeclFunctionKeys (TMutualDef mutualDecls) = + concatMap topDeclFunctionKeys mutualDecls +topDeclFunctionKeys _ = [] mergeCheckedModuleEnvs :: CheckedModule -> [CheckedModule] -> TcEnv @@ -371,27 +406,41 @@ importForwardingWrappers graph checkedModules = wrappersForQualifiers loadedModule importPath (defaultImportQualifiers importPath) wrappersForImport loadedModule (Parsed.ImportAlias importPath qualifier) = wrappersForQualifiers loadedModule importPath [qualifier] - wrappersForImport loadedModule (Parsed.ImportOnly importPath (Parsed.SelectItems items _)) = - let aliases = [(src, alias) | Parsed.SelectItemAs src alias <- items] - in if null aliases - then pure [] - else do - targetModuleId <- - maybe - (Left ("Internal error: import target was not loaded: " ++ Mod.modulePathDisplay importPath)) - Right - (Map.lookup importPath (loadedModuleRefs loadedModule)) - targetModule <- - maybe - (Left ("Internal error: import target was not typechecked: " ++ Mod.moduleIdDisplay targetModuleId)) - Right - (Map.lookup targetModuleId checkedModules) - pure - [ TFunDef (typedAliasingWrapper aliasName fd) - | (sourceName, aliasName) <- aliases, - TFunDef fd <- contracts (checkedModuleTyped targetModule), - sigName (funSignature fd) == sourceName - ] + wrappersForImport loadedModule (Parsed.ImportOnly importPath selector) = do + targetModuleId <- + maybe + (Left ("Internal error: import target was not loaded: " ++ Mod.modulePathDisplay importPath)) + Right + (Map.lookup importPath (loadedModuleRefs loadedModule)) + targetModule <- + maybe + (Left ("Internal error: import target was not typechecked: " ++ Mod.moduleIdDisplay targetModuleId)) + Right + (Map.lookup targetModuleId checkedModules) + bindings <- selectedImportBindingsForModule graph targetModuleId selector + let targetDecls = contracts (checkedModuleTyped targetModule) + targetDataNames = + Set.fromList + [ dataName dataTy + | TDataDef dataTy <- targetDecls + ] + typeAliases = + Map.fromList + [ (sourceName, aliasName) + | (sourceName, aliasName) <- bindings, + sourceName /= aliasName, + sourceName `Set.member` targetDataNames + ] + pure + [ TFunDef (typedImportWrapper typeAliases aliasName fd) + | (sourceName, aliasName) <- bindings, + TFunDef fd <- targetDecls, + sigName (funSignature fd) == sourceName, + sourceName /= aliasName + || typedSignatureType + (renameSignatureTypes typeAliases (funSignature fd)) + /= typedSignatureType (funSignature fd) + ] wrappersForQualifiers loadedModule importPath qualifiers = do targetModuleId <- @@ -441,9 +490,22 @@ typedForwardingWrapper qualifier (FunDef isPub sig body) targetId = Id originalName (typedSignatureType sig) args = map (Var . paramName) (sigParams sig) -typedAliasingWrapper :: Name -> FunDef Id -> FunDef Id -typedAliasingWrapper aliasName (FunDef isPub sig body) = - FunDef isPub (sig {sigName = aliasName}) body +typedImportWrapper :: Map Name Name -> Name -> FunDef Id -> FunDef Id +typedImportWrapper typeAliases aliasName (FunDef isPub sig body) = + FunDef + isPub + ((renameSignatureTypes typeAliases sig) {sigName = aliasName}) + body + +renameSignatureTypes :: Map Name Name -> Signature Id -> Signature Id +renameSignatureTypes typeAliases = + everywhere (mkT renameTy) + where + renameTy (TyCon typeName typeArgs) = + TyCon + (Map.findWithDefault typeName typeName typeAliases) + typeArgs + renameTy ty = ty typedSignatureType :: Signature Id -> Ty typedSignatureType sig = diff --git a/src/Solcore/Frontend/TypeInference/TcMonad.hs b/src/Solcore/Frontend/TypeInference/TcMonad.hs index 435a9e216..6c452de52 100644 --- a/src/Solcore/Frontend/TypeInference/TcMonad.hs +++ b/src/Solcore/Frontend/TypeInference/TcMonad.hs @@ -125,8 +125,12 @@ withPartialDataTypesDisabled action = do pure result typeInfoFor :: DataTy -> TypeInfo -typeInfoFor (DataTy _ vs cons) = - TypeInfo (length vs) (map constrName cons) [] +typeInfoFor (DataTyWithKind kind _ vs cons) = + TypeInfo (length vs) (map constrName cons) (dataTyFieldNames kind) + +dataTyFieldNames :: DataTyKind -> [Name] +dataTyFieldNames EnumKind = [] +dataTyFieldNames (StructKind names) = names freshTyVar :: TcM Ty freshTyVar = Meta <$> freshVar @@ -200,7 +204,7 @@ isDirectCall n = -- including contructors on environment checkDataType :: DataTy -> TcM () -checkDataType d@(DataTy n vs constrs) = +checkDataType d@(DataTyWithKind kind n vs constrs) = do -- check if the type is already defined. r <- maybeAskTypeInfo n @@ -211,12 +215,111 @@ checkDataType d@(DataTy n vs constrs) = modifyTypeInfo n ti -- checking kinds mapM_ kindCheck (concatMap constrTy constrs) `wrapError` d + registerStructType d where - ti = TypeInfo (length vs) (map fst vals) [] + ti = TypeInfo (length vs) (map fst vals) (dataTyFieldNames kind) tc = TyCon n (TyVar <$> vs) vals = map constrBind constrs constrBind c = (constrName c, (funtype (constrTy c) tc)) +registerStructType :: DataTy -> TcM () +registerStructType + ( DataTyWithKind + (StructKind memberNames) + typeName + typeParams + [Constr constructorName memberTypes] + ) + | length memberNames == length memberTypes = do + let structInfo = + StructInfo + { structParams = typeParams, + structConstructor = Just constructorName, + structFields = zip memberNames memberTypes + } + modify + ( \env -> + env + { structTable = + Map.insert typeName structInfo (structTable env) + } + ) + mapM_ + (registerStructFieldSelector typeName structInfo) + (zip [0 ..] (structFields structInfo)) +registerStructType + (DataTyWithKind (StructKind _) typeName typeParams []) = + modify + ( \env -> + env + { structTable = + Map.insert + typeName + (StructInfo typeParams Nothing []) + (structTable env) + } + ) +registerStructType d@(DataTyWithKind (StructKind _) _ _ _) = + tcmError ("malformed semantic struct declaration: " ++ pretty d) +registerStructType _ = pure () + +registerStructFieldSelector :: + Name -> + StructInfo -> + (Int, (Name, Ty)) -> + TcM () +registerStructFieldSelector typeName structInfo (selectedIndex, (memberName, memberTy)) = + case structConstructor structInfo of + Nothing -> + tcmError + ("cannot generate a field selector for opaque struct " ++ pretty typeName) + Just constructorName -> + registerSelector constructorName + where + registerSelector constructorName = do + let params = structParams structInfo + structTy = TyCon typeName (map TyVar params) + selectorName = structFieldSelectorName typeName memberName + selectorTy = funtype [structTy] memberTy + receiverId = Id (Name "$struct") structTy + memberIds = + [ Id (Name ("$field" ++ show index)) memberFieldTy + | (index, (_, memberFieldTy)) <- zip [0 :: Int ..] (structFields structInfo) + ] + constructorTy = + funtype (map idType memberIds) structTy + constructorId = Id constructorName constructorTy + selectedId = + Id + (Name ("$field" ++ show selectedIndex)) + memberTy + signature = + Signature + params + [] + selectorName + [Typed False receiverId structTy] + False + (Just memberTy) + False + body = + [ Match + [Var receiverId] + [ ( [PCon constructorId (map PVar memberIds)], + [Return (Var selectedId)] + ) + ] + ] + selector = FunDef False signature body + scheme = Forall params ([] :=> selectorTy) + extEnv selectorName scheme + addFunctionName selectorName + writeFunDef selector + +structFieldSelectorName :: Name -> Name -> Name +structFieldSelectorName typeName memberName = + QualName typeName ("$structField$" ++ pretty memberName) + -- kind check kindCheck :: Ty -> TcM Ty @@ -397,16 +500,37 @@ askCurrentContract = pure n --- manipulating contract field information - -askField :: Name -> Name -> TcM Scheme -askField cn fn = - do - ti <- askTypeInfo cn - when - (fn `notElem` fieldNames ti) - (undefinedField cn fn) - askEnv fn +-- Looking up and instantiating source-struct fields. + +askStructField :: Ty -> Name -> TcM (Name, Ty) +askStructField (TyCon typeName typeArgs) memberName = do + minfo <- gets (Map.lookup typeName . structTable) + case minfo of + Nothing -> undefinedField typeName memberName + Just StructInfo {structConstructor = Nothing} -> + inaccessibleStructField typeName memberName + Just structInfo -> + case lookup memberName (structFields structInfo) of + Nothing -> undefinedField typeName memberName + Just memberTy -> do + unless (length typeArgs == length (structParams structInfo)) $ + tcmError + ( "malformed struct type application: " + ++ pretty (TyCon typeName typeArgs) + ) + let instantiatedTy = + insts (zip (structParams structInfo) typeArgs) memberTy + pure + ( structFieldSelectorName typeName memberName, + instantiatedTy + ) +askStructField receiverTy memberName = + tcmError + ( "field access requires a concrete struct receiver, but found " + ++ pretty receiverTy + ++ "." + ++ pretty memberName + ) -- manipulating data constructor information @@ -766,6 +890,8 @@ contextLabelMessage diagnostic = Just (DiagnosticCode "SC0227") -> "duplicate class" Just (DiagnosticCode "SC0228") -> "duplicate class method" Just (DiagnosticCode "SC0229") -> "duplicate type" + Just (DiagnosticCode "SC0231") -> "unsupported struct field assignment" + Just (DiagnosticCode "SC0232") -> "inaccessible struct field" _ -> "diagnostic reported here" contextSourceSpan :: (Data a) => a -> Maybe SourceSpan @@ -830,15 +956,35 @@ undefinedType n = [] undefinedField :: Name -> Name -> TcM a -undefinedField n n' = +undefinedField typeName memberName = tcDiagnosticErrorAtName "SC0204" - ("undefined field: " ++ pretty n) - n + ("undefined field: " ++ pretty memberName) + memberName "undefined field" - ["in type: " ++ pretty n'] + ["in type: " ++ pretty typeName] [] +inaccessibleStructField :: Name -> Name -> TcM a +inaccessibleStructField typeName memberName = + tcDiagnosticErrorAtName + "SC0232" + ("struct field is not visible: " ++ pretty memberName) + memberName + "inaccessible struct field" + ["type " ++ pretty typeName ++ " was imported without its constructor"] + ["export the struct with its constructor to make its fields readable"] + +unsupportedStructFieldAssignment :: Name -> TcM a +unsupportedStructFieldAssignment memberName = + tcDiagnosticErrorAtName + "SC0231" + ("struct field assignment is not implemented: " ++ pretty memberName) + memberName + "unsupported struct field assignment" + ["struct values are immutable in the current lowering"] + ["construct an updated struct value and assign it to the whole variable"] + undefinedConstr :: Name -> Name -> TcM a undefinedConstr tn cn = tcDiagnosticErrorAtName @@ -947,7 +1093,7 @@ topLevelFunctionAnnotationError sig = (sigName sig) "incomplete signature" ["signature: " ++ pretty sig] - ["annotate every parameter (name: Type) and provide a return type (returns (Type))"] + ["annotate every parameter (name: Type); omit returns only for a unit-returning function"] methodAnnotationError :: Signature Name -> TcM a methodAnnotationError sig = @@ -957,7 +1103,7 @@ methodAnnotationError sig = (sigName sig) "incomplete method signature" ["signature: " ++ pretty sig] - ["annotate every method parameter and provide a return type"] + ["annotate every method parameter; omit returns only for a unit-returning method"] illegalReturnStatement :: Stmt Name -> TcM a illegalReturnStatement stmt = @@ -1002,11 +1148,17 @@ typeAlreadyDefinedError d n = ["rename or remove the duplicate type definition"] dataTyFromInfo :: Name -> TypeInfo -> TcM DataTy -dataTyFromInfo n (TypeInfo _ cs _) = +dataTyFromInfo n (TypeInfo _ cs memberNames) = do -- getting data constructor types (constrs, vs) <- unzip <$> mapM constrsFromEnv cs - pure (DataTy n (concat vs) constrs) + pure + ( DataTyWithKind + (if null memberNames then EnumKind else StructKind memberNames) + n + (concat vs) + constrs + ) constrsFromEnv :: Name -> TcM (Constr, [Tyvar]) constrsFromEnv n = diff --git a/src/Solcore/Frontend/TypeInference/TcStmt.hs b/src/Solcore/Frontend/TypeInference/TcStmt.hs index e242d8902..69b381c75 100644 --- a/src/Solcore/Frontend/TypeInference/TcStmt.hs +++ b/src/Solcore/Frontend/TypeInference/TcStmt.hs @@ -60,6 +60,8 @@ tcStmtWithExpectedReturn mExpectedReturn stmt = locatedInferResult locatedStmt stmt <$> tcStmtWithExpectedReturn' mExpectedReturn stmt tcStmtWithExpectedReturn' :: Maybe Ty -> Infer Stmt +tcStmtWithExpectedReturn' _ (FieldAccess (Just _) memberName := _) = + unsupportedStructFieldAssignment memberName tcStmtWithExpectedReturn' _ e@(lhs := rhs) = do (lhs1, ps1, t1) <- tcExp lhs @@ -118,12 +120,19 @@ tcStmtWithExpectedReturn' mExpectedReturn (Match es eqns) = ensureVisiblePatternCoverage ts' eqns (eqns', pss1, resTy) <- tcEquationsWithExpectedReturn mExpectedReturn ts' eqns withCurrentSubst (Match es' eqns', concat (pss1 : pss'), resTy) -tcStmtWithExpectedReturn' _ (Asm yblk) = - withLocalCtx yulPrimOps $ do - (newBinds, t) <- tcYulBlock yblk - let word' = monotype word - mapM_ (flip extEnv word') newBinds - pure (Asm yblk, [], t) +tcStmtWithExpectedReturn' mExpectedReturn stmt@(Asm yblk) = do + case validateYulControlFlow yblk of + Left err -> tcmError err `wrapError` stmt + Right () -> pure () + if isEmptyRevertBlock yblk + then do + resultTy <- maybe freshTyVar pure mExpectedReturn + pure (Asm yblk, [], resultTy) + else withLocalCtx yulPrimOps $ do + (newBinds, t) <- tcYulBlock yblk + let word' = monotype word + mapM_ (flip extEnv word') newBinds + pure (Asm yblk, [], t) tcStmtWithExpectedReturn' mExpectedReturn s@(If e blk1 blk2) = do (e', ps, t) <- tcExp e @@ -193,6 +202,14 @@ tcStmtWithExpectedReturn' _ Continue = tcStmtWithExpectedReturn' _ EmptyStmt = pure (EmptyStmt, [], unit) +-- Name resolution lowers source-level @revert;@ to this exact Yul block. +-- Reversion never falls through, so it inhabits the surrounding function's +-- result type just like a bottom value instead of forcing the branch to unit. +isEmptyRevertBlock :: YulBlock -> Bool +isEmptyRevertBlock + [YExp (YCall "revert" [YLit (YulNumber 0), YLit (YulNumber 0)])] = True +isEmptyRevertBlock _ = False + tcEquations :: [Ty] -> Equations Name -> TcM (Equations Id, [Pred], Ty) tcEquations = tcEquationsWithExpectedReturn Nothing @@ -421,13 +438,19 @@ tcExpWithExpected' _ (FieldAccess (Just e) n) = do -- inferring expression type (e', ps, t) <- tcExpWithExpected Nothing e - -- expand synonyms before extracting type name - tExp <- maybeExpandSynonym t - tn <- typeName tExp - -- getting field type - s <- askField tn n - (ps' :=> t') <- freshInst s - withCurrentSubst (FieldAccess (Just e') (Id n t'), ps ++ ps', t') + -- Expand aliases and instantiate the ordered source-struct metadata with + -- the receiver's actual type arguments. + tCurrent <- withCurrentSubst t + receiverTy <- maybeExpandSynonym tCurrent + (selectorName, memberTy) <- askStructField receiverTy n + let selectorTy = funtype [receiverTy] memberTy + -- A normal function call gives the receiver ordinary call-by-value + -- semantics: even a side-effecting receiver is evaluated exactly once. + withCurrentSubst + ( Call Nothing (Id selectorName selectorTy) [e'], + ps, + memberTy + ) tcExpWithExpected' _ ex@(Call me n args) = tcCall me n args `wrapError` ex tcExpWithExpected' mExpected (Lam args bd _) = @@ -1007,7 +1030,14 @@ elabSignature vs1 sig (Forall _ (ps :=> t)) = -- formal parameters are present in the signature. ret = Just $ if null params' then t else (funtype rs t') vs' = bv params' `union` bv ret `union` bv ps - sig2 <- withCurrentSubst (Signature (vs' \\ vs1) ps (sigName sig) params' (sigRetComptime sig) ret (sigPayable sig)) + sig2 <- + withCurrentSubst + ( (Signature (vs' \\ vs1) ps (sigName sig) params' (sigRetComptime sig) ret (sigPayable sig)) + { sigReturnNames = sigReturnNames sig, + sigReturnItems = sigReturnItems sig, + sigModifiers = sigModifiers sig + } + ) pure sig2 elabParam :: Ty -> Param Name -> TcM (Param Id) @@ -1016,7 +1046,12 @@ elabParam t (Untyped c n) = pure $ Typed c (Id n t) t annotateSignature :: Scheme -> Signature Name -> TcM (Signature Name) annotateSignature (Forall vs (ps :=> t)) sig = - pure $ Signature vs ps (sigName sig) params' (sigRetComptime sig) ret (sigPayable sig) + pure $ + (Signature vs ps (sigName sig) params' (sigRetComptime sig) ret (sigPayable sig)) + { sigReturnNames = sigReturnNames sig, + sigReturnItems = sigReturnItems sig, + sigModifiers = sigModifiers sig + } where (ts, t') = splitTy t params' = zipWith annotateParam ts (sigParams sig) @@ -1191,8 +1226,16 @@ schemeFromSignature sig = unwords ["Invalid instance member signature (missing return type):", pretty sig] updateSignature :: [Tyvar] -> Name -> FunDef Id -> FunDef Id -updateSignature vs' c (FunDef p (Signature vs ps n args rc rt pay) bd) = - FunDef p (Signature (vs \\ vs') ps (qualifyName c n) args rc rt pay) bd +updateSignature vs' c (FunDef p sig@(Signature vs ps n args rc rt pay) bd) = + FunDef + p + ( (Signature (vs \\ vs') ps (qualifyName c n) args rc rt pay) + { sigReturnNames = sigReturnNames sig, + sigReturnItems = sigReturnItems sig, + sigModifiers = sigModifiers sig + } + ) + bd checkDeferedConstraints :: [(FunDef Id, [Pred])] -> TcM () checkDeferedConstraints = mapM_ checkDeferedConstraint diff --git a/src/Solcore/Frontend/TypeInference/TcSubst.hs b/src/Solcore/Frontend/TypeInference/TcSubst.hs index 35aeb8ad4..155a6fa68 100644 --- a/src/Solcore/Frontend/TypeInference/TcSubst.hs +++ b/src/Solcore/Frontend/TypeInference/TcSubst.hs @@ -141,16 +141,30 @@ instance HasType Scheme where bv (Forall vs qt) = vs `union` bv qt instance (HasType a) => HasType (Signature a) where - apply s (Signature _ ctx n p rc r pay) = + apply s sig@(Signature _ ctx n p rc r pay) = let ctx' = apply s ctx p' = apply s p r' = apply s r vs' = bv ctx' `union` bv p' `union` bv r' - in Signature vs' ctx' n p' rc r' pay + in (Signature vs' ctx' n p' rc r' pay) + { sigReturnNames = sigReturnNames sig, + sigReturnItems = apply s (sigReturnItems sig), + sigModifiers = sigModifiers sig + } fv (Signature vs c _ p _ r _) = fv (c, p, r) \\ vs mv (Signature _ c _ p _ r _) = mv (c, p, r) bv (Signature vs c _ p _ r _) = vs `union` bv (c, p, r) +instance HasType SignatureReturnItem where + apply s item = + item + { signatureReturnItemType = + apply s (signatureReturnItemType item) + } + fv = fv . signatureReturnItemType + mv = mv . signatureReturnItemType + bv = bv . signatureReturnItemType + instance (HasType a) => HasType (Param a) where apply s (Typed c i t) = Typed c (apply s i) (apply s t) apply s (Untyped c i) = Untyped c (apply s i) @@ -405,12 +419,12 @@ instance (HasType a) => HasType (TopDecl a) where bv _ = [] instance (HasType a) => HasType (Contract a) where - apply s (Contract n vs ds) = - Contract n vs (apply s ds) + apply s (ContractWithKind kind n vs ds) = + ContractWithKind kind n vs (apply s ds) - fv (Contract _ _ ds) = fv ds - mv (Contract _ _ ds) = mv ds - bv (Contract _ _ ds) = bv ds + fv (ContractWithKind _ _ _ ds) = fv ds + mv (ContractWithKind _ _ _ ds) = mv ds + bv (ContractWithKind _ _ _ ds) = bv ds instance (HasType a) => HasType (ContractDecl a) where apply s (CFieldDecl fd) = diff --git a/src/Solcore/Primitives/Primitives.solc b/src/Solcore/Primitives/Primitives.solc index a8e0ca64c..f341ed762 100644 --- a/src/Solcore/Primitives/Primitives.solc +++ b/src/Solcore/Primitives/Primitives.solc @@ -1,12 +1,12 @@ enum Unit { Unit } - type Memory is Word; + alias Memory = Word; trait Ref { function load (r : ref) returns (deref) ; function store (r : ref, v : deref) ; } - type Stack is a; + alias Stack = a; impl Ref, Memory> { function load (r : Stack) returns (Memory) {} diff --git a/std/opcodes.solc b/std/opcodes.solc index 90723e98d..c709a2c76 100644 --- a/std/opcodes.solc +++ b/std/opcodes.solc @@ -79,7 +79,7 @@ export { delegatecall, create2, staticcall, - revert, + revert_, invalid, selfdestruct }; @@ -674,7 +674,7 @@ function staticcall(a: word, b: word, c: word, d: word, e: word, f: word) return return res; } -function revert(a: word, b: word) returns (()) { +function revert_(a: word, b: word) returns (()) { assembly { revert(a, b) } diff --git a/std/std.solc b/std/std.solc index 41207b846..0f4142e77 100644 --- a/std/std.solc +++ b/std/std.solc @@ -1,4 +1,4 @@ -import {add, sub, mul, div, mod, addmod as addmod_, mulmod as mulmod_, and as and_, or as or_, xor as xor_, shl, shr, eq, not as not_, gt as gt_, iszero, keccak256, mstore, mload, mcopy, sstore, sload, gas, calldataload, calldatacopy, returndatasize, returndatacopy, log1 as log1_, call, staticcall, revert as revert_, invalid} from std.opcodes; +import {add, sub, mul, div, mod, addmod as addmod_, mulmod as mulmod_, and as and_, or as or_, xor as xor_, shl, shr, eq, not as not_, gt as gt_, iszero, keccak256, mstore, mload, mcopy, sstore, sload, gas, calldataload, calldatacopy, returndatasize, returndatacopy, log1 as log1_, call, staticcall, revert_, invalid} from std.opcodes; pragma solcore noPattersonCondition ABIEncode, Num, Array, ArrayPush; pragma solcore noCoverageCondition ABIDecode, MemoryType, Array, ArrayPush, RValueIdxAccess; diff --git a/test/DiagnosticCliTests.hs b/test/DiagnosticCliTests.hs index 215860414..593ce2d6f 100644 --- a/test/DiagnosticCliTests.hs +++ b/test/DiagnosticCliTests.hs @@ -74,11 +74,11 @@ diagnosticCliTests = [ "error[SC0220]: top-level function must have complete type annotations", " --> /test/diagnostics/missing-signature.solc:1:10", " |", - "1 | function foo() {", + "1 | function foo(value) {", " | ^^^ incomplete signature", - "note: signature: function foo()", + "note: signature: function foo(value) returns (())", "note: module typecheck failed for /test/diagnostics/missing-signature.solc", - "help: annotate every parameter (name: Type) and provide a return type (returns (Type))" + "help: annotate every parameter (name: Type); omit returns only for a unit-returning function" ], testCase "polymorphic type error uses signature span" $ expectFailure diff --git a/test/LocationTests.hs b/test/LocationTests.hs index 9be1b4e61..978d77353 100644 --- a/test/LocationTests.hs +++ b/test/LocationTests.hs @@ -13,8 +13,8 @@ import Solcore.Frontend.Syntax qualified as Typed import Solcore.Frontend.Syntax.Location import Solcore.Frontend.Syntax.NameResolution (nameResolution) import Solcore.Frontend.Syntax.SyntaxTree qualified as Parsed -import Solcore.Frontend.TypeInference.SccAnalysis (sccAnalysis) import Solcore.Frontend.TypeInference.Id (Id) +import Solcore.Frontend.TypeInference.SccAnalysis (sccAnalysis) import Solcore.Frontend.TypeInference.TcModule import Solcore.Pipeline.Options (stdOpt) import Test.Tasty diff --git a/test/ModuleTypeCheckTests.hs b/test/ModuleTypeCheckTests.hs index 7959bdf99..c9fd95fdc 100644 --- a/test/ModuleTypeCheckTests.hs +++ b/test/ModuleTypeCheckTests.hs @@ -3,7 +3,21 @@ module ModuleTypeCheckTests ) where -import Solcore.Diagnostics (CompilerError, compilerErrorText) +import Data.List (isInfixOf, sort) +import Solcore.Backend.Mast (MastCompUnit (..), MastContract (..), MastTopDecl (..), deployerName) +import Solcore.Backend.Specialise (specialiseCompUnit) +import Solcore.Desugarer.ContractDispatch (contractDispatchTopDecls) +import Solcore.Desugarer.DecisionTreeCompiler (matchCompiler) +import Solcore.Desugarer.DeriveGeneric (deriveGenericTopDecls) +import Solcore.Desugarer.FieldAccess (fieldDesugarTopDecls) +import Solcore.Desugarer.IndirectCall (indirectCallTopDecls) +import Solcore.Diagnostics + ( CompilerError, + compilerErrorDiagnostics, + compilerErrorText, + diagnosticPrimarySpan, + ) +import Solcore.Frontend.ComptimeCheck (checkComptimeEarly) import Solcore.Frontend.Module.Loader ( ModuleGraph (entryModule), ModuleTypeCheckSurface (moduleSurfaceImportedDecls), @@ -11,11 +25,13 @@ import Solcore.Frontend.Module.Loader moduleLocalTypeCheckSurface, ) import Solcore.Frontend.Parser.SolcoreParser (parseCompUnit) +import Solcore.Frontend.Pretty.SolcorePretty qualified as SolcorePretty import Solcore.Frontend.Pretty.TreePretty qualified as TreePretty import Solcore.Frontend.Syntax import Solcore.Frontend.Syntax.NameResolution (nameResolution) import Solcore.Frontend.Syntax.SyntaxTree qualified as Source -import Solcore.Frontend.TypeInference.Id (Id) +import Solcore.Frontend.TypeInference.Id (Id (..)) +import Solcore.Frontend.TypeInference.SccAnalysis (sccAnalysisTopDecls) import Solcore.Frontend.TypeInference.TcContract ( TopDeclCheck (..), TopDeclCheckMode (CheckTopDeclBody), @@ -24,6 +40,7 @@ import Solcore.Frontend.TypeInference.TcContract import Solcore.Frontend.TypeInference.TcEnv (TcEnv) import Solcore.Frontend.TypeInference.TcModule import Solcore.Pipeline.Options (stdOpt) +import Solcore.Pipeline.SolcorePipeline (localDataDefsForDeriving) import Test.Tasty import Test.Tasty.HUnit @@ -104,6 +121,336 @@ moduleTypeCheckTests = assertRight "numeric fixed-array size should be kind-correct" checked, + testCase "internal function parameters remain supported" $ do + checked <- + typecheckSource $ + unlines + [ "function applyCallback(f: function(word) internal returns (word), x: word) returns (word) {", + " return f(x);", + "}" + ] + assertRight "internal function parameter" checked, + testCase "internal function parameters preserve multiple returns" $ do + checked <- + typecheckSource $ + unlines + [ "function applyPair(f: function(word) internal returns (word, bool), x: word) returns (word, bool) {", + " return f(x);", + "}" + ] + assertRight "multi-return internal function parameter" checked, + testCase "external function types fail before internal arrow lowering" $ do + checked <- + typecheckSource $ + unlines + [ "function bad(f: function(word) external returns (word), x: word) returns (word) {", + " return x;", + "}" + ] + assertLocatedFunctionTypeError + "external function type" + "external function types are not supported" + checked, + testCase "nullary function types do not collapse to their result" $ do + checked <- + typecheckSource $ + unlines + [ "function bad(f: function() internal returns (word)) returns (word) {", + " return 0;", + "}" + ] + assertLocatedFunctionTypeError + "nullary function type" + "zero-parameter function types are not supported" + checked, + testCase "struct metadata survives name resolution and typechecking" $ do + checked <- + typecheckSource + "struct Pair { left: word; right: bool; }" + case checked of + Left err -> + assertFailure + ("struct declaration should typecheck:\n" ++ compilerErrorText err) + Right (CompUnit _ typedDecls, _) -> + case [ dt + | TDataDef dt <- typedDecls, + dataName dt == Name "Pair" + ] of + [ DataTyWithKind + (StructKind [Name "left", Name "right"]) + _ + [] + [Constr _ [TyCon (Name "word") [], TyCon (Name "bool") []]] + ] -> + pure () + got -> + assertFailure + ("struct kind or ordered fields were lost: " ++ show got), + testCase "same-spelled contract-local data types keep distinct canonical identities" $ do + resolved@(CompUnit _ resolvedDecls) <- + resolvedSourceOrFail sameNamedLocalTypesSource + let left = findSemanticContract "Left" resolvedDecls + right = findSemanticContract "Right" resolvedDecls + leftS = QualName (Name "Left") "S" + leftE = QualName (Name "Left") "E" + rightS = QualName (Name "Right") "S" + rightE = QualName (Name "Right") "E" + assertEqual + "left local declarations retain kind, fields, and canonical constructors" + [ DataTyWithKind + (StructKind [Name "left"]) + leftS + [] + [Constr (QualName leftS "S") [TyCon (Name "word") []]], + DataTyWithKind + EnumKind + leftE + [] + [Constr (QualName leftE "A") []] + ] + [dt | CDataDecl dt <- decls left] + assertEqual + "right local declarations are independent from left" + [ DataTyWithKind + (StructKind [Name "right", Name "extra"]) + rightS + [] + [ Constr + (QualName rightS "S") + [TyCon (Name "bool") [], TyCon (Name "word") []] + ], + DataTyWithKind + EnumKind + rightE + [] + [Constr (QualName rightE "B") []] + ] + [dt | CDataDecl dt <- decls right] + assertContractFunctionTypes left leftS leftE + assertContractFunctionTypes right rightS rightE + + checked <- typecheckSource sameNamedLocalTypesSource + assertRight + "same-spelled local types should coexist in the type table" + checked + + let rendered = SolcorePretty.pretty resolved + assertBool + "semantic pretty printing keeps local declarations source-shaped" + ( all (`isInfixOf` rendered) ["struct S", "enum E"] + && not ("struct Left.S" `isInfixOf` rendered) + && not ("struct Right.S" `isInfixOf` rendered) + ) + reparsed <- parseCompUnit rendered + case reparsed of + Left err -> + assertFailure + ("pretty-printed local declarations did not parse:\n" ++ err ++ "\n" ++ rendered) + Right reparsedUnit -> do + reresolved <- nameResolution reparsedUnit + assertRight + "pretty-printed local declarations should resolve again" + reresolved, + testCase "local nested data types feed Generic, Storage, and ABI derivation independently" $ do + CompUnit _ resolvedDecls <- + resolvedSourceOrFail sameNamedLocalTypesSource + let dispatched = contractDispatchTopDecls resolvedDecls + inferenceDecls = + map (ModuleInferenceDecl ModuleLocalDecl) dispatched + localData = localDataDefsForDeriving inferenceDecls + expectedNames = + [ QualName (Name "Left") "S", + QualName (Name "Left") "E", + QualName (Name "Right") "S", + QualName (Name "Right") "E" + ] + derivationSurface = + map markerClass ["Generic", "StorageDeriving", "ABIDeriving"] + ++ dispatched + assertEqual + "nested declarations are collected with canonical names" + (sort expectedNames) + (sort (map dataName localData)) + derived <- + case deriveGenericTopDecls localData derivationSurface of + Left err -> assertFailure ("unexpected derivation failure:\n" ++ err) + Right decls' -> pure decls' + let instances = [inst | TInstDef inst <- derived] + mapM_ + (assertAllDerivedInstances instances) + expectedNames, + testCase "same-named parameter cannot capture a struct member read" $ do + checked <- + typecheckSourceAfterFieldDesugar $ + unlines + [ "struct Pair { x: word; }", + "function read(p: Pair, x: bool) returns (word) {", + " return p.x;", + "}" + ] + case checked of + Left err -> + assertFailure + ("same-named member read should typecheck:\n" ++ compilerErrorText err) + Right (CompUnit _ typedDecls, _) -> + case [ body + | TFunDef (FunDef _ sig body) <- typedDecls, + sigName sig == Name "read" + ] of + [ [ Return + ( Call + Nothing + (Id selectorName selectorTy) + [Var (Id receiverName _)] + ) + ] + ] -> do + assertEqual + "member selector" + (QualName (Name "Pair") "$structField$x") + selectorName + assertEqual "receiver remains p" (Name "p") receiverName + assertEqual + "same-named bool parameter does not affect member type" + (funtype [TyCon (Name "Pair") []] (TyCon (Name "word") [])) + selectorTy + got -> + assertFailure + ("member read was not lowered to one selector call: " ++ show got), + testCase "generic nested struct reads instantiate ordered field types" $ do + checked <- + typecheckSourceAfterFieldDesugar $ + unlines + [ "struct Box { value: a; }", + "struct Outer { flag: bool; inner: Box; }", + "function read(o: Outer) returns (word) {", + " return o.inner.value;", + "}" + ] + case checked of + Left err -> + assertFailure + ("nested generic member read should typecheck:\n" ++ compilerErrorText err) + Right (CompUnit _ typedDecls, _) -> + case [ body + | TFunDef (FunDef _ sig body) <- typedDecls, + sigName sig == Name "read" + ] of + [ [ Return + ( Call + Nothing + (Id valueSelector valueSelectorTy) + [ Call + Nothing + (Id innerSelector innerSelectorTy) + [Var (Id receiverName _)] + ] + ) + ] + ] -> do + let word = TyCon (Name "word") [] + boxWord = TyCon (Name "Box") [word] + outerWord = TyCon (Name "Outer") [word] + assertEqual + "outer field selector" + (QualName (Name "Outer") "$structField$inner") + innerSelector + assertEqual + "nested field selector" + (QualName (Name "Box") "$structField$value") + valueSelector + assertEqual "outer generic field type" (funtype [outerWord] boxWord) innerSelectorTy + assertEqual "nested generic field type" (funtype [boxWord] word) valueSelectorTy + assertEqual "receiver remains o" (Name "o") receiverName + got -> + assertFailure + ("nested member reads were not lowered correctly: " ++ show got), + testCase "contract-local struct reads use the canonical local type" $ do + checked <- + typecheckSourceAfterFieldDesugar + "contract C { struct Local { value: word; } function read(p: Local) returns (word) { return p.value; } }" + case checked of + Left err -> + assertFailure + ("contract-local member read should typecheck:\n" ++ compilerErrorText err) + Right (CompUnit _ typedDecls, _) -> + case [ selectorName + | TContr contractDef <- typedDecls, + CFunDecl (FunDef _ sig [Return (Call Nothing (Id selectorName _) [_])]) <- decls contractDef, + sigName sig == Name "read" + ] of + [selectorName] -> + assertEqual + "contract-local selector uses the canonical type name" + (QualName (QualName (Name "C") "Local") "$structField$value") + selectorName + got -> + assertFailure + ("contract-local member read was not lowered: " ++ show got), + testCase "side-effecting struct receivers occur once in the selector call" $ do + checked <- + typecheckSourceAfterFieldDesugar $ + unlines + [ "struct Pair { x: word; }", + "function make(x: word) returns (Pair) { return Pair.Pair(x); }", + "function read(x: word) returns (word) { return make(x).x; }" + ] + case checked of + Left err -> + assertFailure + ("call receiver member read should typecheck:\n" ++ compilerErrorText err) + Right (CompUnit _ typedDecls, _) -> + case [ body + | TFunDef (FunDef _ sig body) <- typedDecls, + sigName sig == Name "read" + ] of + [ [ Return + ( Call + Nothing + (Id _ _) + [Call Nothing (Id makeName _) [Var (Id argumentName _)]] + ) + ] + ] -> + do + assertEqual + "receiver call appears as the selector's single argument" + (Name "make") + makeName + assertEqual + "receiver call argument is not duplicated" + (Name "x") + argumentName + got -> + assertFailure + ("receiver was duplicated or not lowered: " ++ show got), + testCase "unknown struct member reports an undefined-field diagnostic" $ do + checked <- + typecheckSourceAfterFieldDesugar $ + unlines + [ "struct Pair { x: word; }", + "function read(p: Pair) returns (word) {", + " return p.missing;", + "}" + ] + assertLocatedFieldDiagnostic + "unknown struct member" + "SC0204" + checked, + testCase "struct member assignment fails safely before lowering" $ do + checked <- + typecheckSourceAfterFieldDesugar $ + unlines + [ "struct Pair { x: word; }", + "function write(p: Pair, value: word) {", + " p.x = value;", + " return;", + "}" + ] + assertLocatedFieldDiagnostic + "struct member assignment" + "SC0231" + checked, testCase "interface signature has no body to typecheck" $ do checked <- typecheckSource $ @@ -116,12 +463,58 @@ moduleTypeCheckTests = Left err -> assertFailure ("interface signature should typecheck:\n" ++ compilerErrorText err) - Right (CompUnit _ [TContr (Contract _ _ [CSignatureDecl isExternal sig])], _) -> do + Right (CompUnit _ [TContr (ContractWithKind InterfaceKind _ _ [CSignatureDecl isExternal sig])], _) -> do assertBool "external visibility is preserved" isExternal assertEqual "signature name" (Name "read") (sigName sig) assertEqual "signature return" (Just wordTy) (sigReturn sig) + assertEqual + "interface visibility and mutability survive typechecking" + [ VisibilityModifier VisibilityExternal, + MutabilityModifier MutabilityView + ] + (sigModifiers sig) Right other -> assertFailure ("unexpected typed interface shape: " ++ show (fst other)), + testCase "contract visibility and mutability survive typechecking" $ + assertTypedFunctionModifiers + ContractKind + "read" + [ VisibilityModifier VisibilityPublic, + MutabilityModifier MutabilityView + ] + "contract Reader { function read(x: word) public view returns (word) { return x; } }", + testCase "library visibility and mutability survive typechecking" $ + assertTypedFunctionModifiers + LibraryKind + "twice" + [ VisibilityModifier VisibilityInternal, + MutabilityModifier MutabilityPure + ] + "library Math { function twice(x: word) internal pure returns (word) { return x; } }", + testCase "empty interface remains non-runtime through semantic passes" $ + assertContractKindLifecycle + "Empty" + InterfaceKind + False + "interface Empty {}", + testCase "nonempty interface remains non-runtime through semantic passes" $ + assertContractKindLifecycle + "Reader" + InterfaceKind + False + "interface Reader { function read(key: word) external returns (word); }", + testCase "library remains non-runtime through semantic passes" $ + assertContractKindLifecycle + "Math" + LibraryKind + False + "library Math { function twice(x: word) public returns (word) { return x; } }", + testCase "ordinary contract remains the only runtime declaration kind" $ + assertContractKindLifecycle + "Live" + ContractKind + True + "contract Live { function ping() public { return; } }", testCase "ordinary contract function still checks its empty body" $ do checked <- typecheckSource $ @@ -131,6 +524,258 @@ moduleTypeCheckTests = "}" ] assertLeft "non-unit contract function with an empty body" checked, + testCase "bare revert terminates a non-unit function" $ do + checked <- + typecheckSource + "function abort() returns (word) { revert; }" + assertRight "bare revert in word-returning function" checked, + testCase "bare revert satisfies a non-unit conditional branch" $ do + checked <- + typecheckSource $ + unlines + [ "function choose(flag: bool, value: word) returns (word) {", + " if (flag) { return value; } else { revert; }", + "}" + ] + assertRight "bare revert in word-returning branch" checked, + testCase "Yul loop control is rejected outside a loop body" $ do + breakResult <- + typecheckSource + "function badBreak() { assembly { break } return; }" + continueResult <- + typecheckSource + "function badContinue() { assembly { continue } return; }" + assertLeftContaining + "top-level Yul break" + "only valid inside a for-loop body" + breakResult + assertLeftContaining + "top-level Yul continue" + "only valid inside a for-loop body" + continueResult, + testCase "Yul leave is rejected outside a Yul function" $ do + checked <- + typecheckSource + "function badLeave() { assembly { leave } return; }" + assertLeftContaining + "top-level Yul leave" + "only valid inside a Yul function" + checked, + testCase "Yul control transfer is accepted in its lexical context" $ do + checked <- + typecheckSource $ + unlines + [ "function validControl() {", + " assembly {", + " for {} true {} { continue break }", + " function stop() { leave }", + " }", + " return;", + "}" + ] + assertRight "well-scoped Yul control transfer" checked, + testCase "omitted returns clause is a fully annotated unit return" $ do + checked <- + typecheckSource $ + unlines + [ "function nop() {", + " return;", + "}" + ] + assertRight "unit function with omitted returns clause" checked, + testCase "fallback with omitted returns clause typechecks as unit" $ do + checked <- + typecheckSource $ + unlines + [ "contract Receiver {", + " fallback() external {", + " return;", + " }", + "}" + ] + assertRight "unit fallback" checked, + testCase "named return is in scope and supports bare return" $ do + checked <- + typecheckSource $ + unlines + [ "contract Reader {", + " function read(x: word) external returns (result: word) {", + " result = x;", + " return;", + " }", + "}" + ] + case checked of + Left err -> + assertFailure + ("named return should typecheck:\n" ++ compilerErrorText err) + Right (CompUnit _ typedDecls, _) -> + case [ sig + | TContr (ContractWithKind ContractKind contractName _ contractDecls) <- typedDecls, + contractName == Name "Reader", + CFunDecl (FunDef _ sig _) <- contractDecls, + sigName sig == Name "read" + ] of + [sig] -> + do + assertEqual + "legacy return-name view survives typechecking" + [Just (Name "result")] + (sigReturnNames sig) + case sigReturnItems sig of + [returnItem] -> do + assertEqual + "return item name survives typechecking" + (Just (Name "result")) + (signatureReturnItemName returnItem) + assertBool + "runtime return item remains runtime" + (not (signatureReturnItemComptime returnItem)) + assertEqual + "return item type tracks the aggregate result" + (Just (signatureReturnItemType returnItem)) + (sigReturn sig) + returnItems -> + assertFailure + ("unexpected typed return-item metadata: " ++ show returnItems) + other -> + assertFailure ("unexpected typed named-return signatures: " ++ show other), + testCase "comptime named return supports assignment and bare return" $ do + checked <- + typecheckSource $ + unlines + [ "function staged(comptime x: word) returns (comptime result: word) {", + " result = x;", + " return;", + "}" + ] + case checked of + Left err -> + assertFailure + ("comptime named return should typecheck:\n" ++ compilerErrorText err) + Right (typed@(CompUnit _ typedDecls), _) -> do + case [ (sig, body) + | TFunDef (FunDef _ sig body) <- typedDecls, + sigName sig == Name "staged" + ] of + [(sig, Let isComptime _ _ Nothing : _)] -> do + assertBool "named return local remains comptime" isComptime + assertBool "aggregate return remains comptime" (sigRetComptime sig) + assertEqual + "per-item comptime metadata survives typechecking" + [True] + (map signatureReturnItemComptime (sigReturnItems sig)) + other -> + assertFailure + ("unexpected comptime named-return function: " ++ show other) + case checkComptimeEarly typed of + Left err -> + assertFailure + ("comptime named bare return failed early checking:\n" ++ err) + Right () -> pure (), + testCase "uninitialized comptime binding rejects a runtime assignment" $ do + checked <- + typecheckSource $ + unlines + [ "function bad(x: word) returns (word) {", + " let comptime result: word;", + " result = x;", + " return result;", + "}" + ] + case checked of + Left err -> + assertFailure + ("runtime-assignment fixture should typecheck first:\n" ++ compilerErrorText err) + Right (typed, _) -> + case checkComptimeEarly typed of + Left err -> + assertBool + ("unexpected comptime diagnostic: " ++ err) + ("comptime variable" `isInfixOf` err) + Right () -> + assertFailure + "runtime assignment to an uninitialized comptime binding was accepted", + testCase "mixed comptime return items fail explicitly" $ do + checked <- + typecheckSource $ + unlines + [ "function mixed() returns (left: word, comptime right: bool) {", + " left = 1;", + " right = true;", + " return;", + "}" + ] + case checked of + Left err -> do + assertLeftContaining + "mixed comptime return mode" + "SC0123" + (Left err) + assertLeftContaining + "mixed comptime return mode" + "mixed comptime and runtime return items are not supported" + (Left err) + assertBool + "mixed return-mode diagnostic is source-located" + (any ((/= Nothing) . diagnosticPrimarySpan) (compilerErrorDiagnostics err)) + Right _ -> + assertFailure "mixed comptime return mode should fail explicitly", + testCase "bare return remains invalid for unnamed non-unit result" $ do + checked <- + typecheckSource $ + unlines + [ "function bad() returns (word) {", + " return;", + "}" + ] + assertLeft "unnamed word return requires a value" checked, + testCase "explicit unit return is not lowered as a named bare return" $ do + checked <- + typecheckSource $ + unlines + [ "function bad(x: word) returns (result: word) {", + " result = x;", + " return ();", + "}" + ] + assertLeft "explicit unit cannot satisfy a word return" checked, + testCase "named return cannot reuse a parameter name" $ do + checked <- + typecheckSource + "function bad(result: word) returns (result: word) { return; }" + assertLeftContaining "parameter/return collision" "SC0108" checked, + testCase "named return declarations must be unique" $ do + checked <- + typecheckSource + "function bad() returns (result: word, result: word) { return; }" + assertLeftContaining "duplicate named returns" "SC0108" checked, + testCase "nested local cannot shadow a named return" $ do + checked <- + typecheckSource $ + unlines + [ "function bad(x: word) returns (result: word) {", + " if (true) {", + " let result: word = x;", + " return;", + " } else {", + " result = x;", + " return;", + " }", + "}" + ] + assertLeftContaining "nested named-return shadow" "SC0108" checked, + testCase "match binder cannot shadow a named return" $ do + checked <- + typecheckSource $ + unlines + [ "function bad(x: word) returns (result: word) {", + " match (x) {", + " case result { return; }", + " }", + "}" + ] + assertLeftContaining "match named-return shadow" "SC0108" checked, testCase "selective struct import preserves source metadata and pretty round-trips" $ do graphResult <- loadModuleGraph @@ -147,11 +792,10 @@ moduleTypeCheckTests = Left err -> assertFailure ("unexpected module surface failure:\n" ++ err) Right loadedSurface -> pure loadedSurface importedStruct <- - case - [ dt - | Source.TDataDef dt <- moduleSurfaceImportedDecls surface, - Source.dataName dt == "RenamedPair" - ] of + case [ dt + | Source.TDataDef dt <- moduleSurfaceImportedDecls surface, + Source.dataName dt == "RenamedPair" + ] of [dt] -> pure dt unexpectedDecls -> assertFailure @@ -171,6 +815,77 @@ moduleTypeCheckTests = unit ] +sameNamedLocalTypesSource :: String +sameNamedLocalTypesSource = + unlines + [ "contract Left {", + " struct S { left: word; }", + " enum E { A }", + " function echoLeft(value: S) returns (S) { return value; }", + " function tagLeft() returns (E) { return E.A; }", + "}", + "contract Right {", + " struct S { right: bool; extra: word; }", + " enum E { B }", + " function echoRight(value: S) returns (S) { return value; }", + " function tagRight() returns (E) { return E.B; }", + "}" + ] + +resolvedSourceOrFail :: String -> IO (CompUnit Name) +resolvedSourceOrFail source = do + parsedResult <- parseCompUnit source + parsed <- + case parsedResult of + Left err -> assertFailure ("unexpected parse failure:\n" ++ err) + Right compUnit -> pure compUnit + resolvedResult <- nameResolution parsed + case resolvedResult of + Left err -> + assertFailure + ("unexpected name-resolution failure:\n" ++ compilerErrorText err) + Right compUnit -> pure compUnit + +assertContractFunctionTypes :: Contract Name -> Name -> Name -> Assertion +assertContractFunctionTypes contractDef structName enumName = + assertEqual + ("local references in " ++ show (name contractDef)) + [ ([TyCon structName []], Just (TyCon structName [])), + ([], Just (TyCon enumName [])) + ] + [ ([ty | Typed _ _ ty <- sigParams sig], sigReturn sig) + | CFunDecl (FunDef _ sig _) <- decls contractDef + ] + +markerClass :: String -> TopDecl Name +markerClass className' = + TClassDef + ( Class + [] + [] + (Name className') + [] + (TVar (Name "_self")) + [] + ) + +assertAllDerivedInstances :: [Instance Name] -> Name -> Assertion +assertAllDerivedInstances instances typeName = + mapM_ assertDerived ["Generic", "StorageSize", "CanStore", "ABIAttribs", "ABIDecode"] + where + nominalType = TyCon typeName [] + assertDerived className' = + assertEqual + (className' ++ " derivation for " ++ show typeName) + 1 + ( length + [ () + | inst <- instances, + instName inst == Name className', + mainTy inst == nominalType || nominalType `elem` paramsTy inst + ] + ) + assertRight :: String -> Either CompilerError a -> Assertion assertRight _ (Right _) = pure () assertRight label (Left err) = @@ -181,6 +896,190 @@ assertLeft _ (Left _) = pure () assertLeft label (Right _) = assertFailure (label ++ ": expected failure") +assertLeftContaining :: String -> String -> Either CompilerError a -> Assertion +assertLeftContaining label needle (Left err) = + assertBool + (label ++ ": expected diagnostic containing " ++ show needle ++ "\n" ++ compilerErrorText err) + (needle `isInfixOf` compilerErrorText err) +assertLeftContaining label _ (Right _) = + assertFailure (label ++ ": expected failure") + +assertLocatedFunctionTypeError :: + String -> + String -> + Either CompilerError a -> + Assertion +assertLocatedFunctionTypeError label expectedMessage (Left err) = do + assertLeftContaining label "SC0122" (Left err) + assertLeftContaining label expectedMessage (Left err) + assertBool + (label ++ ": expected a source-located diagnostic") + (any ((/= Nothing) . diagnosticPrimarySpan) (compilerErrorDiagnostics err)) +assertLocatedFunctionTypeError label _ (Right _) = + assertFailure (label ++ ": expected failure") + +assertContractKindLifecycle :: String -> ContractKind -> Bool -> String -> Assertion +assertContractKindLifecycle contractName expectedKind shouldGenerateRuntime source = do + parsedResult <- parseCompUnit source + parsed <- + case parsedResult of + Left err -> assertFailure ("unexpected parse failure:\n" ++ err) + Right compUnit -> pure compUnit + resolvedResult <- nameResolution parsed + CompUnit resolvedImports resolvedDecls <- + case resolvedResult of + Left err -> + assertFailure + ("unexpected name-resolution failure:\n" ++ compilerErrorText err) + Right compUnit -> pure compUnit + assertSemanticKind "name resolution" contractName expectedKind resolvedDecls + + let fieldDesugared = fieldDesugarTopDecls resolvedDecls + assertSemanticKind "field desugaring" contractName expectedKind fieldDesugared + if shouldGenerateRuntime + then + assertBool + "ordinary contract receives its storage-context declaration" + (any isGeneratedDataDecl fieldDesugared) + else + assertEqual + "non-runtime declaration kind is unchanged by field desugaring" + resolvedDecls + fieldDesugared + + let dispatched = contractDispatchTopDecls resolvedDecls + dispatchedContract = findSemanticContract contractName dispatched + dispatchedDecls = decls dispatchedContract + hasMain = + any + isMainDecl + dispatchedDecls + hasDeployer = + any + isDeployerDecl + dispatchedDecls + assertSemanticKind "dispatch generation" contractName expectedKind dispatched + assertEqual "runtime main generation" shouldGenerateRuntime hasMain + assertEqual "default constructor deployer generation" shouldGenerateRuntime hasDeployer + if shouldGenerateRuntime + then + assertBool + "ordinary public method receives dispatch declarations" + (any isGeneratedDataDecl dispatched) + else + assertEqual + "non-runtime declaration kind is unchanged by dispatch generation" + resolvedDecls + dispatched + + sccResult <- sccAnalysisTopDecls resolvedDecls + sccDecls <- + case sccResult of + Left err -> assertFailure ("unexpected SCC failure:\n" ++ err) + Right topDecls -> pure topDecls + assertSemanticKind "SCC reconstruction" contractName expectedKind sccDecls + + (directDecls, _) <- indirectCallTopDecls resolvedDecls + assertSemanticKind "indirect-call reconstruction" contractName expectedKind directDecls + + checked <- + typeInferTopDeclChecks + stdOpt + resolvedImports + [] + [] + [ TopDeclCheck CheckTopDeclBody decl + | decl <- resolvedDecls + ] + (typed, tcEnv) <- + case checked of + Left err -> + assertFailure + ("unexpected typecheck failure:\n" ++ compilerErrorText err) + Right result -> pure result + assertSemanticKind "type inference" contractName expectedKind (contracts typed) + + compiledResult <- matchCompiler typed + compiled <- + case compiledResult of + Left err -> assertFailure ("unexpected match compilation failure:\n" ++ err) + Right (compUnit, _) -> pure compUnit + assertSemanticKind "match compilation" contractName expectedKind (contracts compiled) + + let specialisationInput = + if shouldGenerateRuntime + then addTestMain compiled + else compiled + specialised <- specialiseCompUnit specialisationInput False tcEnv + let runtimeContractNames = + [ runtimeName + | MastTContr runtimeContract <- mastTopDecls specialised, + let runtimeName = mastContrName runtimeContract + ] + assertEqual + "specialisation runtime target" + (if shouldGenerateRuntime then [Name contractName] else []) + runtimeContractNames + where + isGeneratedDataDecl (TDataDef _) = True + isGeneratedDataDecl _ = False + isMainDecl (CFunDecl (FunDef _ sig _)) = sigName sig == Name "main" + isMainDecl _ = False + isDeployerDecl (CFunDecl (FunDef _ sig _)) = sigName sig == deployerName + isDeployerDecl _ = False + +addTestMain :: CompUnit Id -> CompUnit Id +addTestMain (CompUnit imps topDecls) = + CompUnit imps (map addMain topDecls) + where + addMain (TContr contractDef) + | contractKind contractDef == ContractKind = + TContr + contractDef + { decls = + CFunDecl + ( FunDef + False + SignatureWithReturnNames + { sigVars = [], + sigContext = [], + sigName = Name "main", + sigParams = [], + sigRetComptime = False, + sigReturn = Just (TyCon (Name "()") []), + sigPayable = False, + sigReturnNames = [], + sigReturnItems = [], + sigModifiers = [] + } + [] + ) + : decls contractDef + } + addMain topDecl = topDecl + +assertSemanticKind :: String -> String -> ContractKind -> [TopDecl a] -> Assertion +assertSemanticKind phase contractName expectedKind topDecls = + assertEqual + (phase ++ " contract kind") + expectedKind + (contractKind (findSemanticContract contractName topDecls)) + +findSemanticContract :: String -> [TopDecl a] -> Contract a +findSemanticContract contractName topDecls = + case [ contractDef + | TContr contractDef <- topDecls, + name contractDef == Name contractName + ] of + [contractDef] -> contractDef + contractsFound -> + error + ( "expected exactly one contract named " + ++ show contractName + ++ ", got " + ++ show (length contractsFound) + ) + typecheckSource :: String -> IO (Either CompilerError (CompUnit Id, TcEnv)) typecheckSource source = do parsedResult <- parseCompUnit source @@ -201,6 +1100,78 @@ typecheckSource source = do | decl <- resolvedDecls ] +typecheckSourceAfterFieldDesugar :: String -> IO (Either CompilerError (CompUnit Id, TcEnv)) +typecheckSourceAfterFieldDesugar source = do + parsedResult <- parseCompUnit source + parsed <- + case parsedResult of + Left err -> assertFailure ("unexpected parse failure:\n" ++ err) + Right compUnit -> pure compUnit + resolvedResult <- nameResolution parsed + case resolvedResult of + Left err -> pure (Left err) + Right (CompUnit resolvedImports resolvedDecls) -> + typeInferTopDeclChecks + stdOpt + resolvedImports + [] + [] + [ TopDeclCheck CheckTopDeclBody decl + | decl <- fieldDesugarTopDecls resolvedDecls + ] + +assertLocatedFieldDiagnostic :: + String -> + String -> + Either CompilerError a -> + Assertion +assertLocatedFieldDiagnostic label expectedCode (Left err) = do + assertLeftContaining label expectedCode (Left err) + assertBool + (label ++ ": expected a source-located diagnostic") + (any ((/= Nothing) . diagnosticPrimarySpan) (compilerErrorDiagnostics err)) +assertLocatedFieldDiagnostic label _ (Right _) = + assertFailure (label ++ ": expected failure") + +assertTypedFunctionModifiers :: + ContractKind -> + String -> + [FunctionModifier] -> + String -> + Assertion +assertTypedFunctionModifiers expectedKind functionName expectedModifiers source = do + checked <- typecheckSource source + case checked of + Left err -> + assertFailure + ("modifier lifecycle fixture failed typechecking:\n" ++ compilerErrorText err) + Right (CompUnit _ typedDecls, _) -> do + let contractDef = + case [c | TContr c <- typedDecls, contractKind c == expectedKind] of + [c] -> c + other -> + error + ( "expected one " + ++ show expectedKind + ++ ", got " + ++ show (length other) + ) + matchingModifiers = + [ sigModifiers sig + | sig <- contractSignatures (decls contractDef), + sigName sig == Name functionName + ] + assertEqual + ("typed modifiers for " ++ functionName) + [expectedModifiers] + matchingModifiers + where + contractSignatures = concatMap fromDecl + fromDecl (CFunDecl (FunDef _ sig _)) = [sig] + fromDecl (CSignatureDecl _ sig) = [sig] + fromDecl (CMutualDecl nestedDecls) = contractSignatures nestedDecls + fromDecl _ = [] + moduleInput :: [ModuleInferenceDecl] -> ModuleTypeCheckInput moduleInput inferenceDecls = withPreparedModuleInferenceDecls (resolvedModuleInput inferenceDecls) inferenceDecls @@ -260,12 +1231,15 @@ usesImportedFun = wordSignature :: String -> Signature Name wordSignature funName = - Signature + SignatureWithReturnNames { sigVars = [], sigContext = [], sigName = Name funName, sigParams = [], sigRetComptime = False, sigReturn = Just wordTy, - sigPayable = False + sigPayable = False, + sigReturnNames = [], + sigReturnItems = [], + sigModifiers = [] } diff --git a/test/ParserTests.hs b/test/ParserTests.hs index 633c9cefc..f5c05ef1d 100644 --- a/test/ParserTests.hs +++ b/test/ParserTests.hs @@ -3,17 +3,22 @@ module ParserTests (parserTests) where import Common.LightYear (Parser, runParserE) +import Data.List (isInfixOf) import Data.List.NonEmpty (NonEmpty ((:|))) -import Solcore.Frontend.Lexer.SolcoreLexer (sc) +import Language.Yul (YLiteral (..), YulExp (..), YulStmt (..)) +import Solcore.Diagnostics (compilerErrorText) +import Solcore.Frontend.Lexer.SolcoreLexer (identifier, sc) import Solcore.Frontend.Parser.Decl (importP, topDeclP) import Solcore.Frontend.Parser.Expr (exprP) import Solcore.Frontend.Parser.Patterns (patP) import Solcore.Frontend.Parser.SolcoreTypes (predP, typeP) import Solcore.Frontend.Parser.Stmt (bodyP, stmtP) +import Solcore.Frontend.Pretty.SolcorePretty qualified as SolcorePretty import Solcore.Frontend.Pretty.TreePretty qualified as TreePretty import Solcore.Frontend.Syntax.Contract qualified as Resolved import Solcore.Frontend.Syntax.Name import Solcore.Frontend.Syntax.NameResolution (nameResolution) +import Solcore.Frontend.Syntax.Stmt qualified as ResolvedStmt import Solcore.Frontend.Syntax.SyntaxTree import Solcore.Frontend.Syntax.Ty qualified as ResolvedTy import Test.Tasty @@ -32,6 +37,15 @@ parseFails p src = Left _ -> return () Right got -> assertFailure ("Expected failure but parsed: " ++ show got) +parseFailsContaining :: (Show a) => Parser a -> String -> String -> Assertion +parseFailsContaining p expected src = + case runParserE (sc *> p <* eof) "" src of + Left err -> + assertBool + ("Expected parse error containing " ++ show expected ++ ", got:\n" ++ err) + (expected `isInfixOf` err) + Right got -> assertFailure ("Expected failure but parsed: " ++ show got) + nameResolutionFails :: String -> Assertion nameResolutionFails src = case runParserE (sc *> topDeclP <* eof) "" src of @@ -88,6 +102,23 @@ roundTripsStmt src = Right reparsed -> assertEqual ("round trip: " ++ rendered) parsed reparsed +roundTripsExp :: String -> Assertion +roundTripsExp src = + case runParserE (sc *> expP <* eof) "" src of + Left err -> assertFailure ("Initial parse error:\n" ++ err) + Right parsed -> + let rendered = TreePretty.pretty parsed + in case runParserE (sc *> expP <* eof) "" rendered of + Left err -> + assertFailure + ( "Pretty-printed expression did not parse:\n" + ++ rendered + ++ "\n" + ++ err + ) + Right reparsed -> + assertEqual ("round trip: " ++ rendered) parsed reparsed + roundTripsType :: String -> Assertion roundTripsType src = case runParserE (sc *> typeP <* eof) "" src of @@ -112,7 +143,8 @@ parserTests :: TestTree parserTests = testGroup "Parser" - [ typeTests, + [ identifierTests, + typeTests, predTests, patternTests, exprTests, @@ -131,6 +163,24 @@ word = TyCon "word" [] bool :: Ty bool = TyCon "bool" [] +identifierTests :: TestTree +identifierTests = + testGroup + "Identifiers" + [ testCase "leading underscore" $ + parsesAs identifier "_id" "_id", + testCase "multiple leading underscores and digits" $ + parsesAs identifier "__value2" "__value2", + testCase "underscore-prefixed expression name" $ + parsesAs expP "_value" (var "_value"), + testCase "boolean literals cannot be rebound as identifiers" $ do + parseFails identifier "true" + parseFails identifier "false" + parseFails + topDeclP + "function invalid(true: word, false: word) returns (word) { return true; }" + ] + typeTests :: TestTree typeTests = testGroup @@ -164,6 +214,21 @@ typeTests = typeP "word[] storage" (TyCon "storage" [TyCon "array" [word]]), + testCase "type suffixes may interleave arrays and locations" $ + parsesAs + typeP + "word[] memory[] storage" + ( TyCon + "storage" + [TyCon "array" [TyCon "memory" [TyCon "array" [word]]]] + ), + testCase "repeated data locations remain distinct type wrappers" $ + parsesAs + typeP + "word memory storage" + (TyCon "storage" [TyCon "memory" [word]]), + testCase "interleaved type suffixes survive source pretty-printing" $ + roundTripsType "word[] memory[] storage", testCase "function type" $ parsesAs typeP @@ -240,10 +305,17 @@ patternTests = "Patterns" [ testCase "wildcard" $ parsesAs patP "_" PWildcard, + testCase "underscore-prefixed name is not a wildcard" $ + parsesAs patP "_value" (Pat "_value" []), + testCase "wildcard cannot take constructor arguments" $ + parseFails patP "_(value)", testCase "integer literal" $ parsesAs patP "42" (PLit (IntLit 42)), testCase "string literal" $ parsesAs patP "\"hi\"" (PLit (StrLit "hi")), + testCase "boolean literal patterns" $ do + parsesAs patP "true" (Pat "true" []) + parsesAs patP "false" (Pat "false" []), testCase "constructor no args" $ parsesAs patP "True" (Pat "True" []), testCase "constructor with one arg" $ @@ -260,6 +332,8 @@ patternTests = parsesAs patP "Some(Pair(x,y))" (Pat "Some" [Pat "Pair" [Pat "x" [], Pat "y" []]]), testCase "dot pattern no args" $ parsesAs patP ".None" (PatDot "None" []), + testCase "dot boolean pattern" $ + parsesAs patP ".true" (PatDot "true" []), testCase "dot pattern with args" $ parsesAs patP ".Some(x)" (PatDot "Some" [Pat "x" []]) ] @@ -283,6 +357,11 @@ exprTests = parsesAs expP "0" (lit 0), testCase "string literal" $ parsesAs expP "\"hello\"" (Lit (StrLit "hello")), + testCase "string literal carriage-return escape" $ + parsesAs expP "\"line\\rbreak\"" (Lit (StrLit "line\rbreak")), + testCase "boolean literals" $ do + parsesAs expP "true" (var "true") + parsesAs expP "false" (var "false"), testCase "variable" $ parsesAs expP "x" (var "x"), testCase "nullary call" $ @@ -291,6 +370,45 @@ exprTests = parsesAs expP "f(1)" (ExpName Nothing "f" [lit 1]), testCase "binary call" $ parsesAs expP "f(1, 2)" (ExpName Nothing "f" [lit 1, lit 2]), + testCase "parenthesized name call keeps the direct-call source shape" $ + parsesAs expP "(f)(1)" (ExpName Nothing "f" [lit 1]), + testCase "call result can be called again" $ + parsesAs + expP + "f(1)(2)" + (ExpApply (ExpName Nothing "f" [lit 1]) [lit 2]), + testCase "postfix call supports zero and multiple arguments" $ do + parsesAs + expP + "f()()" + (ExpApply (ExpName Nothing "f" []) []) + parsesAs + expP + "f(1)(2, 3, 4)" + (ExpApply (ExpName Nothing "f" [lit 1]) [lit 2, lit 3, lit 4]), + testCase "indexed expression can be called" $ + parsesAs + expP + "callbacks[i](x)" + (ExpApply (ExpIndexed (var "callbacks") (var "i")) [var "x"]), + testCase "conditional expression can be called" $ + parsesAs + expP + "(condition ? f : g)(x)" + (ExpApply (ExpCond (var "condition") (var "f") (var "g")) [var "x"]), + testCase "lambda can be called immediately" $ + parsesAs + expP + "(lam(x: word) returns (word) { return x; })(1)" + (ExpApply (Lam [Typed False "x" word] [Return (var "x")] (Just word)) [lit 1]), + testCase "arbitrary postfix calls survive source pretty-printing" $ + mapM_ + roundTripsExp + [ "f(1)(2)", + "callbacks[i](x)", + "(condition ? f : g)(x)", + "(lam(x: word) returns (word) { return x; })(1)" + ], testCase "addition" $ parsesAs expP "1 + 2" (ExpPlus (lit 1) (lit 2)), testCase "subtraction" $ @@ -336,11 +454,25 @@ exprTests = parsesAs expP "x == y" (ExpEE (var "x") (var "y")), testCase "inequality" $ parsesAs expP "x != y" (ExpNE (var "x") (var "y")), + testCase "relational operators are non-associative" $ + parseFails expP "a < b < c", + testCase "equality operators are non-associative" $ + parseFails expP "a == b != c", testCase "arith tighter than comparison" $ parsesAs expP "a + b == c + d" (ExpEE (ExpPlus (var "a") (var "b")) (ExpPlus (var "c") (var "d"))), + testCase "bitwise and binds tighter than comparison" $ + parsesAs + expP + "a & b < c" + (ExpLT (ExpBAnd (var "a") (var "b")) (var "c")), + testCase "comparison binds tighter than equality" $ + parsesAs + expP + "a < b == c" + (ExpEE (ExpLT (var "a") (var "b")) (var "c")), testCase "logical and" $ parsesAs expP "x && y" (ExpLAnd (var "x") (var "y")), testCase "logical or" $ @@ -411,6 +543,8 @@ exprTests = (ExpName Nothing "pair" [var "a", ExpName Nothing "pair" [var "b", var "c"]]), testCase "dot name without args" $ parsesAs expP ".None" (ExpDotName "None" []), + testCase "dot boolean name" $ + parsesAs expP ".true" (ExpDotName "true" []), testCase "dot name with args" $ parsesAs expP ".Some(1)" (ExpDotName "Some" [lit 1]), testCase "lambda no params" $ @@ -427,7 +561,60 @@ exprTests = parsesAs expP "lam(x:word) { return x; }" - (Lam [Typed False "x" word] [Return (var "x")] Nothing) + (Lam [Typed False "x" word] [Return (var "x")] Nothing), + testCase "name resolution lowers arbitrary calls and packs their arguments" $ + case runParserE + (sc *> topDeclP <* eof) + "" + ( "function packing(x: word, y: word, z: word) {" + ++ " (lam() returns (word) { return 1; })();" + ++ " (lam(a: word) returns (word) { return a; })(x);" + ++ " (lam(a: word, b: word, c: word) returns (word) { return a; })(x, y, z);" + ++ " }" + ) of + Left err -> assertFailure ("Parse error:\n" ++ err) + Right parsed -> do + resolved <- nameResolution (CompUnit [] [parsed]) + case resolved of + Right + ( Resolved.CompUnit + _ + [ Resolved.TFunDef + ( Resolved.FunDef + _ + _ + [ ResolvedStmt.StmtExp + (ResolvedStmt.Call Nothing invoke0 [ResolvedStmt.Lam _ _ _, packed0]), + ResolvedStmt.StmtExp + (ResolvedStmt.Call Nothing invoke1 [ResolvedStmt.Lam _ _ _, packed1]), + ResolvedStmt.StmtExp + (ResolvedStmt.Call Nothing invokeMany [ResolvedStmt.Lam _ _ _, packedMany]) + ] + ) + ] + ) -> do + let expectedInvoke = QualName "invokable" "invoke" + assertEqual "zero-argument invoke target" expectedInvoke invoke0 + assertEqual "single-argument invoke target" expectedInvoke invoke1 + assertEqual "multi-argument invoke target" expectedInvoke invokeMany + case packed0 of + ResolvedStmt.Con "()" [] -> pure () + other -> assertFailure ("Unexpected zero-argument packing: " ++ show other) + case packed1 of + ResolvedStmt.Var "x" -> pure () + other -> assertFailure ("Unexpected single-argument packing: " ++ show other) + case packedMany of + ResolvedStmt.Con + "pair" + [ ResolvedStmt.Var "x", + ResolvedStmt.Con + "pair" + [ResolvedStmt.Var "y", ResolvedStmt.Var "z"] + ] -> + pure () + other -> assertFailure ("Unexpected multi-argument packing: " ++ show other) + Right got -> assertFailure ("Unexpected name-resolution shape: " ++ show got) + Left err -> assertFailure ("Name resolution failed: " ++ show err) ] -- | Identifiers that start with a keyword (e.g. `enumValue`, which begins with @@ -503,6 +690,18 @@ stmtTests = Nothing (ExpName Nothing "readResult" []) ), + testCase "tuple destructuring distinguishes a leading-underscore binder from a wildcard" $ + parsesAs + stmtP + "let (_value, _) = readResult();" + ( LetPattern + False + (Pat "pair" [Pat "_value" [], PWildcard]) + Nothing + (ExpName Nothing "readResult" []) + ), + testCase "tuple destructuring rejects duplicate leading-underscore binders" $ + parseFails stmtP "let (_value, _value) = readResult();", testCase "comptime tuple destructuring keeps its binding modifier" $ parsesAs stmtP @@ -519,8 +718,10 @@ stmtTests = parsesAs stmtP "return 0;" (Return (lit 0)), testCase "return expression" $ parsesAs stmtP "return x + 1;" (Return (ExpPlus (var "x") (lit 1))), - testCase "bare return produces the unit expression" $ - parsesAs stmtP "return;" (Return unitExp), + testCase "bare return remains distinct from an explicit unit return" $ + parsesAs stmtP "return;" BareReturn, + testCase "explicit unit return remains an expression return" $ + parsesAs stmtP "return ();" (Return unitExp), testCase "assignment" $ parsesAs stmtP "x = 1;" (Assign (var "x") (lit 1)), testCase "plus-assign" $ @@ -560,6 +761,21 @@ stmtTests = (Assign (var "i") (ExpPlus (var "i") (lit 1))) [] ), + testCase "for initializer accepts tuple destructuring let" $ + parsesAs + stmtP + "for (let (left, right): (word, bool) = readResult(); keepGoing; ) { }" + ( For + ( LetPattern + False + (Pat "pair" [Pat "left" [], Pat "right" []]) + (Just (TyCon "pair" [word, bool])) + (ExpName Nothing "readResult" []) + ) + (var "keepGoing") + EmptyStmt + [] + ), testCase "for loop with empty init and post" $ parsesAs stmtP @@ -604,11 +820,88 @@ stmtTests = (Unchecked [Let False "x" Nothing (Just (lit 1))]), testCase "unchecked block survives source pretty-printing" $ roundTripsStmt "unchecked { let x = 1; }", - testCase "bare revert lowers to the revert operation" $ + testCase "bare revert remains distinct in the source AST" $ parsesAs stmtP "revert;" - (StmtExp (ExpName Nothing "revert" [])), + Revert, + testCase "revert is reserved for the statement form" $ do + parseFails identifier "revert" + parseFails patP "revert" + parsesAs stmtP "revert;" Revert, + testCase "bare revert survives source pretty-printing" $ + roundTripsStmt "revert;", + testCase "Yul control-flow keywords remain statements" $ + parsesAs + stmtP + "assembly { break continue leave }" + (Asm [YBreak, YContinue, YLeave]), + testCase "Yul function declarations preserve arguments and returns" $ + parsesAs + stmtP + "assembly { function pair(x, y) -> left, right { left, right := pair(x, y) } }" + ( Asm + [ YFun + "pair" + ["x", "y"] + (Just ["left", "right"]) + [ YAssign + ["left", "right"] + (YCall "pair" [YIdent "x", YIdent "y"]) + ] + ] + ), + testCase "Yul control flow accepts booleans and dollar identifiers" $ + parsesAs + stmtP + ( "assembly {" + ++ " let $flag := true" + ++ " if $flag { continue }" + ++ " for {} false {} { break }" + ++ " switch $flag case true { leave } default {}" + ++ " }" + ) + ( Asm + [ YLet ["$flag"] (Just (YLit YulTrue)), + YIf (YIdent "$flag") [YContinue], + YFor [] (YLit YulFalse) [] [YBreak], + YSwitch + (YIdent "$flag") + [(YulTrue, [YLeave])] + (Just []) + ] + ), + testCase "Yul keyword prefixes remain ordinary identifiers" $ + parsesAs + stmtP + "assembly { let x := trueValue breakFoo() functionFoo() }" + ( Asm + [ YLet ["x"] (Just (YIdent "trueValue")), + YExp (YCall "breakFoo" []), + YExp (YCall "functionFoo" []) + ] + ), + testCase "Yul metadata expressions accept backtick and interpolation spellings" $ do + parsesAs + stmtP + "assembly { let first := `backtickHole` let second := ${interpolationHole} }" + ( Asm + [ YLet ["first"] (Just (YMeta "backtickHole")), + YLet ["second"] (Just (YMeta "interpolationHole")) + ] + ), + testCase "Yul let requires at least one name" $ + parseFails + stmtP + "assembly { let := 1 }", + testCase "Yul assignment requires at least one name" $ + parseFails + stmtP + "assembly { := 1 }", + testCase "Yul function return arrow requires at least one name" $ + parseFails + stmtP + "assembly { function invalid() -> {} }", testCase "match one equation" $ parsesAs stmtP @@ -765,7 +1058,18 @@ declTests = ( FunDef False (Signature [] [] "data" [] False (Just (TyCon "()" [])) False) - [Return unitExp] + [BareReturn] + ) + ), + testCase "underscore-prefixed function and parameter names parse" $ + parsesAs + topDeclP + "function _id(_value: word) returns (word) { return _value; }" + ( TFunDef + ( FunDef + False + (Signature [] [] "_id" [Typed False "_value" word] False (Just word) False) + [Return (var "_value")] ) ), testCase "empty enum" $ @@ -794,15 +1098,21 @@ declTests = [Constr "Some" [TyCon "a" []], Constr "None" []] ) ), - testCase "user-defined value type" $ + testCase "duplicate top-level struct fields fail name resolution" $ + nameResolutionFails + "struct Pair { value: word; value: bool; }", + testCase "duplicate nested struct fields fail name resolution" $ + nameResolutionFails + "contract C { struct Pair { value: word; value: bool; } }", + testCase "transparent type alias" $ parsesAs topDeclP - "type Word is word;" + "alias Word = word;" (TSym (TySym "Word" [] word)), - testCase "generic user-defined value type" $ + testCase "generic transparent type alias" $ parsesAs topDeclP - "type Pair is (a, b);" + "alias Pair = (a, b);" ( TSym ( TySym "Pair" @@ -810,6 +1120,15 @@ declTests = (pairTy (TyCon "a" []) (TyCon "b" [])) ) ), + testCase "alias is reserved as a declaration keyword" $ + parseFails identifier "alias", + testCase "nominal type syntax is not treated as a transparent alias" $ + parseFailsContaining + topDeclP + "user-defined value types declared with `type ... is ...` are not yet implemented" + "type Word is word;", + testCase "transparent aliases survive source pretty-printing" $ + roundTripsTopDecl "alias Pair = (a, b);", testCase "trait with one method" $ parsesAs topDeclP @@ -1022,31 +1341,31 @@ declTests = ( FunDef False (SignatureWithSyntax [] [] "pureFn" [] Nothing [MutabilityModifier MutabilityPure]) - [Return unitExp] + [BareReturn] ), CFunDecl ( FunDef False (SignatureWithSyntax [] [] "viewFn" [] Nothing [MutabilityModifier MutabilityView]) - [Return unitExp] + [BareReturn] ), CFunDecl ( FunDef False (SignatureWithSyntax [] [] "privateFn" [] Nothing [VisibilityModifier VisibilityPrivate]) - [Return unitExp] + [BareReturn] ), CFunDecl ( FunDef False (SignatureWithSyntax [] [] "internalFn" [] Nothing [VisibilityModifier VisibilityInternal]) - [Return unitExp] + [BareReturn] ), CFunDecl ( FunDef True (SignatureWithSyntax [] [] "externalFn" [] Nothing [VisibilityModifier VisibilityExternal]) - [Return unitExp] + [BareReturn] ) ] ) @@ -1059,7 +1378,7 @@ declTests = ( Contract "C" [] - [CConstrDecl (Constructor [Typed False "x" word] [Return unitExp] False)] + [CConstrDecl (Constructor [Typed False "x" word] [BareReturn] False)] ) ), testCase "payable modifier follows constructor parameters" $ @@ -1070,7 +1389,7 @@ declTests = ( Contract "C" [] - [CConstrDecl (Constructor [Typed False "x" word] [Return unitExp] True)] + [CConstrDecl (Constructor [Typed False "x" word] [BareReturn] True)] ) ), testCase "external payable fallback" $ @@ -1094,7 +1413,7 @@ declTests = MutabilityModifier MutabilityPayable ] ) - [Return unitExp] + [BareReturn] ) ] ) @@ -1111,6 +1430,36 @@ declTests = parseFails topDeclP "contract C { function f() pure view { return; } }", + testCase "module functions retain pure and view without contract visibility" $ do + parsesAs + topDeclP + "function pureFn() pure { return; }" + ( TFunDef + ( FunDef + False + (SignatureWithSyntax [] [] "pureFn" [] Nothing [MutabilityModifier MutabilityPure]) + [BareReturn] + ) + ) + parsesAs + topDeclP + "function viewFn() view { return; }" + ( TFunDef + ( FunDef + False + (SignatureWithSyntax [] [] "viewFn" [] Nothing [MutabilityModifier MutabilityView]) + [BareReturn] + ) + ), + testCase "module functions reject contract visibility and payable" $ + mapM_ + (parseFails topDeclP) + [ "function publicFn() public { return; }", + "function externalFn() external { return; }", + "function internalFn() internal { return; }", + "function privateFn() private { return; }", + "function payableFn() payable { return; }" + ], -- Contract visibility modifiers are not meaningful on impl methods. testCase "public instance method fails" $ parseFails @@ -1147,7 +1496,11 @@ importTests = ( ImportOnly (ExternalPath "ext" (QualName "foo" "bar")) (SelectItems [SelectItem "foo", SelectItemAs "bar" "baz"] []) - ) + ), + testCase "selective import requires at least one item" $ + parseFails importP "import {} from std;", + testCase "hiding clause requires at least one item" $ + parseFails importP "import {foo} from std hiding {};" ] pragmaTests :: TestTree @@ -1243,6 +1596,11 @@ declarationShellTests = topDeclP "struct Pair { left: a; right: word; }" (TDataDef (StructTy "Pair" [TyCon "a" []] ["left", "right"] [TyCon "a" [], word])), + testCase "underscore-prefixed struct and field names parse" $ + parsesAs + topDeclP + "struct _Record { _value: word; }" + (TDataDef (StructTy "_Record" [] ["_value"] [word])), testCase "contract-local struct is a data declaration" $ parsesAs topDeclP @@ -1283,6 +1641,64 @@ declarationShellTests = parseFails topDeclP "interface Oracle { function read() external returns (word) { return 0; } }", + testCase "interface rejects omitted function visibility" $ + parseFailsContaining + topDeclP + "exactly one `external`" + "interface Oracle { function read() returns (word); }", + testCase "interface rejects public function visibility" $ + parseFailsContaining + topDeclP + "exactly one `external`" + "interface Oracle { function read() public returns (word); }", + testCase "interface rejects private function visibility" $ + parseFailsContaining + topDeclP + "exactly one `external`" + "interface Oracle { function read() private returns (word); }", + testCase "interface rejects internal function visibility" $ + parseFailsContaining + topDeclP + "exactly one `external`" + "interface Oracle { function read() internal returns (word); }", + testCase "interface accepts external pure and payable signatures" $ do + case runParserE + (sc *> topDeclP <* eof) + "" + ( "interface Oracle {" + ++ " function compute() external pure returns (word);" + ++ " function deposit() external payable;" + ++ " }" + ) of + Left err -> assertFailure ("Parse error:\n" ++ err) + Right + ( TContr + ( ContractShell + InterfaceKind + _ + _ + [ CSignatureDecl + True + (SignatureWithSyntax _ _ _ _ _ computeModifiers), + CSignatureDecl + True + (SignatureWithSyntax _ _ _ _ _ depositModifiers) + ] + ) + ) -> do + assertEqual + "pure interface signature modifiers" + [ VisibilityModifier VisibilityExternal, + MutabilityModifier MutabilityPure + ] + computeModifiers + assertEqual + "payable interface signature modifiers" + [ VisibilityModifier VisibilityExternal, + MutabilityModifier MutabilityPayable + ] + depositModifiers + Right got -> assertFailure ("Unexpected interface shape: " ++ show got), testCase "interface rejects state fields" $ parseFails topDeclP "interface Oracle { value: word; }", testCase "library accepts contract-like fields, structs, and functions" $ @@ -1332,7 +1748,27 @@ declarationShellTests = testCase "contract fields and functions with distinct names do not collide" $ nameResolutionSucceeds "contract C { value: word; function read() returns (word) { return value; } }", - testCase "name resolution lowers a struct to a one-constructor data type" $ + testCase "underscore-prefixed function and parameter names resolve" $ + nameResolutionSucceeds + "function _id(_value: word) returns (word) { return _value; }", + testCase "underscore-prefixed struct and field names resolve" $ + nameResolutionSucceeds + "struct _Record { _value: word; }", + testCase "underscore-prefixed match binders resolve" $ + nameResolutionSucceeds + ( "function select(_input: word) returns (word) {" + ++ " match (_input) { case _value { return _value; } }" + ++ " return 0;" + ++ " }" + ), + testCase "wildcard patterns do not bind the standalone underscore" $ + nameResolutionFails + ( "function select(_input: word) returns (word) {" + ++ " match (_input) { case _ { return _; } }" + ++ " return 0;" + ++ " }" + ), + testCase "name resolution preserves struct metadata and semantic pretty syntax" $ case runParserE (sc *> topDeclP <* eof) "" "struct Box { value: word; }" of Left err -> assertFailure ("Parse error:\n" ++ err) Right parsed -> do @@ -1342,12 +1778,239 @@ declarationShellTests = ( Resolved.CompUnit _ [ Resolved.TDataDef - (Resolved.DataTy "Box" [] [Resolved.Constr (QualName "Box" "Box") [_]]) + dt@( Resolved.DataTyWithKind + (Resolved.StructKind ["value"]) + "Box" + [] + [Resolved.Constr (QualName "Box" "Box") [ResolvedTy.TyCon "word" []]] + ) ] - ) -> - pure () + ) -> do + let rendered = SolcorePretty.pretty dt + assertBool + ("semantic struct pretty output lost its declaration kind:\n" ++ rendered) + ("struct Box" `isInfixOf` rendered) + assertBool + ("semantic struct pretty output lost its named field:\n" ++ rendered) + ("value: word;" `isInfixOf` rendered) Right got -> assertFailure ("Unexpected lowering result: " ++ show got) Left err -> assertFailure ("Name resolution failed: " ++ show err), + testCase "value member reads retain their receiver with and without name collisions" $ + case runParserE + (sc *> topDeclP <* eof) + "" + ( "contract C {" + ++ " struct Pair { x: word; }" + ++ " function collision(p: Pair, x: word) returns (word) { return p.x; }" + ++ " function noCollision(p: Pair) returns (word) { return p.x; }" + ++ " }" + ) of + Left err -> assertFailure ("Parse error:\n" ++ err) + Right parsed -> do + resolved <- nameResolution (CompUnit [] [parsed]) + case resolved of + Right + ( Resolved.CompUnit + _ + [ Resolved.TContr + ( Resolved.ContractWithKind + _ + _ + _ + [ _, + Resolved.CFunDecl + ( Resolved.FunDef + _ + _ + [ResolvedStmt.Return (ResolvedStmt.FieldAccess (Just (ResolvedStmt.Var "p")) "x")] + ), + Resolved.CFunDecl + ( Resolved.FunDef + _ + _ + [ResolvedStmt.Return (ResolvedStmt.FieldAccess (Just (ResolvedStmt.Var "p")) "x")] + ) + ] + ) + ] + ) -> + pure () + Right got -> + assertFailure + ("value receiver was dropped during name resolution: " ++ show got) + Left err -> + assertFailure ("Name resolution failed: " ++ show err), + testCase "type qualifiers still win over same-named parameters" $ + case runParserE + (sc *> topDeclP <* eof) + "" + ( "contract C {" + ++ " enum Choice { Left }" + ++ " function pick(Left: word) returns (Choice) { return Choice.Left; }" + ++ " }" + ) of + Left err -> assertFailure ("Parse error:\n" ++ err) + Right parsed -> do + resolved <- nameResolution (CompUnit [] [parsed]) + case resolved of + Right + ( Resolved.CompUnit + _ + [ Resolved.TContr + ( Resolved.ContractWithKind + _ + _ + _ + [ _, + Resolved.CFunDecl + ( Resolved.FunDef + _ + _ + [ ResolvedStmt.Return + ( ResolvedStmt.Con + (QualName (QualName "C" "Choice") "Left") + [] + ) + ] + ) + ] + ) + ] + ) -> + pure () + Right got -> + assertFailure + ("type qualifier was captured by a parameter: " ++ show got) + Left err -> + assertFailure ("Name resolution failed: " ++ show err), + testCase "contract names qualify contract-local constructors" $ + nameResolutionSucceeds + ( "contract C {" + ++ " struct S { value: word; }" + ++ " function make() returns (S) { return C.S.S(1); }" + ++ " }" + ), + testCase "resolved pretty-printing preserves one named tuple return item" $ + case runParserE + (sc *> topDeclP <* eof) + "" + ( "function pairResult() returns (result: (word, bool)) {" + ++ " result = (1, true);" + ++ " return;" + ++ " }" + ) of + Left err -> assertFailure ("Parse error:\n" ++ err) + Right parsed -> do + resolved <- nameResolution (CompUnit [] [parsed]) + case resolved of + Left err -> + assertFailure ("Name resolution failed: " ++ show err) + Right (Resolved.CompUnit _ [resolvedDecl]) -> do + let rendered = SolcorePretty.pretty resolvedDecl + case runParserE (sc *> topDeclP <* eof) "" rendered of + Left err -> + assertFailure + ( "Resolved pretty output did not parse:\n" + ++ rendered + ++ "\n" + ++ err + ) + Right reparsed -> do + reresolved <- nameResolution (CompUnit [] [reparsed]) + case reresolved of + Right + ( Resolved.CompUnit + _ + [ Resolved.TFunDef + (Resolved.FunDef _ signature _) + ] + ) -> + assertEqual + "return name and tuple boundary survive semantic pretty-printing" + [ Resolved.SignatureReturnItem + False + (Just "result") + ( ResolvedTy.TyCon + "pair" + [ ResolvedTy.TyCon "word" [], + ResolvedTy.TyCon "bool" [] + ] + ) + ] + (Resolved.sigReturnItems signature) + Right got -> + assertFailure + ("Unexpected re-resolved output: " ++ show got) + Left err -> + assertFailure + ( "Resolved pretty output failed name resolution:\n" + ++ rendered + ++ "\n" + ++ show err + ) + Right got -> + assertFailure ("Unexpected resolved output: " ++ show got), + testCase "resolved pretty-printing keeps bare contract fields reusable" $ + case runParserE + (sc *> topDeclP <* eof) + "" + ( "contract C {" + ++ " value: word;" + ++ " function read() returns (word) { return value; }" + ++ " }" + ) of + Left err -> assertFailure ("Parse error:\n" ++ err) + Right parsed -> do + resolved <- nameResolution (CompUnit [] [parsed]) + case resolved of + Left err -> + assertFailure ("Name resolution failed: " ++ show err) + Right (Resolved.CompUnit _ [resolvedDecl]) -> do + let rendered = SolcorePretty.pretty resolvedDecl + assertBool + ("semantic pretty output invented an undefined receiver:\n" ++ rendered) + (not ("this." `isInfixOf` rendered)) + case runParserE (sc *> topDeclP <* eof) "" rendered of + Left err -> + assertFailure + ( "Resolved pretty output did not parse:\n" + ++ rendered + ++ "\n" + ++ err + ) + Right reparsed -> do + reresolved <- nameResolution (CompUnit [] [reparsed]) + case reresolved of + Left err -> + assertFailure + ( "Resolved pretty output failed name resolution:\n" + ++ rendered + ++ "\n" + ++ show err + ) + Right _ -> pure () + Right got -> + assertFailure ("Unexpected resolved output: " ++ show got), + testCase "value member calls fail explicitly during name resolution" $ + case runParserE + (sc *> topDeclP <* eof) + "" + ( "contract C {" + ++ " struct Pair { x: word; }" + ++ " function bad(p: Pair) returns (word) { return p.x(); }" + ++ " }" + ) of + Left err -> assertFailure ("Parse error:\n" ++ err) + Right parsed -> do + resolved <- nameResolution (CompUnit [] [parsed]) + case resolved of + Left err -> + assertBool + ("expected SC0124, got:\n" ++ compilerErrorText err) + ("SC0124" `isInfixOf` compilerErrorText err) + Right got -> + assertFailure + ("value member call was silently lowered: " ++ show got), testCase "name resolution preserves an interface signature without a body" $ case runParserE (sc *> topDeclP <* eof) "" "interface I { function f() external; }" of Left err -> assertFailure ("Parse error:\n" ++ err) @@ -1358,70 +2021,157 @@ declarationShellTests = ( Resolved.CompUnit _ [ Resolved.TContr - (Resolved.Contract "I" [] [Resolved.CSignatureDecl True _]) + ( Resolved.ContractWithKind + Resolved.InterfaceKind + "I" + [] + [Resolved.CSignatureDecl True signature] + ) ] ) -> - pure () + assertEqual + "exact interface modifiers survive name resolution" + [ Resolved.VisibilityModifier Resolved.VisibilityExternal + ] + (Resolved.sigModifiers signature) + Right got -> assertFailure ("Unexpected lowering result: " ++ show got) + Left err -> assertFailure ("Name resolution failed: " ++ show err), + testCase "name resolution preserves a library declaration kind" $ + case runParserE (sc *> topDeclP <* eof) "" "library L { function f() internal { return; } }" of + Left err -> assertFailure ("Parse error:\n" ++ err) + Right parsed -> do + resolved <- nameResolution (CompUnit [] [parsed]) + case resolved of + Right + ( Resolved.CompUnit + _ + [ Resolved.TContr + ( Resolved.ContractWithKind + Resolved.LibraryKind + "L" + [] + [Resolved.CFunDecl _] + ) + ] + ) -> + pure () Right got -> assertFailure ("Unexpected lowering result: " ++ show got) Left err -> assertFailure ("Name resolution failed: " ++ show err), testCase "name resolution lowers source modifiers and named returns" $ - case - runParserE - (sc *> topDeclP <* eof) - "" - "contract C { function pair() external payable returns (left: word, right: bool) { return (1, 0); } }" - of - Left err -> assertFailure ("Parse error:\n" ++ err) - Right parsed -> do - resolved <- nameResolution (CompUnit [] [parsed]) - case resolved of - Right - ( Resolved.CompUnit - _ - [ Resolved.TContr - (Resolved.Contract "C" [] [Resolved.CFunDecl (Resolved.FunDef isPublic sig _)]) - ] - ) -> do - assertBool "external lowers to the semantic public bit" isPublic - assertBool "payable lowers to the semantic payable bit" (Resolved.sigPayable sig) - assertEqual - "return names are discarded only at semantic lowering" - ( Just - ( ResolvedTy.TyCon - "pair" - [ResolvedTy.TyCon "word" [], ResolvedTy.TyCon "bool" []] - ) + case runParserE + (sc *> topDeclP <* eof) + "" + "contract C { function pair() external payable returns (left: word, right: bool) { return (1, 0); } }" of + Left err -> assertFailure ("Parse error:\n" ++ err) + Right parsed -> do + resolved <- nameResolution (CompUnit [] [parsed]) + case resolved of + Right + ( Resolved.CompUnit + _ + [ Resolved.TContr + ( Resolved.ContractWithKind + Resolved.ContractKind + "C" + [] + [Resolved.CFunDecl (Resolved.FunDef isPublic sig _)] + ) + ] + ) -> do + assertBool "external lowers to the semantic public bit" isPublic + assertBool "payable lowers to the semantic payable bit" (Resolved.sigPayable sig) + assertEqual + "return items still aggregate to the backend result type" + ( Just + ( ResolvedTy.TyCon + "pair" + [ResolvedTy.TyCon "word" [], ResolvedTy.TyCon "bool" []] + ) + ) + (Resolved.sigReturn sig) + assertEqual + "semantic lowering preserves return-item names and comptime modes" + [(Just "left", False), (Just "right", False)] + [ ( Resolved.signatureReturnItemName returnItem, + Resolved.signatureReturnItemComptime returnItem + ) + | returnItem <- Resolved.sigReturnItems sig + ] + Right got -> assertFailure ("Unexpected lowering result: " ++ show got) + Left err -> assertFailure ("Name resolution failed: " ++ show err), + testCase "name resolution deliberately lowers supported internal function types" $ + case runParserE + (sc *> topDeclP <* eof) + "" + "function apply(f: function(word) internal returns (word, bool), x: word) returns (word, bool) { return f(x); }" of + Left err -> assertFailure ("Parse error:\n" ++ err) + Right parsed -> do + resolved <- nameResolution (CompUnit [] [parsed]) + case resolved of + Right + ( Resolved.CompUnit + _ + [ Resolved.TFunDef + ( Resolved.FunDef + _ + signature + _ + ) + ] + ) -> do + callbackTy <- + case Resolved.sigParams signature of + ResolvedStmt.Typed _ "f" ty : _ -> pure ty + params -> + assertFailure ("Unexpected resolved parameters: " ++ show params) + assertEqual + "supported internal function types lower to the existing arrow representation" + ( ResolvedTy.funtype + [ResolvedTy.TyCon "word" []] + ( ResolvedTy.TyCon + "pair" + [ ResolvedTy.TyCon "word" [], + ResolvedTy.TyCon "bool" [] + ] ) - (Resolved.sigReturn sig) - Right got -> assertFailure ("Unexpected lowering result: " ++ show got) - Left err -> assertFailure ("Name resolution failed: " ++ show err), - testCase "name resolution lowers a zero-arity function type explicitly" $ - case - runParserE - (sc *> topDeclP <* eof) - "" - "type Callback is function() external returns (word);" - of - Left err -> assertFailure ("Parse error:\n" ++ err) - Right parsed -> do - resolved <- nameResolution (CompUnit [] [parsed]) - case resolved of - Right - ( Resolved.CompUnit - _ - [Resolved.TSym (Resolved.TySym "Callback" [] callbackTy)] - ) -> - assertEqual - "the existing semantic AST represents a nullary function by its result" - (ResolvedTy.TyCon "word" []) - callbackTy - Right got -> assertFailure ("Unexpected lowering result: " ++ show got) - Left err -> assertFailure ("Name resolution failed: " ++ show err), + ) + callbackTy + Right got -> assertFailure ("Unexpected lowering result: " ++ show got) + Left err -> assertFailure ("Name resolution failed: " ++ show err), + testCase "name resolution rejects external function types instead of treating them as internal" $ + assertFunctionTypeResolutionError + "external function types are not supported" + "function bad(f: function(word) external returns (word)) returns (word) { return 0; }", + testCase "name resolution rejects nullary function types instead of collapsing them to the result" $ + assertFunctionTypeResolutionError + "zero-parameter function types are not supported" + "function bad(f: function() internal returns (word)) returns (word) { return 0; }", testCase "new declaration shells survive source pretty-printing" $ mapM_ roundTripsTopDecl [ "struct Pair { x: word; y: bool; }", + "struct _Record { _value: word; }", + "function _id(_value: word) returns (word) { return _value; }", "interface Oracle { function read(key: word) external view returns (word); }", "library Math { function twice(x: word) internal pure returns (word) { return x + x; } }" ] ] + +assertFunctionTypeResolutionError :: String -> String -> Assertion +assertFunctionTypeResolutionError expectedMessage source = + case runParserE (sc *> topDeclP <* eof) "" source of + Left err -> assertFailure ("Parse error:\n" ++ err) + Right parsed -> do + resolved <- nameResolution (CompUnit [] [parsed]) + case resolved of + Left err -> do + let rendered = compilerErrorText err + assertBool + ("Expected SC0122 diagnostic, got:\n" ++ rendered) + ("SC0122" `isInfixOf` rendered) + assertBool + ("Expected diagnostic message " ++ show expectedMessage ++ ", got:\n" ++ rendered) + (expectedMessage `isInfixOf` rendered) + Right got -> + assertFailure + ("Expected name-resolution failure but resolved: " ++ show got) diff --git a/test/diagnostics/missing-signature.solc b/test/diagnostics/missing-signature.solc index 059ca49d1..87a8a745f 100644 --- a/test/diagnostics/missing-signature.solc +++ b/test/diagnostics/missing-signature.solc @@ -1,3 +1,3 @@ -function foo() { +function foo(value) { return 1; } diff --git a/test/examples/cases/bare-revert.solc b/test/examples/cases/bare-revert.solc new file mode 100644 index 000000000..5e864a610 --- /dev/null +++ b/test/examples/cases/bare-revert.solc @@ -0,0 +1,3 @@ +function abortWithoutImports() returns (word) { + revert; +} diff --git a/test/examples/cases/fresh-pat-arg-synonym.solc b/test/examples/cases/fresh-pat-arg-synonym.solc index 8a5edefc0..587570d03 100644 --- a/test/examples/cases/fresh-pat-arg-synonym.solc +++ b/test/examples/cases/fresh-pat-arg-synonym.solc @@ -1,4 +1,4 @@ -type W is word; +alias W = word; function f(x:W) returns (W) { return x; } diff --git a/test/examples/cases/instance-synonym-int.solc b/test/examples/cases/instance-synonym-int.solc index 3999932a3..6fc0a48ae 100644 --- a/test/examples/cases/instance-synonym-int.solc +++ b/test/examples/cases/instance-synonym-int.solc @@ -1,4 +1,4 @@ -type W is word; +alias W = word; trait FromWord { function fromWord(x:word) returns (i); diff --git a/test/examples/cases/instance-synonym.solc b/test/examples/cases/instance-synonym.solc index 15655d4c0..971e04632 100644 --- a/test/examples/cases/instance-synonym.solc +++ b/test/examples/cases/instance-synonym.solc @@ -1,4 +1,4 @@ -type W is word; +alias W = word; trait IdTy { function id(x:self) returns (self); diff --git a/test/examples/cases/overlap-synonym-detected.solc b/test/examples/cases/overlap-synonym-detected.solc index 48edb5432..6c20aabf2 100644 --- a/test/examples/cases/overlap-synonym-detected.solc +++ b/test/examples/cases/overlap-synonym-detected.solc @@ -1,4 +1,4 @@ -type W is word; +alias W = word; trait IdTy { function id(x:self) returns (self); diff --git a/test/examples/cases/overlap-synonym-missed-order.solc b/test/examples/cases/overlap-synonym-missed-order.solc index 48d6c64a6..497ffb90f 100644 --- a/test/examples/cases/overlap-synonym-missed-order.solc +++ b/test/examples/cases/overlap-synonym-missed-order.solc @@ -1,4 +1,4 @@ -type W is word; +alias W = word; trait IdTy { function id(x:self) returns (self); diff --git a/test/examples/cases/overlap-synonym-missed-two-synonyms.solc b/test/examples/cases/overlap-synonym-missed-two-synonyms.solc index b8d7fe03b..a12c50179 100644 --- a/test/examples/cases/overlap-synonym-missed-two-synonyms.solc +++ b/test/examples/cases/overlap-synonym-missed-two-synonyms.solc @@ -1,5 +1,5 @@ -type W is word; -type V is word; +alias W = word; +alias V = word; trait IdTy { function id(x:self) returns (self); diff --git a/test/examples/cases/synonym-arity-mismatch.solc b/test/examples/cases/synonym-arity-mismatch.solc index 294d3cfff..39c42e928 100644 --- a/test/examples/cases/synonym-arity-mismatch.solc +++ b/test/examples/cases/synonym-arity-mismatch.solc @@ -1,4 +1,4 @@ -type F is pair; +alias F = pair; function main() returns (F) { return pair(42, 0); diff --git a/test/examples/cases/synonym-basic.solc b/test/examples/cases/synonym-basic.solc index b5c9f21ca..b4f46fea3 100644 --- a/test/examples/cases/synonym-basic.solc +++ b/test/examples/cases/synonym-basic.solc @@ -1,5 +1,5 @@ -type Uint is word; -type Point is pair; +alias Uint = word; +alias Point = pair; function useUint(x: Uint) returns (word) { return x; @@ -18,4 +18,4 @@ function getX(p: Point) returns (word) { function main() returns (word) { let p: Point = makePoint(10, 20); return getX(p); -} \ No newline at end of file +} diff --git a/test/examples/cases/synonym-in-function.solc b/test/examples/cases/synonym-in-function.solc index e01493b89..81fd97184 100644 --- a/test/examples/cases/synonym-in-function.solc +++ b/test/examples/cases/synonym-in-function.solc @@ -1,6 +1,6 @@ // Synonyms in function parameter and return types -type Int is word; -type Point is pair; +alias Int = word; +alias Point = pair; function add(a: Int, b: Int) returns (Int) { return a; @@ -27,4 +27,4 @@ function main() returns (word) { let b: Int = 20; let p: Point = makePoint(a, b); return getX(p); -} \ No newline at end of file +} diff --git a/test/examples/cases/synonym-long-cycle.solc b/test/examples/cases/synonym-long-cycle.solc index d34f5d91c..bd137a245 100644 --- a/test/examples/cases/synonym-long-cycle.solc +++ b/test/examples/cases/synonym-long-cycle.solc @@ -1,8 +1,8 @@ // Longer recursive cycle should be rejected -type A is B; -type B is C; -type C is A; +alias A = B; +alias B = C; +alias C = A; function main() returns (word) { return 0; -} \ No newline at end of file +} diff --git a/test/examples/cases/synonym-nested.solc b/test/examples/cases/synonym-nested.solc index b15f6c0a7..985662421 100644 --- a/test/examples/cases/synonym-nested.solc +++ b/test/examples/cases/synonym-nested.solc @@ -1,11 +1,11 @@ // Deeply nested synonyms (synonym of synonym of synonym) -type Word1 is word; -type Word2 is Word1; -type Word3 is Word2; +alias Word1 = word; +alias Word2 = Word1; +alias Word3 = Word2; -type Pair1 is pair; -type Pair2 is Pair1; -type Pair3 is Pair2; +alias Pair1 = pair; +alias Pair2 = Pair1; +alias Pair3 = Pair2; function useWord3(x: Word3) returns (word) { return x; diff --git a/test/examples/cases/synonym-param.solc b/test/examples/cases/synonym-param.solc index 4cd0eee7f..9f863dd20 100644 --- a/test/examples/cases/synonym-param.solc +++ b/test/examples/cases/synonym-param.solc @@ -1,5 +1,5 @@ -type MyPair is pair; -type IntPair is MyPair; +alias MyPair = pair; +alias IntPair = MyPair; function makePair(x: word, y: word) returns (MyPair) { return pair(x, y); diff --git a/test/examples/cases/synonym-recursive.solc b/test/examples/cases/synonym-recursive.solc index 0b0c47b7c..a6322abc6 100644 --- a/test/examples/cases/synonym-recursive.solc +++ b/test/examples/cases/synonym-recursive.solc @@ -1,8 +1,8 @@ -type A is B; -type B is A; +alias A = B; +alias B = A; contract RecursiveTest { function main() public returns (word) { return 0; } -} \ No newline at end of file +} diff --git a/test/examples/cases/synonym-self-recursive.solc b/test/examples/cases/synonym-self-recursive.solc index 94af721f5..2cbb40811 100644 --- a/test/examples/cases/synonym-self-recursive.solc +++ b/test/examples/cases/synonym-self-recursive.solc @@ -1,6 +1,6 @@ // Self-recursive synonym should be rejected -type A is A; +alias A = A; function main() returns (word) { return 0; -} \ No newline at end of file +} diff --git a/test/examples/cases/type-synonym-arg.solc b/test/examples/cases/type-synonym-arg.solc index 8a5edefc0..587570d03 100644 --- a/test/examples/cases/type-synonym-arg.solc +++ b/test/examples/cases/type-synonym-arg.solc @@ -1,4 +1,4 @@ -type W is word; +alias W = word; function f(x:W) returns (W) { return x; } diff --git a/test/examples/comptime/ct_named_return.solc b/test/examples/comptime/ct_named_return.solc new file mode 100644 index 000000000..f47e56d61 --- /dev/null +++ b/test/examples/comptime/ct_named_return.solc @@ -0,0 +1,10 @@ +contract ComptimeNamedReturn { + function staged(comptime x: word) returns (comptime result: word) { + result = x; + return; + } + + function main() returns (word) { + return staged(42); + } +} diff --git a/test/examples/comptime/fromInt.solc b/test/examples/comptime/fromInt.solc index 50a5a8a3e..89c96e9ca 100644 --- a/test/examples/comptime/fromInt.solc +++ b/test/examples/comptime/fromInt.solc @@ -8,7 +8,7 @@ Here we use a bit less ambitious approach: literals of type word and `fromWord` import std; -type uint is uint256; // misleads instance solver +alias uint = uint256; // misleads instance solver trait Int { function fromWord(x:word) returns (comptime i); // meaning result is comptime whenever arg is diff --git a/test/imports/bare_revert_import_main.solc b/test/imports/bare_revert_import_main.solc new file mode 100644 index 000000000..936f6d8fa --- /dev/null +++ b/test/imports/bare_revert_import_main.solc @@ -0,0 +1,6 @@ +import bare_revert_lib; + +function callImportedAbort() returns (()) { + bare_revert_lib.abortFromImportedModule(); + return; +} diff --git a/test/imports/bare_revert_lib.solc b/test/imports/bare_revert_lib.solc new file mode 100644 index 000000000..46d9569c5 --- /dev/null +++ b/test/imports/bare_revert_lib.solc @@ -0,0 +1,6 @@ +export { abortFromImportedModule }; + +function abortFromImportedModule() returns (()) { + revert; + return; +} From 6d8cf73dc276331633c763066d09d339999b4090 Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Thu, 23 Jul 2026 23:09:00 +0900 Subject: [PATCH 06/33] Harden new-syntax migration --- scripts/migrate_new_syntax.py | 292 +++++++++++++++++++++++++---- scripts/test_migrate_new_syntax.py | 281 +++++++++++++++++++++++++++ 2 files changed, 541 insertions(+), 32 deletions(-) diff --git a/scripts/migrate_new_syntax.py b/scripts/migrate_new_syntax.py index d3ff2b3c4..c758ac0eb 100755 --- a/scripts/migrate_new_syntax.py +++ b/scripts/migrate_new_syntax.py @@ -7,7 +7,9 @@ * parenthesized calls are changed to angle-bracket type applications only in a syntactic type position; * only git-tracked ``.solc`` files and the explicitly listed Core ``.sol`` - sources are eligible for the default corpus migration. + sources are eligible for the default corpus migration; in packaged source + trees without ``.git`` metadata, the same corpus is discovered below the + repository's ``src``, ``std``, and ``test`` source roots. The unresolved export grammar and contextual ``.Constructor`` shorthand are intentionally preserved. Legacy proxy shorthand is migrated to @@ -18,6 +20,7 @@ import argparse import dataclasses +import os import pathlib import re import subprocess @@ -35,6 +38,8 @@ "concept-art/has-field.sol", ) +PACKAGED_SOLC_ROOTS = ("src", "std", "test") + CLASSIC_SOL_FILES = frozenset( { "blog-post/PaymentHandler.sol", @@ -814,7 +819,11 @@ def transform_type_declarations(source: str) -> str: continue params = parsed cursor = close + 1 - if cursor >= len(tokens) or tokens[cursor].text not in {"=", "is"}: + # ``type Name is Type`` is the new nominal user-defined-value-type + # spelling. It must remain untouched even while compiler support is + # pending. Only the legacy transparent ``type Name = Type`` form is + # migrated to ``alias``. + if cursor >= len(tokens) or tokens[cursor].text != "=": index += 1 continue end_index, _ = declaration_end(source, tokens, cursor + 1) @@ -824,7 +833,7 @@ def transform_type_declarations(source: str) -> str: index += 1 continue params_text = f"<{', '.join(params)}>" if params else "" - replacement = f"type {name}{params_text} is {parsed_rhs[0]};" + replacement = f"alias {name}{params_text} = {parsed_rhs[0]};" replacement = ( preserved_comments( source, tokens[start].start, tokens[end_index].end @@ -1477,18 +1486,116 @@ def transform_types_in_colon_positions(source: str) -> str: return apply_edits(source, edits) +def module_path_token_indexes(tokens: Sequence[Token]) -> set[int]: + """Return indexes in import/export module-path clauses.""" + + protected: set[int] = set() + pairs = {"(": ")", "[": "]", "{": "}", "<": ">"} + index = 0 + while index < len(tokens): + declaration = tokens[index].text + if declaration not in {"import", "export"}: + index += 1 + continue + + stack: list[str] = [] + end = index + 1 + while end < len(tokens): + text = tokens[end].text + if not stack and text == ";": + break + if text in pairs: + stack.append(pairs[text]) + elif stack and text == stack[-1]: + stack.pop() + end += 1 + if end >= len(tokens): + index += 1 + continue + + stack.clear() + top_level: list[int] = [] + for cursor in range(index + 1, end): + text = tokens[cursor].text + if not stack: + top_level.append(cursor) + if text in pairs: + stack.append(pairs[text]) + elif stack and text == stack[-1]: + stack.pop() + + path_start = index + 1 + path_end = end + if declaration == "import": + from_index = next( + ( + cursor + for cursor in top_level + if tokens[cursor].text == "from" + ), + None, + ) + if from_index is not None: + path_start = from_index + 1 + path_end = next( + ( + cursor + for cursor in top_level + if cursor >= path_start + and tokens[cursor].text == "hiding" + ), + end, + ) + elif path_start < end and tokens[path_start].text == "{": + for cursor in range(path_start + 1, end): + if tokens[cursor].text != "@": + continue + terminal = next( + ( + candidate + for candidate in range(cursor + 1, end - 1) + if tokens[candidate].text == "." + and tokens[candidate + 1].text == "*" + ), + None, + ) + if terminal is not None: + protected.update(range(cursor, terminal)) + index = end + 1 + continue + else: + path_end = next( + ( + cursor + for cursor in top_level + if cursor >= path_start + and ( + tokens[cursor].text == "as" + or ( + tokens[cursor].text == "." + and cursor + 1 < end + and tokens[cursor + 1].text in {"{", "*"} + ) + ) + ), + end, + ) + + protected.update(range(path_start, path_end)) + index = end + 1 + + return protected + + def transform_proxy_expressions(source: str) -> str: tokens = significant(source) parser = TypeParser(tokens) + module_paths = module_path_token_indexes(tokens) edits: list[Edit] = [] for index, token in enumerate(tokens): if token.text != "@" or index + 1 >= len(tokens): continue - boundary = declaration_boundary(tokens, index) - if any( - candidate.text == "import" - for candidate in tokens[boundary:index] - ): + if index in module_paths: continue parsed = parser.parse(index + 1) if parsed is None: @@ -2154,19 +2261,128 @@ def apply_file_fixups(relative: pathlib.Path, source: str) -> str: return fixed -def tracked_core_sources() -> list[pathlib.Path]: - process = subprocess.run( - ["git", "ls-files", "-z", "--", "*.solc"], - cwd=REPO_ROOT, - check=True, - stdout=subprocess.PIPE, +def symlink_component(relative: pathlib.Path) -> pathlib.Path | None: + """Return the first symlink in a repository-relative source path.""" + + if relative.is_absolute() or not relative.parts or ".." in relative.parts: + raise ValueError(f"refusing unsafe source path: {relative}") + current = REPO_ROOT + for part in relative.parts: + current /= part + if current.is_symlink(): + return current + return None + + +def require_symlink_free_source(relative: pathlib.Path) -> None: + component = symlink_component(relative) + if component is None: + return + try: + display_component = component.relative_to(REPO_ROOT) + except ValueError: + display_component = component + raise ValueError( + f"refusing to migrate symlink source: {relative} " + f"(symlink component: {display_component})" ) - tracked_solc = [ - path - for raw in process.stdout.split(b"\0") - if raw - for path in [pathlib.Path(raw.decode("utf-8"))] - ] + + +def open_source_without_symlinks(relative: pathlib.Path, flags: int) -> int: + """Open a source through directory descriptors without following links.""" + + require_symlink_free_source(relative) + nofollow = getattr(os, "O_NOFOLLOW", 0) + directory = getattr(os, "O_DIRECTORY", 0) + directory_fds: list[int] = [] + try: + current_fd = os.open( + REPO_ROOT, + os.O_RDONLY | directory | nofollow, + ) + directory_fds.append(current_fd) + for part in relative.parts[:-1]: + current_fd = os.open( + part, + os.O_RDONLY | directory | nofollow, + dir_fd=current_fd, + ) + directory_fds.append(current_fd) + return os.open( + relative.parts[-1], + flags | nofollow, + dir_fd=current_fd, + ) + except OSError as exc: + raise ValueError( + f"refusing to access source through an unsafe worktree path: " + f"{relative}: {exc}" + ) from exc + finally: + for directory_fd in reversed(directory_fds): + os.close(directory_fd) + + +def read_worktree_source(relative: pathlib.Path) -> str: + descriptor = open_source_without_symlinks(relative, os.O_RDONLY) + with os.fdopen(descriptor, encoding="utf-8") as source: + return source.read() + + +def write_worktree_source(relative: pathlib.Path, source: str) -> None: + descriptor = open_source_without_symlinks( + relative, + os.O_WRONLY | os.O_TRUNC, + ) + with os.fdopen(descriptor, "w", encoding="utf-8") as destination: + destination.write(source) + + +def packaged_solc_sources() -> list[pathlib.Path]: + """Discover the Solcore corpus when VCS metadata is unavailable. + + Nix copies the repository into an isolated source tree without ``.git``. + Restricting this fallback to the package's source/test roots avoids + accidentally treating unrelated root-level or proof-of-concept files as + migration inputs. + """ + + return sorted( + path.relative_to(REPO_ROOT) + for root_name in PACKAGED_SOLC_ROOTS + for path in (REPO_ROOT / root_name).rglob("*.solc") + if path.is_file() and not path.is_symlink() + ) + + +def tracked_core_sources() -> list[pathlib.Path]: + tracked_solc: list[pathlib.Path] + if not (REPO_ROOT / ".git").exists(): + tracked_solc = packaged_solc_sources() + else: + process = subprocess.run( + [ + "git", + "-c", + f"safe.directory={REPO_ROOT}", + "ls-files", + "-s", + "-z", + "--", + "*.solc", + ], + cwd=REPO_ROOT, + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + tracked_solc = [ + pathlib.Path(raw_path.decode("utf-8")) + for entry in process.stdout.split(b"\0") + if entry + for metadata, raw_path in [entry.split(b"\t", 1)] + if metadata.split(maxsplit=1)[0] != b"120000" + ] paths = [*tracked_solc, *(pathlib.Path(path) for path in CORE_SOL_FILES)] return sorted(dict.fromkeys(paths)) @@ -2174,12 +2390,19 @@ def tracked_core_sources() -> list[pathlib.Path]: def eligible_paths(arguments: Sequence[str]) -> list[pathlib.Path]: allowed = frozenset(tracked_core_sources()) if not arguments: - return sorted(allowed) + result = sorted(allowed) + for candidate in result: + require_symlink_free_source(candidate) + return result result: list[pathlib.Path] = [] for argument in arguments: candidate = pathlib.Path(argument) if candidate.is_absolute(): - candidate = candidate.resolve().relative_to(REPO_ROOT) + # Keep the lexical path. Resolving first would turn an untracked + # symlink alias into its tracked target and bypass the corpus + # allow-list (and, with --write, modify that target). + candidate = candidate.relative_to(REPO_ROOT) + require_symlink_free_source(candidate) if candidate not in allowed: raise ValueError( f"refusing to migrate non-Core or untracked source: {candidate}" @@ -2214,17 +2437,19 @@ def main(argv: Sequence[str] | None = None) -> int: changed: list[pathlib.Path] = [] for relative in paths: - path = REPO_ROOT / relative - # The import-resolution fixtures contain tracked symlink aliases. Their - # tracked blob is the link target, not Solcore source, and writing via - # pathlib would overwrite the target file. The target itself is also a - # tracked eligible source and is migrated independently. - if path.is_symlink(): - continue - current = path.read_text() + try: + current = read_worktree_source(relative) + except ValueError as error: + parser.error(str(error)) if args.from_head: source = subprocess.run( - ["git", "show", f"HEAD:{relative.as_posix()}"], + [ + "git", + "-c", + f"safe.directory={REPO_ROOT}", + "show", + f"HEAD:{relative.as_posix()}", + ], cwd=REPO_ROOT, check=True, stdout=subprocess.PIPE, @@ -2240,7 +2465,10 @@ def main(argv: Sequence[str] | None = None) -> int: continue changed.append(relative) if args.write: - path.write_text(migrated) + try: + write_worktree_source(relative, migrated) + except ValueError as error: + parser.error(str(error)) action = "updated" if args.write else "needs migration" for path in changed: diff --git a/scripts/test_migrate_new_syntax.py b/scripts/test_migrate_new_syntax.py index a1e0c40d2..8be759160 100644 --- a/scripts/test_migrate_new_syntax.py +++ b/scripts/test_migrate_new_syntax.py @@ -3,10 +3,15 @@ from __future__ import annotations +import contextlib import importlib.util +import io import pathlib +import subprocess import sys +import tempfile import unittest +from unittest import mock SCRIPT = pathlib.Path(__file__).with_name("migrate_new_syntax.py") @@ -55,8 +60,272 @@ def test_data_identifiers_are_stable(self) -> None: with self.subTest(source=source): self.assert_stable(source) + def test_transparent_aliases_are_stable(self) -> None: + cases = ( + "alias Word = uint256;\n", + "alias Map = pair;\n", + ) + for source in cases: + with self.subTest(source=source): + self.assert_stable(source) + + def test_nominal_user_defined_value_type_syntax_is_stable(self) -> None: + self.assert_stable("type Wad is uint256;\n") + + def test_external_module_imports_are_stable(self) -> None: + cases = ( + "import @ext.foo.bar;\n", + "import * as Foo from @ext.foo.bar;\n", + ) + for source in cases: + with self.subTest(source=source): + self.assert_stable(source) + + def test_external_selective_import_is_stable(self) -> None: + self.assert_stable( + "import {foo, bar as baz} from @ext.foo.bar;\n" + ) + + def test_external_glob_import_with_hiding_is_stable(self) -> None: + self.assert_stable( + "import {*} from @ext.foo.bar hiding {bar};\n" + ) + + def test_external_exports_are_stable(self) -> None: + cases = ( + "export @ext.foo.bar;\n", + "export @ext.foo.bar as Foo;\n", + "export @ext.foo.bar.{foo};\n", + "export @ext.foo.bar.*;\n", + "export {@ext.foo.bar.*};\n", + ) + for source in cases: + with self.subTest(source=source): + self.assert_stable(source) + + def test_tracked_symlink_aliases_are_not_counted_as_sources(self) -> None: + sources = migration.tracked_core_sources() + self.assertFalse( + any((migration.REPO_ROOT / source).is_symlink() for source in sources) + ) + + def test_absolute_symlink_alias_cannot_bypass_source_allow_list(self) -> None: + alias = migration.REPO_ROOT / "test/imports/mirror/api.solc" + self.assertTrue(alias.is_symlink()) + for argument in (str(alias.relative_to(migration.REPO_ROOT)), str(alias)): + with self.subTest(argument=argument): + with self.assertRaisesRegex(ValueError, "symlink source"): + migration.eligible_paths([argument]) + + def test_regular_index_entries_cannot_write_through_worktree_symlinks( + self, + ) -> None: + legacy_source = "type Word = word;\n" + for layout in ("source", "parent"): + with self.subTest(layout=layout), tempfile.TemporaryDirectory() as directory: + temporary_root = pathlib.Path(directory) + root = temporary_root / "repo" + root.mkdir() + subprocess.run( + ["git", "init", "--quiet"], + cwd=root, + check=True, + ) + + relative = pathlib.Path("src/nested/victim.solc") + tracked = root / relative + tracked.parent.mkdir(parents=True) + tracked.write_text(legacy_source) + subprocess.run( + ["git", "add", "--", relative.as_posix()], + cwd=root, + check=True, + ) + subprocess.run( + [ + "git", + "-c", + "user.name=Migration Test", + "-c", + "user.email=migration-test@example.invalid", + "commit", + "--quiet", + "-m", + "fixture", + ], + cwd=root, + check=True, + ) + index_entry = subprocess.run( + ["git", "ls-files", "-s", "--", relative.as_posix()], + cwd=root, + check=True, + stdout=subprocess.PIPE, + text=True, + ).stdout + self.assertTrue(index_entry.startswith("100644 ")) + + if layout == "source": + target = temporary_root / "outside.solc" + target.write_text(legacy_source) + tracked.unlink() + tracked.symlink_to(target) + else: + linked_parent = tracked.parent + outside_parent = temporary_root / "outside-parent" + linked_parent.rename(outside_parent) + linked_parent.symlink_to( + outside_parent, + target_is_directory=True, + ) + target = outside_parent / tracked.name + + with ( + mock.patch.object(migration, "REPO_ROOT", root), + mock.patch.object(migration, "CORE_SOL_FILES", ()), + ): + for arguments in ( + [], + [relative.as_posix()], + [str(root / relative)], + ): + with self.subTest(arguments=arguments): + with self.assertRaisesRegex( + ValueError, + "symlink source", + ): + migration.eligible_paths(arguments) + + for arguments in ( + ["--write"], + ["--write", relative.as_posix()], + ["--write", str(root / relative)], + ["--write", "--from-head", relative.as_posix()], + ): + with self.subTest(cli_arguments=arguments): + with ( + contextlib.redirect_stderr(io.StringIO()), + self.assertRaises(SystemExit) as raised, + ): + migration.main(arguments) + self.assertEqual(raised.exception.code, 2) + self.assertEqual(target.read_text(), legacy_source) + + def test_git_failure_does_not_expand_write_scope_to_untracked_files(self) -> None: + with tempfile.TemporaryDirectory() as directory: + root = pathlib.Path(directory) + (root / ".git").mkdir() + failures = ( + subprocess.CalledProcessError(128, ["git", "ls-files"]), + FileNotFoundError("git"), + ) + for failure in failures: + with self.subTest(failure=type(failure).__name__): + with ( + mock.patch.object(migration, "REPO_ROOT", root), + mock.patch.object( + migration.subprocess, + "run", + side_effect=failure, + ), + mock.patch.object( + migration, + "packaged_solc_sources", + ) as fallback, + ): + with self.assertRaises(type(failure)): + migration.tracked_core_sources() + fallback.assert_not_called() + + def test_from_head_uses_safe_directory_and_never_writes_after_git_failure( + self, + ) -> None: + with tempfile.TemporaryDirectory() as directory: + root = pathlib.Path(directory) + relative = pathlib.Path("src/example.solc") + source = root / relative + source.parent.mkdir(parents=True) + source.write_text("function current() {}\n") + failure = subprocess.CalledProcessError( + 128, + ["git", "show", f"HEAD:{relative.as_posix()}"], + stderr="fatal: detected dubious ownership in repository", + ) + + def reject_git_show( + command: list[str], + **kwargs: object, + ) -> subprocess.CompletedProcess[str]: + self.assertEqual( + command, + [ + "git", + "-c", + f"safe.directory={root}", + "show", + f"HEAD:{relative.as_posix()}", + ], + ) + self.assertEqual(kwargs["cwd"], root) + raise failure + + with ( + mock.patch.object(migration, "REPO_ROOT", root), + mock.patch.object( + migration, + "eligible_paths", + return_value=[relative], + ), + mock.patch.object( + migration.subprocess, + "run", + side_effect=reject_git_show, + ), + mock.patch.object(migration, "write_worktree_source") as write, + ): + with self.assertRaises(subprocess.CalledProcessError) as raised: + migration.main( + ["--write", "--from-head", relative.as_posix()] + ) + self.assertIs(raised.exception, failure) + write.assert_not_called() + + def test_packaged_source_fallback_is_scoped_to_core_roots(self) -> None: + with tempfile.TemporaryDirectory() as directory: + root = pathlib.Path(directory) + for relative in ("src/a.solc", "std/b.solc", "test/c.solc"): + path = root / relative + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("function current() {}\n") + (root / "scratch.solc").write_text("legacy root scratch\n") + (root / "poc").mkdir() + (root / "poc/experiment.solc").write_text("legacy experiment\n") + (root / "test/link.solc").symlink_to(root / "src/a.solc") + + with mock.patch.object(migration, "REPO_ROOT", root): + self.assertEqual( + migration.packaged_solc_sources(), + [ + pathlib.Path("src/a.solc"), + pathlib.Path("std/b.solc"), + pathlib.Path("test/c.solc"), + ], + ) + class LegacyMigrationTests(unittest.TestCase): + def test_transparent_type_declarations_become_aliases(self) -> None: + cases = ( + ("type Word = word;\n", "alias Word = word;\n"), + ( + "type Pair(a, b) = pair(a, b);\n", + "alias Pair = pair;\n", + ), + ) + for source, expected in cases: + with self.subTest(source=source): + self.assertEqual(migration.migrate_source(source), expected) + def test_expression_annotation_still_becomes_conversion(self) -> None: source = ( "function convert(x: word) returns (word) " @@ -83,6 +352,18 @@ def test_top_level_and_contract_data_declarations_migrate(self) -> None: ) self.assertEqual(migration.migrate_source(source), expected) + def test_external_selective_import_migrates(self) -> None: + source = "import @ext.foo.bar.{foo, bar as baz};\n" + expected = "import {foo, bar as baz} from @ext.foo.bar;\n" + self.assertEqual(migration.migrate_source(source), expected) + + def test_proxy_expression_outside_module_path_still_migrates(self) -> None: + source = "function f() { return @Foo.bar; }\n" + expected = ( + "function f() { return Proxy as Proxy; }\n" + ) + self.assertEqual(migration.migrate_source(source), expected) + def test_sum_parameter_fixup_is_reproducible(self) -> None: generated = ( "function sum (p1 : (T1, T2), p2 (T1, T2)) " From c81c76c9d9ddee1ea506c42e167c4cf59fe7a2cb Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Thu, 23 Jul 2026 23:09:44 +0900 Subject: [PATCH 07/33] Regenerate syntax railroad diagrams --- doc/railroad/sail.bnf | 62 ++++-- doc/src/sail/diagrams/AdditiveExpr.svg | 28 +++ doc/src/sail/diagrams/AliasDef.svg | 26 +++ doc/src/sail/diagrams/ArraySize.svg | 20 ++ doc/src/sail/diagrams/ArraySuffix.svg | 23 +++ doc/src/sail/diagrams/Assignment.svg | 21 +++ doc/src/sail/diagrams/AssignmentOperator.svg | 25 +++ doc/src/sail/diagrams/BindingPattern.svg | 21 +++ doc/src/sail/diagrams/BindingPatternList.svg | 26 +++ doc/src/sail/diagrams/BindingTuple.svg | 23 +++ doc/src/sail/diagrams/BitwiseAndExpr.svg | 26 +++ doc/src/sail/diagrams/BitwiseOrExpr.svg | 26 +++ doc/src/sail/diagrams/BitwiseXorExpr.svg | 26 +++ doc/src/sail/diagrams/CastExpr.svg | 26 +++ doc/src/sail/diagrams/ClassDef.svg | 35 ---- doc/src/sail/diagrams/CompilationUnit.svg | 7 +- doc/src/sail/diagrams/ConditionalExpr.svg | 26 +++ doc/src/sail/diagrams/Constraint.svg | 15 +- doc/src/sail/diagrams/Constructor.svg | 11 +- doc/src/sail/diagrams/Contract.svg | 2 +- doc/src/sail/diagrams/ContractDecl.svg | 10 +- doc/src/sail/diagrams/DataDef.svg | 29 --- doc/src/sail/diagrams/DataLocation.svg | 21 +++ doc/src/sail/diagrams/EnumDef.svg | 39 ++++ .../{DataConstr.svg => EnumVariant.svg} | 0 doc/src/sail/diagrams/EqualityExpr.svg | 26 +++ doc/src/sail/diagrams/Equation.svg | 26 --- doc/src/sail/diagrams/EscapeSequence.svg | 25 +++ doc/src/sail/diagrams/ExportDecl.svg | 110 ++++------- doc/src/sail/diagrams/ExportItem.svg | 37 ++-- doc/src/sail/diagrams/Expr.svg | 146 +------------- doc/src/sail/diagrams/Fallback.svg | 26 +++ doc/src/sail/diagrams/ForInitClause.svg | 26 +++ doc/src/sail/diagrams/ForInitItem.svg | 21 +++ doc/src/sail/diagrams/ForInitStmt.svg | 49 ----- doc/src/sail/diagrams/ForPostClause.svg | 26 +++ doc/src/sail/diagrams/ForPostItem.svg | 20 ++ doc/src/sail/diagrams/ForPostStmt.svg | 49 ----- doc/src/sail/diagrams/Function.svg | 16 +- doc/src/sail/diagrams/FunctionAttribute.svg | 25 +++ doc/src/sail/diagrams/FunctionType.svg | 36 ++++ doc/src/sail/diagrams/FunctionVisibility.svg | 20 ++ doc/src/sail/diagrams/GenericArguments.svg | 21 +++ doc/src/sail/diagrams/GenericParams.svg | 21 +++ doc/src/sail/diagrams/Identifier.svg | 24 +-- .../{HidingList.svg => IdentifierList.svg} | 0 doc/src/sail/diagrams/ImplDef.svg | 37 ++++ doc/src/sail/diagrams/Import.svg | 90 +++------ doc/src/sail/diagrams/ImportItem.svg | 8 +- doc/src/sail/diagrams/InstDef.svg | 39 ---- doc/src/sail/diagrams/Interface.svg | 32 ++++ doc/src/sail/diagrams/InterfaceAttribute.svg | 22 +++ doc/src/sail/diagrams/InterfaceSignature.svg | 45 +++++ doc/src/sail/diagrams/LambdaExpr.svg | 34 ++++ doc/src/sail/diagrams/LambdaParam.svg | 27 +++ doc/src/sail/diagrams/LambdaParamList.svg | 26 +++ doc/src/sail/diagrams/LetStmt.svg | 44 +++++ doc/src/sail/diagrams/Library.svg | 30 +++ doc/src/sail/diagrams/LibraryDecl.svg | 22 +++ doc/src/sail/diagrams/Literal.svg | 6 +- doc/src/sail/diagrams/LogicalAndExpr.svg | 26 +++ doc/src/sail/diagrams/LogicalOrExpr.svg | 26 +++ doc/src/sail/diagrams/MatchArgs.svg | 26 --- doc/src/sail/diagrams/MatchArm.svg | 25 +++ doc/src/sail/diagrams/MatchPattern.svg | 23 +++ doc/src/sail/diagrams/MatchStmt.svg | 29 +++ doc/src/sail/diagrams/ModulePath.svg | 26 ++- doc/src/sail/diagrams/MultiplicativeExpr.svg | 29 +++ doc/src/sail/diagrams/OperatorSymbol.svg | 24 --- doc/src/sail/diagrams/Param.svg | 20 +- doc/src/sail/diagrams/Pattern.svg | 16 +- doc/src/sail/diagrams/PostfixExpr.svg | 24 +++ doc/src/sail/diagrams/PostfixSuffix.svg | 31 +++ doc/src/sail/diagrams/PowerExpr.svg | 24 +++ doc/src/sail/diagrams/Pragma.svg | 30 ++- doc/src/sail/diagrams/PragmaKind.svg | 21 --- doc/src/sail/diagrams/PragmaToken.svg | 29 +++ doc/src/sail/diagrams/PragmaValue.svg | 24 +++ doc/src/sail/diagrams/PrimaryExpr.svg | 45 +++++ .../{DataConstrs.svg => QualifiedName.svg} | 6 +- doc/src/sail/diagrams/RelationalExpr.svg | 28 +++ doc/src/sail/diagrams/ReturnItem.svg | 27 +++ doc/src/sail/diagrams/ReturnItemList.svg | 26 +++ doc/src/sail/diagrams/ShiftExpr.svg | 28 +++ doc/src/sail/diagrams/SigPrefix.svg | 29 --- doc/src/sail/diagrams/Signature.svg | 46 +++-- doc/src/sail/diagrams/SimpleStmt.svg | 21 +++ doc/src/sail/diagrams/SolcorePragma.svg | 22 +++ doc/src/sail/diagrams/Stmt.svg | 133 ++++++------- doc/src/sail/diagrams/StringLiteral.svg | 18 +- doc/src/sail/diagrams/StructDef.svg | 30 +++ doc/src/sail/diagrams/StructField.svg | 22 +++ doc/src/sail/diagrams/TopDecl.svg | 23 +-- doc/src/sail/diagrams/TraitDef.svg | 33 ++++ doc/src/sail/diagrams/Type.svg | 33 +--- doc/src/sail/diagrams/TypeAtom.svg | 44 +++++ doc/src/sail/diagrams/TypeName.svg | 23 --- doc/src/sail/diagrams/TypeSuffix.svg | 20 ++ doc/src/sail/diagrams/TypeSynonym.svg | 26 --- doc/src/sail/diagrams/TypeVarParams.svg | 28 --- doc/src/sail/diagrams/TypeVarSeq.svg | 22 --- doc/src/sail/diagrams/UnaryExpr.svg | 22 +++ doc/src/sail/diagrams/WhereClause.svg | 20 ++ doc/src/sail/diagrams/YMeta.svg | 34 ++++ doc/src/sail/diagrams/YulExpr.svg | 37 ++-- doc/src/sail/diagrams/YulIdentifier.svg | 31 +++ doc/src/sail/diagrams/YulLiteral.svg | 6 +- doc/src/sail/diagrams/YulNames.svg | 20 +- doc/src/sail/diagrams/YulStmt.svg | 178 ++++++++++-------- 109 files changed, 2245 insertions(+), 1075 deletions(-) create mode 100644 doc/src/sail/diagrams/AdditiveExpr.svg create mode 100644 doc/src/sail/diagrams/AliasDef.svg create mode 100644 doc/src/sail/diagrams/ArraySize.svg create mode 100644 doc/src/sail/diagrams/ArraySuffix.svg create mode 100644 doc/src/sail/diagrams/Assignment.svg create mode 100644 doc/src/sail/diagrams/AssignmentOperator.svg create mode 100644 doc/src/sail/diagrams/BindingPattern.svg create mode 100644 doc/src/sail/diagrams/BindingPatternList.svg create mode 100644 doc/src/sail/diagrams/BindingTuple.svg create mode 100644 doc/src/sail/diagrams/BitwiseAndExpr.svg create mode 100644 doc/src/sail/diagrams/BitwiseOrExpr.svg create mode 100644 doc/src/sail/diagrams/BitwiseXorExpr.svg create mode 100644 doc/src/sail/diagrams/CastExpr.svg delete mode 100644 doc/src/sail/diagrams/ClassDef.svg create mode 100644 doc/src/sail/diagrams/ConditionalExpr.svg delete mode 100644 doc/src/sail/diagrams/DataDef.svg create mode 100644 doc/src/sail/diagrams/DataLocation.svg create mode 100644 doc/src/sail/diagrams/EnumDef.svg rename doc/src/sail/diagrams/{DataConstr.svg => EnumVariant.svg} (100%) create mode 100644 doc/src/sail/diagrams/EqualityExpr.svg delete mode 100644 doc/src/sail/diagrams/Equation.svg create mode 100644 doc/src/sail/diagrams/EscapeSequence.svg create mode 100644 doc/src/sail/diagrams/Fallback.svg create mode 100644 doc/src/sail/diagrams/ForInitClause.svg create mode 100644 doc/src/sail/diagrams/ForInitItem.svg delete mode 100644 doc/src/sail/diagrams/ForInitStmt.svg create mode 100644 doc/src/sail/diagrams/ForPostClause.svg create mode 100644 doc/src/sail/diagrams/ForPostItem.svg delete mode 100644 doc/src/sail/diagrams/ForPostStmt.svg create mode 100644 doc/src/sail/diagrams/FunctionAttribute.svg create mode 100644 doc/src/sail/diagrams/FunctionType.svg create mode 100644 doc/src/sail/diagrams/FunctionVisibility.svg create mode 100644 doc/src/sail/diagrams/GenericArguments.svg create mode 100644 doc/src/sail/diagrams/GenericParams.svg rename doc/src/sail/diagrams/{HidingList.svg => IdentifierList.svg} (100%) create mode 100644 doc/src/sail/diagrams/ImplDef.svg delete mode 100644 doc/src/sail/diagrams/InstDef.svg create mode 100644 doc/src/sail/diagrams/Interface.svg create mode 100644 doc/src/sail/diagrams/InterfaceAttribute.svg create mode 100644 doc/src/sail/diagrams/InterfaceSignature.svg create mode 100644 doc/src/sail/diagrams/LambdaExpr.svg create mode 100644 doc/src/sail/diagrams/LambdaParam.svg create mode 100644 doc/src/sail/diagrams/LambdaParamList.svg create mode 100644 doc/src/sail/diagrams/LetStmt.svg create mode 100644 doc/src/sail/diagrams/Library.svg create mode 100644 doc/src/sail/diagrams/LibraryDecl.svg create mode 100644 doc/src/sail/diagrams/LogicalAndExpr.svg create mode 100644 doc/src/sail/diagrams/LogicalOrExpr.svg delete mode 100644 doc/src/sail/diagrams/MatchArgs.svg create mode 100644 doc/src/sail/diagrams/MatchArm.svg create mode 100644 doc/src/sail/diagrams/MatchPattern.svg create mode 100644 doc/src/sail/diagrams/MatchStmt.svg create mode 100644 doc/src/sail/diagrams/MultiplicativeExpr.svg delete mode 100644 doc/src/sail/diagrams/OperatorSymbol.svg create mode 100644 doc/src/sail/diagrams/PostfixExpr.svg create mode 100644 doc/src/sail/diagrams/PostfixSuffix.svg create mode 100644 doc/src/sail/diagrams/PowerExpr.svg delete mode 100644 doc/src/sail/diagrams/PragmaKind.svg create mode 100644 doc/src/sail/diagrams/PragmaToken.svg create mode 100644 doc/src/sail/diagrams/PragmaValue.svg create mode 100644 doc/src/sail/diagrams/PrimaryExpr.svg rename doc/src/sail/diagrams/{DataConstrs.svg => QualifiedName.svg} (85%) create mode 100644 doc/src/sail/diagrams/RelationalExpr.svg create mode 100644 doc/src/sail/diagrams/ReturnItem.svg create mode 100644 doc/src/sail/diagrams/ReturnItemList.svg create mode 100644 doc/src/sail/diagrams/ShiftExpr.svg delete mode 100644 doc/src/sail/diagrams/SigPrefix.svg create mode 100644 doc/src/sail/diagrams/SimpleStmt.svg create mode 100644 doc/src/sail/diagrams/SolcorePragma.svg create mode 100644 doc/src/sail/diagrams/StructDef.svg create mode 100644 doc/src/sail/diagrams/StructField.svg create mode 100644 doc/src/sail/diagrams/TraitDef.svg create mode 100644 doc/src/sail/diagrams/TypeAtom.svg delete mode 100644 doc/src/sail/diagrams/TypeName.svg create mode 100644 doc/src/sail/diagrams/TypeSuffix.svg delete mode 100644 doc/src/sail/diagrams/TypeSynonym.svg delete mode 100644 doc/src/sail/diagrams/TypeVarParams.svg delete mode 100644 doc/src/sail/diagrams/TypeVarSeq.svg create mode 100644 doc/src/sail/diagrams/UnaryExpr.svg create mode 100644 doc/src/sail/diagrams/WhereClause.svg create mode 100644 doc/src/sail/diagrams/YMeta.svg create mode 100644 doc/src/sail/diagrams/YulIdentifier.svg diff --git a/doc/railroad/sail.bnf b/doc/railroad/sail.bnf index 8276bba8f..01a38247b 100644 --- a/doc/railroad/sail.bnf +++ b/doc/railroad/sail.bnf @@ -36,7 +36,7 @@ TopDecl = Contract | ImplDef | StructDef | EnumDef - | TypeDef + | AliasDef | ExportDecl @@ -102,7 +102,9 @@ PragmaTargets = Identifier { "," Identifier } ## Types -Type = TypeAtom { ArraySuffix } [ DataLocation ] +Type = TypeAtom { TypeSuffix } + +TypeSuffix = ArraySuffix | DataLocation TypeAtom = QualifiedName [ GenericArguments ] | "mapping" "(" Type "=>" Type ")" @@ -134,7 +136,7 @@ ReturnItem = [ "comptime" ] [ Identifier ":" ] Type GenericParams = "<" IdentifierList ">" -## Structs, Enums, and User-Defined Types +## Structs, Enums, and Type Aliases StructDef = "struct" Identifier [ GenericParams ] "{" { StructField } "}" @@ -146,7 +148,11 @@ EnumDef = "enum" Identifier [ GenericParams ] EnumVariant = Identifier [ "(" TypeList ")" ] -TypeDef = "type" Identifier [ GenericParams ] "is" Type ";" +AliasDef = "alias" Identifier [ GenericParams ] "=" Type ";" + +# "type" Identifier "is" Type ";" is reserved for nominal user-defined value +# types. The current compiler rejects that form until nominal semantics are +# implemented; it must not be interpreted as a transparent AliasDef. ## Patterns @@ -172,16 +178,16 @@ LogicalOrExpr = LogicalAndExpr { "||" LogicalAndExpr } LogicalAndExpr = EqualityExpr { "&&" EqualityExpr } -EqualityExpr = BitwiseOrExpr { ( "==" | "!=" ) BitwiseOrExpr } +EqualityExpr = RelationalExpr [ ( "==" | "!=" ) RelationalExpr ] + +RelationalExpr = BitwiseOrExpr + [ ( "<" | ">" | "<=" | ">=" ) BitwiseOrExpr ] BitwiseOrExpr = BitwiseXorExpr { "|" BitwiseXorExpr } BitwiseXorExpr = BitwiseAndExpr { "^" BitwiseAndExpr } -BitwiseAndExpr = RelationalExpr { "&" RelationalExpr } - -RelationalExpr = ShiftExpr - { ( "<" | ">" | "<=" | ">=" ) ShiftExpr } +BitwiseAndExpr = ShiftExpr { "&" ShiftExpr } ShiftExpr = AdditiveExpr { ( "<<" | ">>" ) AdditiveExpr } @@ -316,6 +322,11 @@ FunctionAttribute = "public" | "view" | "payable" +# Contract and library functions admit at most one visibility attribute and at +# most one mutability attribute. Module-level functions, trait signatures, and +# implementation methods reject visibility and "payable"; "pure" and "view" +# remain available there. + TraitDef = "trait" Identifier GenericParams [ WhereClause ] "{" { Signature ";" } "}" @@ -347,7 +358,21 @@ Constructor = "constructor" "(" [ ParamList ] ")" Fallback = "fallback" "(" ")" "external" [ "payable" ] Body Interface = "interface" Identifier [ GenericParams ] - "{" { Signature ";" } "}" + "{" { InterfaceSignature ";" } "}" + +InterfaceSignature = "function" Identifier [ GenericParams ] + "(" [ ParamList ] ")" + { InterfaceAttribute } + [ "returns" "(" [ ReturnItemList ] ")" ] + [ WhereClause ] + +InterfaceAttribute = "external" + | "pure" + | "view" + | "payable" + +# An InterfaceSignature contains "external" exactly once and at most one of +# "pure", "view", and "payable". Library = "library" Identifier [ GenericParams ] "{" { LibraryDecl } "}" @@ -364,6 +389,8 @@ AsmBlock = "assembly" "{" { YulStmt } "}" YulStmt = YulNames ":=" YulExpr | "let" YulNames [ ":=" YulExpr ] + | "function" YulIdentifier "(" [ YulNames ] ")" + [ "->" YulNames ] "{" { YulStmt } "}" | "if" YulExpr "{" { YulStmt } "}" | "switch" YulExpr { YulCase } [ "default" "{" { YulStmt } "}" ] @@ -378,12 +405,19 @@ YulStmt = YulNames ":=" YulExpr YulCase = "case" YulLiteral "{" { YulStmt } "}" YulExpr = YulLiteral - | Identifier - | Identifier "(" [ YulExprList ] ")" + | YulIdentifier + | YulIdentifier "(" [ YulExprList ] ")" + | YMeta | "return" "(" [ YulExprList ] ")" -YulNames = Identifier { "," Identifier } +YMeta = "`" { char } "`" + | "${" { char } "}" + +YulNames = YulIdentifier { "," YulIdentifier } YulExprList = YulExpr { "," YulExpr } -YulLiteral = Integer | StringLiteral +YulLiteral = Integer | StringLiteral | "true" | "false" + +YulIdentifier = ( letter | "_" | "$" ) + { letter | digit | "_" | "$" } diff --git a/doc/src/sail/diagrams/AdditiveExpr.svg b/doc/src/sail/diagrams/AdditiveExpr.svg new file mode 100644 index 000000000..4aab4ea75 --- /dev/null +++ b/doc/src/sail/diagrams/AdditiveExpr.svg @@ -0,0 +1,28 @@ + + + + + + +MultiplicativeExpr + + + + + ++ +- +MultiplicativeExpr + \ No newline at end of file diff --git a/doc/src/sail/diagrams/AliasDef.svg b/doc/src/sail/diagrams/AliasDef.svg new file mode 100644 index 000000000..61e87b0bd --- /dev/null +++ b/doc/src/sail/diagrams/AliasDef.svg @@ -0,0 +1,26 @@ + + + + + + +alias +Identifier + + +GenericParams += +Type +; \ No newline at end of file diff --git a/doc/src/sail/diagrams/ArraySize.svg b/doc/src/sail/diagrams/ArraySize.svg new file mode 100644 index 000000000..63f68ce46 --- /dev/null +++ b/doc/src/sail/diagrams/ArraySize.svg @@ -0,0 +1,20 @@ + + + + + + +Integer +Type \ No newline at end of file diff --git a/doc/src/sail/diagrams/ArraySuffix.svg b/doc/src/sail/diagrams/ArraySuffix.svg new file mode 100644 index 000000000..7e0637544 --- /dev/null +++ b/doc/src/sail/diagrams/ArraySuffix.svg @@ -0,0 +1,23 @@ + + + + + + +[ + + +ArraySize +] \ No newline at end of file diff --git a/doc/src/sail/diagrams/Assignment.svg b/doc/src/sail/diagrams/Assignment.svg new file mode 100644 index 000000000..dda5acfc0 --- /dev/null +++ b/doc/src/sail/diagrams/Assignment.svg @@ -0,0 +1,21 @@ + + + + + + +Expr +AssignmentOperator +Expr \ No newline at end of file diff --git a/doc/src/sail/diagrams/AssignmentOperator.svg b/doc/src/sail/diagrams/AssignmentOperator.svg new file mode 100644 index 000000000..978092097 --- /dev/null +++ b/doc/src/sail/diagrams/AssignmentOperator.svg @@ -0,0 +1,25 @@ + + + + + + += ++= +-= +%= +&= +|= +^= \ No newline at end of file diff --git a/doc/src/sail/diagrams/BindingPattern.svg b/doc/src/sail/diagrams/BindingPattern.svg new file mode 100644 index 000000000..84c84692f --- /dev/null +++ b/doc/src/sail/diagrams/BindingPattern.svg @@ -0,0 +1,21 @@ + + + + + + +Identifier +_ +BindingTuple \ No newline at end of file diff --git a/doc/src/sail/diagrams/BindingPatternList.svg b/doc/src/sail/diagrams/BindingPatternList.svg new file mode 100644 index 000000000..7be889689 --- /dev/null +++ b/doc/src/sail/diagrams/BindingPatternList.svg @@ -0,0 +1,26 @@ + + + + + + +BindingPattern + + + + +, +BindingPattern + \ No newline at end of file diff --git a/doc/src/sail/diagrams/BindingTuple.svg b/doc/src/sail/diagrams/BindingTuple.svg new file mode 100644 index 000000000..5eafb51b8 --- /dev/null +++ b/doc/src/sail/diagrams/BindingTuple.svg @@ -0,0 +1,23 @@ + + + + + + +( +BindingPattern +, +BindingPatternList +) \ No newline at end of file diff --git a/doc/src/sail/diagrams/BitwiseAndExpr.svg b/doc/src/sail/diagrams/BitwiseAndExpr.svg new file mode 100644 index 000000000..1a521706e --- /dev/null +++ b/doc/src/sail/diagrams/BitwiseAndExpr.svg @@ -0,0 +1,26 @@ + + + + + + +ShiftExpr + + + + +& +ShiftExpr + \ No newline at end of file diff --git a/doc/src/sail/diagrams/BitwiseOrExpr.svg b/doc/src/sail/diagrams/BitwiseOrExpr.svg new file mode 100644 index 000000000..c1d250a20 --- /dev/null +++ b/doc/src/sail/diagrams/BitwiseOrExpr.svg @@ -0,0 +1,26 @@ + + + + + + +BitwiseXorExpr + + + + +| +BitwiseXorExpr + \ No newline at end of file diff --git a/doc/src/sail/diagrams/BitwiseXorExpr.svg b/doc/src/sail/diagrams/BitwiseXorExpr.svg new file mode 100644 index 000000000..50d324581 --- /dev/null +++ b/doc/src/sail/diagrams/BitwiseXorExpr.svg @@ -0,0 +1,26 @@ + + + + + + +BitwiseAndExpr + + + + +^ +BitwiseAndExpr + \ No newline at end of file diff --git a/doc/src/sail/diagrams/CastExpr.svg b/doc/src/sail/diagrams/CastExpr.svg new file mode 100644 index 000000000..a3bfa1cdf --- /dev/null +++ b/doc/src/sail/diagrams/CastExpr.svg @@ -0,0 +1,26 @@ + + + + + + +UnaryExpr + + + + +as +Type + \ No newline at end of file diff --git a/doc/src/sail/diagrams/ClassDef.svg b/doc/src/sail/diagrams/ClassDef.svg deleted file mode 100644 index 4d2bfc554..000000000 --- a/doc/src/sail/diagrams/ClassDef.svg +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - -SigPrefix -class -Identifier -: -Identifier - - -TypeVarParams -{ - - - - -Signature -; - -} \ No newline at end of file diff --git a/doc/src/sail/diagrams/CompilationUnit.svg b/doc/src/sail/diagrams/CompilationUnit.svg index bd728cda2..81b0e63c1 100644 --- a/doc/src/sail/diagrams/CompilationUnit.svg +++ b/doc/src/sail/diagrams/CompilationUnit.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file +Pragma +TopDecl + \ No newline at end of file diff --git a/doc/src/sail/diagrams/ConditionalExpr.svg b/doc/src/sail/diagrams/ConditionalExpr.svg new file mode 100644 index 000000000..39327b42a --- /dev/null +++ b/doc/src/sail/diagrams/ConditionalExpr.svg @@ -0,0 +1,26 @@ + + + + + + +LogicalOrExpr + + + +? +Expr +: +ConditionalExpr \ No newline at end of file diff --git a/doc/src/sail/diagrams/Constraint.svg b/doc/src/sail/diagrams/Constraint.svg index 5259664c7..c110516d4 100644 --- a/doc/src/sail/diagrams/Constraint.svg +++ b/doc/src/sail/diagrams/Constraint.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file +QualifiedName + + +GenericArguments \ No newline at end of file diff --git a/doc/src/sail/diagrams/Constructor.svg b/doc/src/sail/diagrams/Constructor.svg index d5080a741..fda66375d 100644 --- a/doc/src/sail/diagrams/Constructor.svg +++ b/doc/src/sail/diagrams/Constructor.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file +) + + +payable +Body \ No newline at end of file diff --git a/doc/src/sail/diagrams/Contract.svg b/doc/src/sail/diagrams/Contract.svg index 3ecb03308..82a1c385c 100644 --- a/doc/src/sail/diagrams/Contract.svg +++ b/doc/src/sail/diagrams/Contract.svg @@ -20,7 +20,7 @@ svg.railroad-diagram rect.group-box { Identifier -TypeVarParams +GenericParams { diff --git a/doc/src/sail/diagrams/ContractDecl.svg b/doc/src/sail/diagrams/ContractDecl.svg index 7d36cffac..d00e9e645 100644 --- a/doc/src/sail/diagrams/ContractDecl.svg +++ b/doc/src/sail/diagrams/ContractDecl.svg @@ -1,4 +1,4 @@ - + FieldDecl -DataDef -Function -Constructor \ No newline at end of file +StructDef +EnumDef +Function +Constructor +Fallback \ No newline at end of file diff --git a/doc/src/sail/diagrams/DataDef.svg b/doc/src/sail/diagrams/DataDef.svg deleted file mode 100644 index 0f894a43f..000000000 --- a/doc/src/sail/diagrams/DataDef.svg +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - -data -Identifier - - -TypeVarParams - - - -= -DataConstrs -; \ No newline at end of file diff --git a/doc/src/sail/diagrams/DataLocation.svg b/doc/src/sail/diagrams/DataLocation.svg new file mode 100644 index 000000000..f0427e377 --- /dev/null +++ b/doc/src/sail/diagrams/DataLocation.svg @@ -0,0 +1,21 @@ + + + + + + +memory +storage +calldata \ No newline at end of file diff --git a/doc/src/sail/diagrams/EnumDef.svg b/doc/src/sail/diagrams/EnumDef.svg new file mode 100644 index 000000000..aa8ba4502 --- /dev/null +++ b/doc/src/sail/diagrams/EnumDef.svg @@ -0,0 +1,39 @@ + + + + + + +enum +Identifier + + +GenericParams +{ + + + +EnumVariant + + + + +, +EnumVariant + + + +, +} \ No newline at end of file diff --git a/doc/src/sail/diagrams/DataConstr.svg b/doc/src/sail/diagrams/EnumVariant.svg similarity index 100% rename from doc/src/sail/diagrams/DataConstr.svg rename to doc/src/sail/diagrams/EnumVariant.svg diff --git a/doc/src/sail/diagrams/EqualityExpr.svg b/doc/src/sail/diagrams/EqualityExpr.svg new file mode 100644 index 000000000..f7fa13507 --- /dev/null +++ b/doc/src/sail/diagrams/EqualityExpr.svg @@ -0,0 +1,26 @@ + + + + + + +RelationalExpr + + + + +== +!= +RelationalExpr \ No newline at end of file diff --git a/doc/src/sail/diagrams/Equation.svg b/doc/src/sail/diagrams/Equation.svg deleted file mode 100644 index 1a7706acb..000000000 --- a/doc/src/sail/diagrams/Equation.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - -| -PatternList -=> - - - -Stmt - \ No newline at end of file diff --git a/doc/src/sail/diagrams/EscapeSequence.svg b/doc/src/sail/diagrams/EscapeSequence.svg new file mode 100644 index 000000000..a6b4e6c6b --- /dev/null +++ b/doc/src/sail/diagrams/EscapeSequence.svg @@ -0,0 +1,25 @@ + + + + + + +\ + +\ +" +n +t +r \ No newline at end of file diff --git a/doc/src/sail/diagrams/ExportDecl.svg b/doc/src/sail/diagrams/ExportDecl.svg index 002afa58b..a0a37a601 100644 --- a/doc/src/sail/diagrams/ExportDecl.svg +++ b/doc/src/sail/diagrams/ExportDecl.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file + + +export +{ + + +ExportItems +} +; + +export +ModulePath +; + +export +ModulePath +as +Identifier +; + +export +ModulePath +. +{ + + +ExportFromItems +} +; + +export +ModulePath +. +* +; \ No newline at end of file diff --git a/doc/src/sail/diagrams/ExportItem.svg b/doc/src/sail/diagrams/ExportItem.svg index ee72a9817..90ff41eee 100644 --- a/doc/src/sail/diagrams/ExportItem.svg +++ b/doc/src/sail/diagrams/ExportItem.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file + +* +Identifier + +Identifier +( +ExportConstructors +) + +ModulePath +. +* \ No newline at end of file diff --git a/doc/src/sail/diagrams/Expr.svg b/doc/src/sail/diagrams/Expr.svg index dd98c92ef..b2b89a643 100644 --- a/doc/src/sail/diagrams/Expr.svg +++ b/doc/src/sail/diagrams/Expr.svg @@ -1,4 +1,4 @@ - + - - - -Identifier -( - - -ExprList -) - -Expr -. -Identifier -( - - -ExprList -) - -Expr -. -Identifier - -. -Identifier -( - - -ExprList -) - -. -Identifier -Identifier -Literal - -( -Expr -) - -( -) - -( -Expr -, -Expr - - - - -, -Expr - -) - -lam -( - - -ParamList -) - - - --> -Type -Body - -Expr -: -Type - -Expr -[ -Expr -] - -Expr -+ -Expr - -Expr -- -Expr - -Expr -* -Expr - -Expr -/ -Expr - -Expr -% -Expr - -Expr -< -Expr - -Expr -> -Expr - -Expr -<= -Expr - -Expr ->= -Expr - -Expr -== -Expr - -Expr -!= -Expr - -Expr -&& -Expr - -Expr -|| -Expr - -! -Expr - -if -Expr -then -Expr -else -Expr - -@ -Type \ No newline at end of file + +ConditionalExpr \ No newline at end of file diff --git a/doc/src/sail/diagrams/Fallback.svg b/doc/src/sail/diagrams/Fallback.svg new file mode 100644 index 000000000..321f97aa2 --- /dev/null +++ b/doc/src/sail/diagrams/Fallback.svg @@ -0,0 +1,26 @@ + + + + + + +fallback +( +) +external + + +payable +Body \ No newline at end of file diff --git a/doc/src/sail/diagrams/ForInitClause.svg b/doc/src/sail/diagrams/ForInitClause.svg new file mode 100644 index 000000000..1506dfca9 --- /dev/null +++ b/doc/src/sail/diagrams/ForInitClause.svg @@ -0,0 +1,26 @@ + + + + + + +ForInitItem + + + + +, +ForInitItem + \ No newline at end of file diff --git a/doc/src/sail/diagrams/ForInitItem.svg b/doc/src/sail/diagrams/ForInitItem.svg new file mode 100644 index 000000000..965080bd2 --- /dev/null +++ b/doc/src/sail/diagrams/ForInitItem.svg @@ -0,0 +1,21 @@ + + + + + + +LetStmt +Assignment +Expr \ No newline at end of file diff --git a/doc/src/sail/diagrams/ForInitStmt.svg b/doc/src/sail/diagrams/ForInitStmt.svg deleted file mode 100644 index 5084deeca..000000000 --- a/doc/src/sail/diagrams/ForInitStmt.svg +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - -Expr -= -Expr - -Expr -+= -Expr - -Expr --= -Expr - -let -Identifier -: -Type - - - -= -Expr - -let -Identifier - - - -= -Expr -Expr \ No newline at end of file diff --git a/doc/src/sail/diagrams/ForPostClause.svg b/doc/src/sail/diagrams/ForPostClause.svg new file mode 100644 index 000000000..a8d0a3765 --- /dev/null +++ b/doc/src/sail/diagrams/ForPostClause.svg @@ -0,0 +1,26 @@ + + + + + + +ForPostItem + + + + +, +ForPostItem + \ No newline at end of file diff --git a/doc/src/sail/diagrams/ForPostItem.svg b/doc/src/sail/diagrams/ForPostItem.svg new file mode 100644 index 000000000..f528ff7bf --- /dev/null +++ b/doc/src/sail/diagrams/ForPostItem.svg @@ -0,0 +1,20 @@ + + + + + + +Assignment +Expr \ No newline at end of file diff --git a/doc/src/sail/diagrams/ForPostStmt.svg b/doc/src/sail/diagrams/ForPostStmt.svg deleted file mode 100644 index 5084deeca..000000000 --- a/doc/src/sail/diagrams/ForPostStmt.svg +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - -Expr -= -Expr - -Expr -+= -Expr - -Expr --= -Expr - -let -Identifier -: -Type - - - -= -Expr - -let -Identifier - - - -= -Expr -Expr \ No newline at end of file diff --git a/doc/src/sail/diagrams/Function.svg b/doc/src/sail/diagrams/Function.svg index 938900ef1..13d144a1b 100644 --- a/doc/src/sail/diagrams/Function.svg +++ b/doc/src/sail/diagrams/Function.svg @@ -1,4 +1,4 @@ - + - - - -Signature -Body - -Signature -{ -Expr -} \ No newline at end of file + + +Signature +Body \ No newline at end of file diff --git a/doc/src/sail/diagrams/FunctionAttribute.svg b/doc/src/sail/diagrams/FunctionAttribute.svg new file mode 100644 index 000000000..2e349c048 --- /dev/null +++ b/doc/src/sail/diagrams/FunctionAttribute.svg @@ -0,0 +1,25 @@ + + + + + + +public +private +external +internal +pure +view +payable \ No newline at end of file diff --git a/doc/src/sail/diagrams/FunctionType.svg b/doc/src/sail/diagrams/FunctionType.svg new file mode 100644 index 000000000..f341f8b42 --- /dev/null +++ b/doc/src/sail/diagrams/FunctionType.svg @@ -0,0 +1,36 @@ + + + + + + +function +( + + +TypeList +) + + +FunctionVisibility + + + +returns +( + + +TypeList +) \ No newline at end of file diff --git a/doc/src/sail/diagrams/FunctionVisibility.svg b/doc/src/sail/diagrams/FunctionVisibility.svg new file mode 100644 index 000000000..5a7f32c01 --- /dev/null +++ b/doc/src/sail/diagrams/FunctionVisibility.svg @@ -0,0 +1,20 @@ + + + + + + +internal +external \ No newline at end of file diff --git a/doc/src/sail/diagrams/GenericArguments.svg b/doc/src/sail/diagrams/GenericArguments.svg new file mode 100644 index 000000000..e954a6ddf --- /dev/null +++ b/doc/src/sail/diagrams/GenericArguments.svg @@ -0,0 +1,21 @@ + + + + + + +< +TypeList +> \ No newline at end of file diff --git a/doc/src/sail/diagrams/GenericParams.svg b/doc/src/sail/diagrams/GenericParams.svg new file mode 100644 index 000000000..18524eacf --- /dev/null +++ b/doc/src/sail/diagrams/GenericParams.svg @@ -0,0 +1,21 @@ + + + + + + +< +IdentifierList +> \ No newline at end of file diff --git a/doc/src/sail/diagrams/Identifier.svg b/doc/src/sail/diagrams/Identifier.svg index 59d90f44e..3c0a35728 100644 --- a/doc/src/sail/diagrams/Identifier.svg +++ b/doc/src/sail/diagrams/Identifier.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file + + +letter +_ + + + + +letter +digit +_ + \ No newline at end of file diff --git a/doc/src/sail/diagrams/HidingList.svg b/doc/src/sail/diagrams/IdentifierList.svg similarity index 100% rename from doc/src/sail/diagrams/HidingList.svg rename to doc/src/sail/diagrams/IdentifierList.svg diff --git a/doc/src/sail/diagrams/ImplDef.svg b/doc/src/sail/diagrams/ImplDef.svg new file mode 100644 index 000000000..59db5c578 --- /dev/null +++ b/doc/src/sail/diagrams/ImplDef.svg @@ -0,0 +1,37 @@ + + + + + + + + +default +impl + + +GenericParams +QualifiedName +GenericArguments + + +WhereClause +{ + + + +Function + +} \ No newline at end of file diff --git a/doc/src/sail/diagrams/Import.svg b/doc/src/sail/diagrams/Import.svg index e058d7946..723325dc4 100644 --- a/doc/src/sail/diagrams/Import.svg +++ b/doc/src/sail/diagrams/Import.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file + + +import +ModulePath +; + +import +* +as +Identifier +from +ModulePath +; + +import +{ +ImportItems +} +from +ModulePath + + + +hiding +{ +IdentifierList +} +; \ No newline at end of file diff --git a/doc/src/sail/diagrams/ImportItem.svg b/doc/src/sail/diagrams/ImportItem.svg index 3663fbc2d..3e6a88a97 100644 --- a/doc/src/sail/diagrams/ImportItem.svg +++ b/doc/src/sail/diagrams/ImportItem.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file +Identifier \ No newline at end of file diff --git a/doc/src/sail/diagrams/InstDef.svg b/doc/src/sail/diagrams/InstDef.svg deleted file mode 100644 index c48170f13..000000000 --- a/doc/src/sail/diagrams/InstDef.svg +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - -SigPrefix - - -default -instance -Type -: -TypeName - - - -( -TypeList -) -{ - - - -Function - -} \ No newline at end of file diff --git a/doc/src/sail/diagrams/Interface.svg b/doc/src/sail/diagrams/Interface.svg new file mode 100644 index 000000000..a948bc678 --- /dev/null +++ b/doc/src/sail/diagrams/Interface.svg @@ -0,0 +1,32 @@ + + + + + + +interface +Identifier + + +GenericParams +{ + + + + +InterfaceSignature +; + +} \ No newline at end of file diff --git a/doc/src/sail/diagrams/InterfaceAttribute.svg b/doc/src/sail/diagrams/InterfaceAttribute.svg new file mode 100644 index 000000000..4cda8d3d8 --- /dev/null +++ b/doc/src/sail/diagrams/InterfaceAttribute.svg @@ -0,0 +1,22 @@ + + + + + + +external +pure +view +payable \ No newline at end of file diff --git a/doc/src/sail/diagrams/InterfaceSignature.svg b/doc/src/sail/diagrams/InterfaceSignature.svg new file mode 100644 index 000000000..7bd56ccec --- /dev/null +++ b/doc/src/sail/diagrams/InterfaceSignature.svg @@ -0,0 +1,45 @@ + + + + + + +function +Identifier + + +GenericParams +( + + +ParamList +) + + + +InterfaceAttribute + + + + +returns +( + + +ReturnItemList +) + + +WhereClause \ No newline at end of file diff --git a/doc/src/sail/diagrams/LambdaExpr.svg b/doc/src/sail/diagrams/LambdaExpr.svg new file mode 100644 index 000000000..8ffc61808 --- /dev/null +++ b/doc/src/sail/diagrams/LambdaExpr.svg @@ -0,0 +1,34 @@ + + + + + + +lam +( + + +LambdaParamList +) + + + +returns +( + + +TypeList +) +Body \ No newline at end of file diff --git a/doc/src/sail/diagrams/LambdaParam.svg b/doc/src/sail/diagrams/LambdaParam.svg new file mode 100644 index 000000000..aca24f9fe --- /dev/null +++ b/doc/src/sail/diagrams/LambdaParam.svg @@ -0,0 +1,27 @@ + + + + + + + + +comptime +Identifier + + + +: +Type \ No newline at end of file diff --git a/doc/src/sail/diagrams/LambdaParamList.svg b/doc/src/sail/diagrams/LambdaParamList.svg new file mode 100644 index 000000000..893fdd6df --- /dev/null +++ b/doc/src/sail/diagrams/LambdaParamList.svg @@ -0,0 +1,26 @@ + + + + + + +LambdaParam + + + + +, +LambdaParam + \ No newline at end of file diff --git a/doc/src/sail/diagrams/LetStmt.svg b/doc/src/sail/diagrams/LetStmt.svg new file mode 100644 index 000000000..6799da404 --- /dev/null +++ b/doc/src/sail/diagrams/LetStmt.svg @@ -0,0 +1,44 @@ + + + + + + +let + + +comptime + + +Identifier + + + +: +Type + + + += +Expr + +BindingTuple + + + +: +Type += +Expr \ No newline at end of file diff --git a/doc/src/sail/diagrams/Library.svg b/doc/src/sail/diagrams/Library.svg new file mode 100644 index 000000000..67deb2ed3 --- /dev/null +++ b/doc/src/sail/diagrams/Library.svg @@ -0,0 +1,30 @@ + + + + + + +library +Identifier + + +GenericParams +{ + + + +LibraryDecl + +} \ No newline at end of file diff --git a/doc/src/sail/diagrams/LibraryDecl.svg b/doc/src/sail/diagrams/LibraryDecl.svg new file mode 100644 index 000000000..2cfc3f11c --- /dev/null +++ b/doc/src/sail/diagrams/LibraryDecl.svg @@ -0,0 +1,22 @@ + + + + + + +FieldDecl +StructDef +EnumDef +Function \ No newline at end of file diff --git a/doc/src/sail/diagrams/Literal.svg b/doc/src/sail/diagrams/Literal.svg index eec1c97bc..5cdc34dc3 100644 --- a/doc/src/sail/diagrams/Literal.svg +++ b/doc/src/sail/diagrams/Literal.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file +StringLiteral +true +false \ No newline at end of file diff --git a/doc/src/sail/diagrams/LogicalAndExpr.svg b/doc/src/sail/diagrams/LogicalAndExpr.svg new file mode 100644 index 000000000..3667b86ed --- /dev/null +++ b/doc/src/sail/diagrams/LogicalAndExpr.svg @@ -0,0 +1,26 @@ + + + + + + +EqualityExpr + + + + +&& +EqualityExpr + \ No newline at end of file diff --git a/doc/src/sail/diagrams/LogicalOrExpr.svg b/doc/src/sail/diagrams/LogicalOrExpr.svg new file mode 100644 index 000000000..8cacc5302 --- /dev/null +++ b/doc/src/sail/diagrams/LogicalOrExpr.svg @@ -0,0 +1,26 @@ + + + + + + +LogicalAndExpr + + + + +|| +LogicalAndExpr + \ No newline at end of file diff --git a/doc/src/sail/diagrams/MatchArgs.svg b/doc/src/sail/diagrams/MatchArgs.svg deleted file mode 100644 index be7cf44e2..000000000 --- a/doc/src/sail/diagrams/MatchArgs.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - -Expr - - - - -, -Expr - \ No newline at end of file diff --git a/doc/src/sail/diagrams/MatchArm.svg b/doc/src/sail/diagrams/MatchArm.svg new file mode 100644 index 000000000..60e93d652 --- /dev/null +++ b/doc/src/sail/diagrams/MatchArm.svg @@ -0,0 +1,25 @@ + + + + + + + +case +MatchPattern +Body + +default +Body \ No newline at end of file diff --git a/doc/src/sail/diagrams/MatchPattern.svg b/doc/src/sail/diagrams/MatchPattern.svg new file mode 100644 index 000000000..fb7a1389a --- /dev/null +++ b/doc/src/sail/diagrams/MatchPattern.svg @@ -0,0 +1,23 @@ + + + + + + +Pattern + +( +PatternList +) \ No newline at end of file diff --git a/doc/src/sail/diagrams/MatchStmt.svg b/doc/src/sail/diagrams/MatchStmt.svg new file mode 100644 index 000000000..7a2487ecc --- /dev/null +++ b/doc/src/sail/diagrams/MatchStmt.svg @@ -0,0 +1,29 @@ + + + + + + +match +( +ExprList +) +{ + + + +MatchArm + +} \ No newline at end of file diff --git a/doc/src/sail/diagrams/ModulePath.svg b/doc/src/sail/diagrams/ModulePath.svg index e519bc14b..a0778eabe 100644 --- a/doc/src/sail/diagrams/ModulePath.svg +++ b/doc/src/sail/diagrams/ModulePath.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file + + + + +@ +Identifier +. +Identifier + + + + +. +Identifier + \ No newline at end of file diff --git a/doc/src/sail/diagrams/MultiplicativeExpr.svg b/doc/src/sail/diagrams/MultiplicativeExpr.svg new file mode 100644 index 000000000..6e3edceac --- /dev/null +++ b/doc/src/sail/diagrams/MultiplicativeExpr.svg @@ -0,0 +1,29 @@ + + + + + + +PowerExpr + + + + + +* +/ +% +PowerExpr + \ No newline at end of file diff --git a/doc/src/sail/diagrams/OperatorSymbol.svg b/doc/src/sail/diagrams/OperatorSymbol.svg deleted file mode 100644 index e14e6eba7..000000000 --- a/doc/src/sail/diagrams/OperatorSymbol.svg +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - -opChar - - - -opChar - \ No newline at end of file diff --git a/doc/src/sail/diagrams/Param.svg b/doc/src/sail/diagrams/Param.svg index be76d0962..aca24f9fe 100644 --- a/doc/src/sail/diagrams/Param.svg +++ b/doc/src/sail/diagrams/Param.svg @@ -1,4 +1,4 @@ - + - - - -Identifier -: -Type -Identifier \ No newline at end of file + + + + +comptime +Identifier + + + +: +Type \ No newline at end of file diff --git a/doc/src/sail/diagrams/Pattern.svg b/doc/src/sail/diagrams/Pattern.svg index 1d7b01261..4aec88a03 100644 --- a/doc/src/sail/diagrams/Pattern.svg +++ b/doc/src/sail/diagrams/Pattern.svg @@ -16,14 +16,14 @@ svg.railroad-diagram rect.group-box { - -TypeName - - - -( -PatternList -) + +QualifiedName + + + +( +PatternList +) . Identifier diff --git a/doc/src/sail/diagrams/PostfixExpr.svg b/doc/src/sail/diagrams/PostfixExpr.svg new file mode 100644 index 000000000..668e2f98a --- /dev/null +++ b/doc/src/sail/diagrams/PostfixExpr.svg @@ -0,0 +1,24 @@ + + + + + + +PrimaryExpr + + + +PostfixSuffix + \ No newline at end of file diff --git a/doc/src/sail/diagrams/PostfixSuffix.svg b/doc/src/sail/diagrams/PostfixSuffix.svg new file mode 100644 index 000000000..fdf9a0e76 --- /dev/null +++ b/doc/src/sail/diagrams/PostfixSuffix.svg @@ -0,0 +1,31 @@ + + + + + + + +( + + +ExprList +) + +. +Identifier + +[ +Expr +] \ No newline at end of file diff --git a/doc/src/sail/diagrams/PowerExpr.svg b/doc/src/sail/diagrams/PowerExpr.svg new file mode 100644 index 000000000..249aff10e --- /dev/null +++ b/doc/src/sail/diagrams/PowerExpr.svg @@ -0,0 +1,24 @@ + + + + + + +CastExpr + + + +** +PowerExpr \ No newline at end of file diff --git a/doc/src/sail/diagrams/Pragma.svg b/doc/src/sail/diagrams/Pragma.svg index 83fa0c6d8..23e73298a 100644 --- a/doc/src/sail/diagrams/Pragma.svg +++ b/doc/src/sail/diagrams/Pragma.svg @@ -1,4 +1,4 @@ - + - - -pragma -PragmaKind - - -PragmaTargets -; \ No newline at end of file + + + +pragma +solidity +PragmaValue +; + +pragma +abicoder +PragmaValue +; + +pragma +solcore +SolcorePragma + + +PragmaTargets +; \ No newline at end of file diff --git a/doc/src/sail/diagrams/PragmaKind.svg b/doc/src/sail/diagrams/PragmaKind.svg deleted file mode 100644 index ec7c8999e..000000000 --- a/doc/src/sail/diagrams/PragmaKind.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - -no-coverage-condition -no-patterson-condition -no-bounded-variable-condition \ No newline at end of file diff --git a/doc/src/sail/diagrams/PragmaToken.svg b/doc/src/sail/diagrams/PragmaToken.svg new file mode 100644 index 000000000..e8d56b9f6 --- /dev/null +++ b/doc/src/sail/diagrams/PragmaToken.svg @@ -0,0 +1,29 @@ + + + + + + +Identifier +Integer +. +^ +~ +< +> +<= +>= += +- \ No newline at end of file diff --git a/doc/src/sail/diagrams/PragmaValue.svg b/doc/src/sail/diagrams/PragmaValue.svg new file mode 100644 index 000000000..a86b82ec8 --- /dev/null +++ b/doc/src/sail/diagrams/PragmaValue.svg @@ -0,0 +1,24 @@ + + + + + + +PragmaToken + + + +PragmaToken + \ No newline at end of file diff --git a/doc/src/sail/diagrams/PrimaryExpr.svg b/doc/src/sail/diagrams/PrimaryExpr.svg new file mode 100644 index 000000000..afeffd6c2 --- /dev/null +++ b/doc/src/sail/diagrams/PrimaryExpr.svg @@ -0,0 +1,45 @@ + + + + + + +QualifiedName + +. +Identifier + + + +( + + +ExprList +) +Literal + +( +) + +( +Expr +) + +( +Expr +, +ExprList +) +LambdaExpr \ No newline at end of file diff --git a/doc/src/sail/diagrams/DataConstrs.svg b/doc/src/sail/diagrams/QualifiedName.svg similarity index 85% rename from doc/src/sail/diagrams/DataConstrs.svg rename to doc/src/sail/diagrams/QualifiedName.svg index 23c723e46..e519bc14b 100644 --- a/doc/src/sail/diagrams/DataConstrs.svg +++ b/doc/src/sail/diagrams/QualifiedName.svg @@ -16,11 +16,11 @@ svg.railroad-diagram rect.group-box { -DataConstr +Identifier -| -DataConstr +. +Identifier \ No newline at end of file diff --git a/doc/src/sail/diagrams/RelationalExpr.svg b/doc/src/sail/diagrams/RelationalExpr.svg new file mode 100644 index 000000000..ce3487c69 --- /dev/null +++ b/doc/src/sail/diagrams/RelationalExpr.svg @@ -0,0 +1,28 @@ + + + + + + +BitwiseOrExpr + + + + +< +> +<= +>= +BitwiseOrExpr \ No newline at end of file diff --git a/doc/src/sail/diagrams/ReturnItem.svg b/doc/src/sail/diagrams/ReturnItem.svg new file mode 100644 index 000000000..0e90edf09 --- /dev/null +++ b/doc/src/sail/diagrams/ReturnItem.svg @@ -0,0 +1,27 @@ + + + + + + + + +comptime + + + +Identifier +: +Type \ No newline at end of file diff --git a/doc/src/sail/diagrams/ReturnItemList.svg b/doc/src/sail/diagrams/ReturnItemList.svg new file mode 100644 index 000000000..b9dd0e777 --- /dev/null +++ b/doc/src/sail/diagrams/ReturnItemList.svg @@ -0,0 +1,26 @@ + + + + + + +ReturnItem + + + + +, +ReturnItem + \ No newline at end of file diff --git a/doc/src/sail/diagrams/ShiftExpr.svg b/doc/src/sail/diagrams/ShiftExpr.svg new file mode 100644 index 000000000..5c173c1d6 --- /dev/null +++ b/doc/src/sail/diagrams/ShiftExpr.svg @@ -0,0 +1,28 @@ + + + + + + +AdditiveExpr + + + + + +<< +>> +AdditiveExpr + \ No newline at end of file diff --git a/doc/src/sail/diagrams/SigPrefix.svg b/doc/src/sail/diagrams/SigPrefix.svg deleted file mode 100644 index 643fc4a40..000000000 --- a/doc/src/sail/diagrams/SigPrefix.svg +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - -forall -TypeVarSeq -. -ConstraintList -=> - -forall -TypeVarSeq -. -ε \ No newline at end of file diff --git a/doc/src/sail/diagrams/Signature.svg b/doc/src/sail/diagrams/Signature.svg index 25bf4f897..6ad361fb3 100644 --- a/doc/src/sail/diagrams/Signature.svg +++ b/doc/src/sail/diagrams/Signature.svg @@ -1,4 +1,4 @@ - + - - -SigPrefix -function -Identifier -( - - -ParamList -) - - - --> -Type \ No newline at end of file + + +function +Identifier + + +GenericParams +( + + +ParamList +) + + + +FunctionAttribute + + + + +returns +( + + +ReturnItemList +) + + +WhereClause \ No newline at end of file diff --git a/doc/src/sail/diagrams/SimpleStmt.svg b/doc/src/sail/diagrams/SimpleStmt.svg new file mode 100644 index 000000000..965080bd2 --- /dev/null +++ b/doc/src/sail/diagrams/SimpleStmt.svg @@ -0,0 +1,21 @@ + + + + + + +LetStmt +Assignment +Expr \ No newline at end of file diff --git a/doc/src/sail/diagrams/SolcorePragma.svg b/doc/src/sail/diagrams/SolcorePragma.svg new file mode 100644 index 000000000..0308f6685 --- /dev/null +++ b/doc/src/sail/diagrams/SolcorePragma.svg @@ -0,0 +1,22 @@ + + + + + + +noCoverageCondition +noPattersonCondition +noBoundVariableCondition +noGenericInstanceFor \ No newline at end of file diff --git a/doc/src/sail/diagrams/Stmt.svg b/doc/src/sail/diagrams/Stmt.svg index d72e302a0..82cac016c 100644 --- a/doc/src/sail/diagrams/Stmt.svg +++ b/doc/src/sail/diagrams/Stmt.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file + + +SimpleStmt +; + +return + + +Expr +; +MatchStmt +AsmBlock + +if +( +Expr +) +Body + + + +else +Body + +for +( + + +ForInitClause +; +Expr +; + + +ForPostClause +) +Body + +while +( +Expr +) +Body + +unchecked +Body + +break +; + +continue +; + +revert +; +Body \ No newline at end of file diff --git a/doc/src/sail/diagrams/StringLiteral.svg b/doc/src/sail/diagrams/StringLiteral.svg index 97867a037..3e04fc001 100644 --- a/doc/src/sail/diagrams/StringLiteral.svg +++ b/doc/src/sail/diagrams/StringLiteral.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file + + + + +char +EscapeSequence + +" \ No newline at end of file diff --git a/doc/src/sail/diagrams/StructDef.svg b/doc/src/sail/diagrams/StructDef.svg new file mode 100644 index 000000000..11e4a2941 --- /dev/null +++ b/doc/src/sail/diagrams/StructDef.svg @@ -0,0 +1,30 @@ + + + + + + +struct +Identifier + + +GenericParams +{ + + + +StructField + +} \ No newline at end of file diff --git a/doc/src/sail/diagrams/StructField.svg b/doc/src/sail/diagrams/StructField.svg new file mode 100644 index 000000000..a4c03a965 --- /dev/null +++ b/doc/src/sail/diagrams/StructField.svg @@ -0,0 +1,22 @@ + + + + + + +Identifier +: +Type +; \ No newline at end of file diff --git a/doc/src/sail/diagrams/TopDecl.svg b/doc/src/sail/diagrams/TopDecl.svg index 45c3b19f4..0ffa9877b 100644 --- a/doc/src/sail/diagrams/TopDecl.svg +++ b/doc/src/sail/diagrams/TopDecl.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file + +Contract +Interface +Library +Function +TraitDef +ImplDef +StructDef +EnumDef +AliasDef +ExportDecl \ No newline at end of file diff --git a/doc/src/sail/diagrams/TraitDef.svg b/doc/src/sail/diagrams/TraitDef.svg new file mode 100644 index 000000000..3ed89421c --- /dev/null +++ b/doc/src/sail/diagrams/TraitDef.svg @@ -0,0 +1,33 @@ + + + + + + +trait +Identifier +GenericParams + + +WhereClause +{ + + + + +Signature +; + +} \ No newline at end of file diff --git a/doc/src/sail/diagrams/Type.svg b/doc/src/sail/diagrams/Type.svg index f6ce6eb34..342cbd40c 100644 --- a/doc/src/sail/diagrams/Type.svg +++ b/doc/src/sail/diagrams/Type.svg @@ -1,4 +1,4 @@ - + - - - -TypeName - - - -( -TypeList -) - -( -TypeList -) --> -Type - -( -TypeList -) - -@ -Type \ No newline at end of file + + +TypeAtom + + + +TypeSuffix + \ No newline at end of file diff --git a/doc/src/sail/diagrams/TypeAtom.svg b/doc/src/sail/diagrams/TypeAtom.svg new file mode 100644 index 000000000..36edf2dd9 --- /dev/null +++ b/doc/src/sail/diagrams/TypeAtom.svg @@ -0,0 +1,44 @@ + + + + + + + +QualifiedName + + +GenericArguments + +mapping +( +Type +=> +Type +) + +( +) + +( +Type +) + +( +Type +, +TypeList +) +FunctionType \ No newline at end of file diff --git a/doc/src/sail/diagrams/TypeName.svg b/doc/src/sail/diagrams/TypeName.svg deleted file mode 100644 index 7021b49d9..000000000 --- a/doc/src/sail/diagrams/TypeName.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - -Identifier - -TypeName -. -Identifier \ No newline at end of file diff --git a/doc/src/sail/diagrams/TypeSuffix.svg b/doc/src/sail/diagrams/TypeSuffix.svg new file mode 100644 index 000000000..ce66a45d7 --- /dev/null +++ b/doc/src/sail/diagrams/TypeSuffix.svg @@ -0,0 +1,20 @@ + + + + + + +ArraySuffix +DataLocation \ No newline at end of file diff --git a/doc/src/sail/diagrams/TypeSynonym.svg b/doc/src/sail/diagrams/TypeSynonym.svg deleted file mode 100644 index f9a99bffb..000000000 --- a/doc/src/sail/diagrams/TypeSynonym.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - -type -Identifier - - -TypeVarParams -= -Type -; \ No newline at end of file diff --git a/doc/src/sail/diagrams/TypeVarParams.svg b/doc/src/sail/diagrams/TypeVarParams.svg deleted file mode 100644 index 737596a66..000000000 --- a/doc/src/sail/diagrams/TypeVarParams.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - -( -Identifier - - - - -, -Identifier - -) \ No newline at end of file diff --git a/doc/src/sail/diagrams/TypeVarSeq.svg b/doc/src/sail/diagrams/TypeVarSeq.svg deleted file mode 100644 index 105f247ea..000000000 --- a/doc/src/sail/diagrams/TypeVarSeq.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - -Identifier - \ No newline at end of file diff --git a/doc/src/sail/diagrams/UnaryExpr.svg b/doc/src/sail/diagrams/UnaryExpr.svg new file mode 100644 index 000000000..a14b5244e --- /dev/null +++ b/doc/src/sail/diagrams/UnaryExpr.svg @@ -0,0 +1,22 @@ + + + + + + + +! +UnaryExpr +PostfixExpr \ No newline at end of file diff --git a/doc/src/sail/diagrams/WhereClause.svg b/doc/src/sail/diagrams/WhereClause.svg new file mode 100644 index 000000000..b0898d9b1 --- /dev/null +++ b/doc/src/sail/diagrams/WhereClause.svg @@ -0,0 +1,20 @@ + + + + + + +where +ConstraintList \ No newline at end of file diff --git a/doc/src/sail/diagrams/YMeta.svg b/doc/src/sail/diagrams/YMeta.svg new file mode 100644 index 000000000..00127a1a5 --- /dev/null +++ b/doc/src/sail/diagrams/YMeta.svg @@ -0,0 +1,34 @@ + + + + + + + +` + + + +char + +` + +${ + + + +char + +} \ No newline at end of file diff --git a/doc/src/sail/diagrams/YulExpr.svg b/doc/src/sail/diagrams/YulExpr.svg index 39a6b28a1..0c2179666 100644 --- a/doc/src/sail/diagrams/YulExpr.svg +++ b/doc/src/sail/diagrams/YulExpr.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file + +YulLiteral +YulIdentifier + +YulIdentifier +( + + +YulExprList +) +YMeta + +return +( + + +YulExprList +) \ No newline at end of file diff --git a/doc/src/sail/diagrams/YulIdentifier.svg b/doc/src/sail/diagrams/YulIdentifier.svg new file mode 100644 index 000000000..a67afaf39 --- /dev/null +++ b/doc/src/sail/diagrams/YulIdentifier.svg @@ -0,0 +1,31 @@ + + + + + + + +letter +_ +$ + + + + +letter +digit +_ +$ + \ No newline at end of file diff --git a/doc/src/sail/diagrams/YulLiteral.svg b/doc/src/sail/diagrams/YulLiteral.svg index eec1c97bc..5cdc34dc3 100644 --- a/doc/src/sail/diagrams/YulLiteral.svg +++ b/doc/src/sail/diagrams/YulLiteral.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file +StringLiteral +true +false \ No newline at end of file diff --git a/doc/src/sail/diagrams/YulNames.svg b/doc/src/sail/diagrams/YulNames.svg index e14782bf6..db025fa43 100644 --- a/doc/src/sail/diagrams/YulNames.svg +++ b/doc/src/sail/diagrams/YulNames.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file + +YulIdentifier + + + + +, +YulIdentifier + \ No newline at end of file diff --git a/doc/src/sail/diagrams/YulStmt.svg b/doc/src/sail/diagrams/YulStmt.svg index e909ffde9..edf6c4a25 100644 --- a/doc/src/sail/diagrams/YulStmt.svg +++ b/doc/src/sail/diagrams/YulStmt.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file + + +YulNames +:= +YulExpr + +let +YulNames + + + +:= +YulExpr + +function +YulIdentifier +( + + +YulNames +) + + + +-> +YulNames +{ + + + +YulStmt + +} + +if +YulExpr +{ + + + +YulStmt + +} + +switch +YulExpr + + + +YulCase + + + + +default +{ + + + +YulStmt + +} + +for +{ + + + +YulStmt + +} +YulExpr +{ + + + +YulStmt + +} +{ + + + +YulStmt + +} +continue +break +leave +YulExpr + +{ + + + +YulStmt + +} \ No newline at end of file From fb1862aba5ee6e9243efe92a0761e33777bb7236 Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Thu, 23 Jul 2026 23:10:03 +0900 Subject: [PATCH 08/33] Harden railroad diagram generation --- doc/railroad/bnf2railroad.py | 189 +++++++++++++++++++-- doc/src/sail/diagrams/.bnf2railroad-output | 2 + 2 files changed, 178 insertions(+), 13 deletions(-) create mode 100644 doc/src/sail/diagrams/.bnf2railroad-output diff --git a/doc/railroad/bnf2railroad.py b/doc/railroad/bnf2railroad.py index 8ae02803f..3b486bad2 100644 --- a/doc/railroad/bnf2railroad.py +++ b/doc/railroad/bnf2railroad.py @@ -3,13 +3,15 @@ bnf2railroad.py — BNF/EBNF grammar file -> SVG railroad diagrams Usage - python3 bnf2railroad.py GRAMMAR.bnf OUTPUT_DIR/ + python3 bnf2railroad.py [--clean | --check] GRAMMAR.bnf OUTPUT_DIR/ """ +import filecmp import io import os import re import sys +import tempfile try: import railroad as rr @@ -36,6 +38,12 @@ } """ +_OUTPUT_MARKER = ".bnf2railroad-output" +_OUTPUT_MARKER_TEXT = """\ +This directory is owned by doc/railroad/bnf2railroad.py. +All SVG files in it may be removed by --clean. +""" + def _strip_line_comment(line: str) -> str: """Remove a trailing # or -- comment, respecting quoted strings.""" @@ -242,7 +250,7 @@ def _to_rr(node): def _write_svg(diagram, path: str): - """Write diagram to path, injecting xmlns and embedded CSS.""" + """Atomically write a diagram without following an output symlink.""" buf = io.StringIO() diagram.writeSvg(buf.write) svg = buf.getvalue() @@ -250,24 +258,112 @@ def _write_svg(diagram, path: str): svg = svg.replace(' str: + return os.path.join(output_dir, _OUTPUT_MARKER) + + +def mark_generated_output_dir(output_dir: str) -> None: + marker_path = _marker_path(output_dir) + try: + with open(marker_path, "x") as marker: + marker.write(_OUTPUT_MARKER_TEXT) + except FileExistsError: + sys.exit( + f"refusing to replace existing output marker {marker_path!r}" + ) + + +def output_dir_is_owned(output_dir: str) -> bool: + marker_path = _marker_path(output_dir) + if not os.path.lexists(marker_path): + return False + try: + if os.path.islink(marker_path): + raise OSError("marker must not be a symlink") + with open(marker_path) as marker: + marker_text = marker.read() + except (FileNotFoundError, OSError) as exc: + sys.exit( + f"refusing output directory {output_dir!r}: " + f"{exc}" + ) + if marker_text != _OUTPUT_MARKER_TEXT: + sys.exit( + f"refusing output directory {output_dir!r}: " + f"invalid {_OUTPUT_MARKER} marker" + ) + return True + + +def require_owned_output_dir(output_dir: str, operation: str) -> None: + if not output_dir_is_owned(output_dir): + sys.exit( + f"refusing {operation} for unowned output directory {output_dir!r}: " + f"missing {_OUTPUT_MARKER} marker" + ) + + +def prepare_generated_output_dir(output_dir: str) -> None: + if not os.path.lexists(output_dir): + os.makedirs(output_dir) + if os.path.islink(output_dir) or not os.path.isdir(output_dir): + sys.exit( + f"refusing unsafe output directory {output_dir!r}: " + "expected a real directory, not a symlink or file" + ) + if output_dir_is_owned(output_dir): + return + entries = os.listdir(output_dir) + if entries: + sys.exit( + f"refusing to generate into unowned non-empty output directory " + f"{output_dir!r}; initialize a new or empty directory instead" + ) + mark_generated_output_dir(output_dir) + + +def reject_svg_symlinks(output_dir: str, operation: str) -> None: + for entry in os.scandir(output_dir): + if entry.name.endswith(".svg") and entry.is_symlink(): + sys.exit( + f"refusing {operation} with symlink output " + f"{entry.path!r}" + ) -def main(argv=None): - if argv is None: - argv = sys.argv[1:] - if len(argv) < 2: - print(__doc__) - sys.exit(1) - grammar_file, output_dir = argv[0], argv[1] - os.makedirs(output_dir, exist_ok=True) +def generate(grammar_file: str, output_dir: str) -> set[str]: + prepare_generated_output_dir(output_dir) + reject_svg_symlinks(output_dir, "generation") rules = read_rules(grammar_file) print(f"Loaded {len(rules)} rule(s) from {grammar_file!r}") ok = failed = 0 + generated: set[str] = set() for name, body in rules.items(): if not body.strip(): print(f" skip {name} (empty body)") @@ -277,6 +373,7 @@ def main(argv=None): diagram = rr.Diagram(_to_rr(ast)) out = os.path.join(output_dir, f"{name}.svg") _write_svg(diagram, out) + generated.add(f"{name}.svg") print(f" wrote {out}") ok += 1 except Exception as exc: @@ -286,6 +383,72 @@ def main(argv=None): print(f"\n{ok} diagram(s) written, {failed} error(s).") if failed: sys.exit(1) + return generated + + +def svg_names(directory: str) -> set[str]: + return { + entry.name + for entry in os.scandir(directory) + if entry.name.endswith(".svg") and entry.is_file(follow_symlinks=False) + } + + +def check_generated(grammar_file: str, output_dir: str) -> None: + require_owned_output_dir(output_dir, "--check") + reject_svg_symlinks(output_dir, "--check") + with tempfile.TemporaryDirectory(prefix="sail-railroad-") as expected_dir: + expected = generate(grammar_file, expected_dir) + actual = svg_names(output_dir) + missing = sorted(expected - actual) + stale = sorted(actual - expected) + changed = sorted( + name + for name in expected & actual + if not filecmp.cmp( + os.path.join(expected_dir, name), + os.path.join(output_dir, name), + shallow=False, + ) + ) + if missing or stale or changed: + for label, names in ( + ("missing", missing), + ("stale", stale), + ("out of date", changed), + ): + for name in names: + print(f" {label}: {name}", file=sys.stderr) + sys.exit("railroad diagrams are not synchronized with the grammar") + print(f"All {len(expected)} railroad diagram(s) are up to date.") + + +def main(argv=None): + if argv is None: + argv = sys.argv[1:] + + check = "--check" in argv + clean = "--clean" in argv + positional = [arg for arg in argv if arg not in {"--check", "--clean"}] + if check and clean: + sys.exit("--check and --clean cannot be used together") + if len(positional) != 2: + print(__doc__) + sys.exit(1) + + grammar_file, output_dir = positional + if check: + check_generated(grammar_file, output_dir) + return + + if clean: + require_owned_output_dir(output_dir, "--clean") + generated = generate(grammar_file, output_dir) + if clean: + stale = sorted(svg_names(output_dir) - generated) + for name in stale: + os.remove(os.path.join(output_dir, name)) + print(f" removed stale {os.path.join(output_dir, name)}") if __name__ == '__main__': diff --git a/doc/src/sail/diagrams/.bnf2railroad-output b/doc/src/sail/diagrams/.bnf2railroad-output new file mode 100644 index 000000000..d09c68c76 --- /dev/null +++ b/doc/src/sail/diagrams/.bnf2railroad-output @@ -0,0 +1,2 @@ +This directory is owned by doc/railroad/bnf2railroad.py. +All SVG files in it may be removed by --clean. From e6eb6066d06c6eb751380ae04bd78bcba7979336 Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Thu, 23 Jul 2026 23:10:18 +0900 Subject: [PATCH 09/33] Document audited syntax behavior --- doc/src/sail/datatypes.md | 15 +++++---- doc/src/sail/syntax.md | 71 ++++++++++++++++++++++++++++++++------- 2 files changed, 67 insertions(+), 19 deletions(-) diff --git a/doc/src/sail/datatypes.md b/doc/src/sail/datatypes.md index 16b60bd6e..b78063bfd 100644 --- a/doc/src/sail/datatypes.md +++ b/doc/src/sail/datatypes.md @@ -86,8 +86,9 @@ A constructor with fields is applied like a function: `TxOutcome.Success(TxStatus.Settled)` produces a value of type `TxOutcome` wrapping a value of type `TxStatus`. -Fields are extracted by pattern matching; there is no record-style field access. -The pattern mirrors the constructor application: +Enum-constructor payloads are extracted by pattern matching; unlike named +`struct` fields, positional payloads have no record-style field access. The +pattern mirrors the constructor application: ```solcore function outcomeCode(x: TxOutcome) returns (word) { @@ -358,8 +359,8 @@ a compile-time device: the compiler expands them before type checking and they leave no trace in the generated code. ```solcore -type Int is word; -type Point is pair; +alias Int = word; +alias Point = pair; function makePoint(x: Int, y: Int) returns (Point) { return (x, y); @@ -377,12 +378,12 @@ function getX(p: Point) returns (Int) { Like data types, synonyms can have type parameters: ```solcore -type Map is pair; // toy example +alias Map = pair; // toy example ``` > **Warning** Recursive type synonyms are not allowed. A synonym must not refer -> directly or indirectly to itself. Attempting to define `type A is B` and -> `type B is A` simultaneously is a compile-time error. +> directly or indirectly to itself. Attempting to define `alias A = B` and +> `alias B = A` simultaneously is a compile-time error. --- diff --git a/doc/src/sail/syntax.md b/doc/src/sail/syntax.md index e4aa5f7d0..23fdb1f77 100644 --- a/doc/src/sail/syntax.md +++ b/doc/src/sail/syntax.md @@ -99,7 +99,9 @@ bytes calldata ``` Array suffixes and the data locations `memory`, `storage`, and `calldata` -follow the complete element type. Function types use `function(...)` and +wrap the complete type to their left. They are regular Core type suffixes, so +they may be interleaved or repeated when representing nested references, for +example `word[] memory[] storage`. Function types use `function(...)` and `returns (...)`; the former source-level arrow type is not part of the grammar. Explicit conversion uses `as` with a complete target type: @@ -130,6 +132,19 @@ struct Pair { } ``` +Named struct fields can be read with postfix member access. Field reads may be +chained, and the receiver is evaluated exactly once: + +```solidity +function first(p: Pair) returns (word) { + return p.x; +} +``` + +Assignment through a struct member is not implemented yet and is rejected with +a dedicated diagnostic instead of being interpreted as an unrelated variable +assignment. + Ordinary enums and payload-carrying algebraic data types share one declaration form: @@ -153,12 +168,17 @@ Option.Some(1) Option.None ``` -A user-defined type uses `is`: +A transparent type synonym uses `alias` and `=`: ```solidity -type Wad is word; +alias Word = word; ``` +The Solidity spelling `type Wad is word;` is reserved for a nominal +user-defined value type. The current compiler rejects that form until nominal +wrapping, unwrapping, ABI, and storage semantics are implemented; it is never +treated as a transparent alias. + --- ## Traits, Implementations, and Generics @@ -232,6 +252,11 @@ library Hashing { } ``` +Every interface function must declare `external` exactly once. It may also +declare one of `pure`, `view`, or `payable`; omitted, `public`, `internal`, and +`private` interface visibility are rejected rather than silently omitted from +the ABI. + --- ## Functions @@ -257,6 +282,12 @@ function nop() { } ``` +Contract and library functions accept at most one visibility modifier +(`public`, `external`, `internal`, or `private`) and at most one mutability +modifier (`pure`, `view`, or `payable`). Module-level functions, trait +signatures, and implementation methods do not have contract visibility and +cannot be `payable`; they may use `pure` or `view`. + Generic parameters follow the function name. Constraints appear after the return clause. @@ -307,9 +338,10 @@ assembly { ... } revert; ``` -Assignments support `=`, compound assignment operators, field access, and -indexing. A plain call or other expression used as a statement also ends in -`;`. +Assignments support `=` and compound assignment operators for assignable +variables and indexed values. Struct-member lvalues are the exception noted +above and are rejected until update lowering is implemented. A plain call or +other expression used as a statement also ends in `;`. --- @@ -350,6 +382,8 @@ unary and binary operators, conditional expressions, and conversions: ```solidity f(x, y) +makeAdder(x)(y) +callbacks[index](value) token.balanceOf(account) values[index] !ok @@ -362,10 +396,17 @@ condition ? yes : no expression as T ``` -Power is right-associative. Multiplication and addition, shifts, comparisons, -equality, bitwise operators, logical operators, and the conditional operator -then follow in decreasing precedence. Conversion with `as` binds more tightly -than power and is left-associative. +Call, member, and indexing suffixes may be repeated on any primary expression. +Direct and member calls keep their ordinary call representation; calls on +computed values are checked through the `invokable` abstraction. + +Power is right-associative. In decreasing precedence, the remaining binary +groups are multiplication, addition, shifts, bitwise `&`, bitwise `^`, bitwise +`|`, comparisons, equality, logical `&&`, and logical `||`; the conditional +operator is lower still. Conversion with `as` binds more tightly than power and +is left-associative. Comparison and equality operators are non-associative, so +chains such as `a < b < c` and `a == b == c` must be written as explicit +logical combinations. The compiler retains `lam(...) returns (...) { ... }` for lambda expressions as a Core extension. @@ -375,8 +416,14 @@ a Core extension. ## Assembly An `assembly { ... }` block embeds the Yul sublanguage. Yul declarations, -assignment, `if`, `switch`, and `for` retain Yul syntax and do not use SAIL -statement terminators. +assignment, `if`, `switch`, `for`, `break`, `continue`, and `leave` retain Yul +syntax and do not use SAIL statement terminators. + +Yul function declarations use `function name(args) -> results { ... }`. An +arrow must be followed by at least one result name. Yul identifiers may begin +with `_` or `$`, and boolean literals are `true` and `false`. A Yul `let` or +assignment must name at least one target. Backtick-delimited and `${...}` meta +expressions remain available as a compiler extension. ```solidity function load(slot: word) returns (word) { From b79493d9ef77686d4c4ba0c1620cb1651bd80ab6 Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Thu, 23 Jul 2026 23:10:35 +0900 Subject: [PATCH 10/33] Add hermetic syntax validation checks --- flake.nix | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/flake.nix b/flake.nix index 826e75154..55a4087cd 100644 --- a/flake.nix +++ b/flake.nix @@ -87,6 +87,80 @@ touch $out ''; + syntax-migration = pkgs.runCommand "syntax-migration-check" { + nativeBuildInputs = [ + pkgs.gitMinimal + pkgs.python3 + ]; + src = gitignore ./.; + } '' + # Exercise the packaged-source fallback against exactly the files + # shipped by the gitignore filter. The filtered source can retain + # Git metadata whose index names intentionally excluded files + # (for example test/examples/spec/attic), so it is not itself a + # complete Git worktree. + cp -R "$src" packaged-source + chmod -R u+w packaged-source + rm -rf packaged-source/.git + cd packaged-source + python3 scripts/test_migrate_new_syntax.py + python3 scripts/migrate_new_syntax.py --check + touch $out + ''; + + railroad-diagrams = pkgs.runCommand "railroad-diagram-check" { + nativeBuildInputs = [ + pkgs.python3 + pkgs.python3Packages.railroad-diagrams + ]; + src = gitignore ./.; + } '' + cd $src + python3 doc/railroad/bnf2railroad.py --check \ + doc/railroad/sail.bnf doc/src/sail/diagrams + markerless_dir="$(mktemp -d)" + cp doc/src/sail/diagrams/*.svg "$markerless_dir/" + if python3 doc/railroad/bnf2railroad.py --check \ + doc/railroad/sail.bnf "$markerless_dir"; then + echo "--check unexpectedly accepted markerless output" >&2 + exit 1 + fi + unowned_dir="$(mktemp -d)" + printf '%s\n' manual > "$unowned_dir/manual.svg" + if python3 doc/railroad/bnf2railroad.py \ + doc/railroad/sail.bnf "$unowned_dir"; then + echo "generation unexpectedly adopted a non-empty unowned directory" >&2 + exit 1 + fi + test ! -e "$unowned_dir/.bnf2railroad-output" + if python3 doc/railroad/bnf2railroad.py --clean \ + doc/railroad/sail.bnf "$unowned_dir"; then + echo "--clean unexpectedly accepted an unowned directory" >&2 + exit 1 + fi + grep -Fxqx manual "$unowned_dir/manual.svg" + symlink_dir="$(mktemp -d)" + external_svg="$(mktemp)" + printf '%s\n' external-sentinel > "$external_svg" + cp doc/src/sail/diagrams/.bnf2railroad-output "$symlink_dir/" + cp doc/src/sail/diagrams/*.svg "$symlink_dir/" + rm "$symlink_dir/Param.svg" + ln -s "$external_svg" "$symlink_dir/Param.svg" + if python3 doc/railroad/bnf2railroad.py --check \ + doc/railroad/sail.bnf "$symlink_dir"; then + echo "--check unexpectedly accepted a symlink output" >&2 + exit 1 + fi + grep -Fxqx external-sentinel "$external_svg" + if python3 doc/railroad/bnf2railroad.py \ + doc/railroad/sail.bnf "$symlink_dir"; then + echo "generation unexpectedly accepted a symlink output" >&2 + exit 1 + fi + grep -Fxqx external-sentinel "$external_svg" + touch $out + ''; + contests = pkgs.stdenv.mkDerivation { pname = "solcore-contests"; version = "0.0"; @@ -155,6 +229,8 @@ pkgs.go-ethereum pkgs.jq pkgs.nlohmann_json + pkgs.python3 + pkgs.python3Packages.railroad-diagrams pkgs.solc evmone-lib (hspkgs.hevm.overrideAttrs (old: { patches = []; })) From 5a28d570e6d2ecfa18502673204f1abb3c3602fc Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Fri, 24 Jul 2026 19:05:17 +0900 Subject: [PATCH 11/33] Disambiguate cast targets from operators --- src/Solcore/Frontend/Parser/SolcoreTypes.hs | 5 ++++- test/ParserTests.hs | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/Solcore/Frontend/Parser/SolcoreTypes.hs b/src/Solcore/Frontend/Parser/SolcoreTypes.hs index 95bcbebe1..947c1f32a 100644 --- a/src/Solcore/Frontend/Parser/SolcoreTypes.hs +++ b/src/Solcore/Frontend/Parser/SolcoreTypes.hs @@ -83,7 +83,10 @@ arraySizeP = <|> typeP namedTypeP :: Parser Ty -namedTypeP = TyCon <$> qualifiedName <*> option [] (angles (typeP `sepBy1` comma)) +namedTypeP = + TyCon + <$> qualifiedName + <*> option [] (try (angles (typeP `sepBy1` comma))) parenTypeP :: Parser Ty parenTypeP = parens (mkParenTy <$> (typeP `sepBy` comma)) diff --git a/test/ParserTests.hs b/test/ParserTests.hs index f5c05ef1d..6d1dbd483 100644 --- a/test/ParserTests.hs +++ b/test/ParserTests.hs @@ -505,6 +505,26 @@ exprTests = expP "x as word + y" (ExpPlus (TyExp (var "x") word) (var "y")), + testCase "conversion target does not consume relational or shift operators" $ do + parsesAs + expP + "x as word < y" + (ExpLT (TyExp (var "x") word) (var "y")) + parsesAs + expP + "x as word <= y" + (ExpLE (TyExp (var "x") word) (var "y")) + parsesAs + expP + "x as word << y" + (ExpShiftL (TyExp (var "x") word) (var "y")), + testCase "converted comparisons and shifts survive source pretty-printing" $ + mapM_ + roundTripsExp + [ "x as word < y", + "x as word <= y", + "x as word << y" + ], testCase "parentheses allow converting a complete addition" $ parsesAs expP From 93cdf7d6beeb5c40871d78d703c14485937d470a Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Fri, 24 Jul 2026 19:06:56 +0900 Subject: [PATCH 12/33] Preserve empty return clauses in semantic pretty output --- src/Solcore/Frontend/Pretty/SolcorePretty.hs | 7 +++- test/ParserTests.hs | 38 ++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/Solcore/Frontend/Pretty/SolcorePretty.hs b/src/Solcore/Frontend/Pretty/SolcorePretty.hs index 6867a6eec..e90379e07 100644 --- a/src/Solcore/Frontend/Pretty/SolcorePretty.hs +++ b/src/Solcore/Frontend/Pretty/SolcorePretty.hs @@ -311,7 +311,12 @@ pprContractSignature isExternal sig@(Signature vs ctx n ps rc ty _) = pprResolvedReturns :: Signature a -> Bool -> Maybe Ty -> Doc pprResolvedReturns sig returnComptime returnTy = case sigReturnItems sig of - [] -> pprRetTy returnComptime returnTy + [] + | not returnComptime, + Just (TyCon n []) <- returnTy, + isUnit n -> + empty + | otherwise -> pprRetTy returnComptime returnTy items -> text "returns" <+> parens (commaSep (map pprResolvedReturnItem items)) diff --git a/test/ParserTests.hs b/test/ParserTests.hs index 6d1dbd483..4058d362b 100644 --- a/test/ParserTests.hs +++ b/test/ParserTests.hs @@ -1970,6 +1970,44 @@ declarationShellTests = ) Right got -> assertFailure ("Unexpected resolved output: " ++ show got), + testCase "resolved pretty-printing does not invent a unit return item" $ + case runParserE + (sc *> topDeclP <* eof) + "" + "contract C { function nop() public { return; } }" of + Left err -> assertFailure ("Parse error:\n" ++ err) + Right parsed -> do + resolved <- nameResolution (CompUnit [] [parsed]) + case resolved of + Left err -> + assertFailure ("Name resolution failed: " ++ show err) + Right (Resolved.CompUnit _ [resolvedDecl]) -> do + let rendered = SolcorePretty.pretty resolvedDecl + assertBool + ("semantic pretty output invented a return clause:\n" ++ rendered) + (not ("returns" `isInfixOf` rendered)) + nameResolutionSucceeds rendered + Right got -> + assertFailure ("Unexpected resolved output: " ++ show got), + testCase "resolved pretty-printing preserves an explicit unit return item" $ + case runParserE + (sc *> topDeclP <* eof) + "" + "function unitValue() returns (()) { return (); }" of + Left err -> assertFailure ("Parse error:\n" ++ err) + Right parsed -> do + resolved <- nameResolution (CompUnit [] [parsed]) + case resolved of + Left err -> + assertFailure ("Name resolution failed: " ++ show err) + Right (Resolved.CompUnit _ [resolvedDecl]) -> do + let rendered = SolcorePretty.pretty resolvedDecl + assertBool + ("semantic pretty output lost the explicit unit item:\n" ++ rendered) + ("returns (())" `isInfixOf` rendered) + nameResolutionSucceeds rendered + Right got -> + assertFailure ("Unexpected resolved output: " ++ show got), testCase "resolved pretty-printing keeps bare contract fields reusable" $ case runParserE (sc *> topDeclP <* eof) From e961405d9b4a9585cbc9ec090170127605f5c2b8 Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Fri, 24 Jul 2026 19:09:44 +0900 Subject: [PATCH 13/33] Escape Yul strings in pretty output --- src/Language/Yul.hs | 9 ++++++--- test/ParserTests.hs | 9 +++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/Language/Yul.hs b/src/Language/Yul.hs index ca3a40991..43a77cd61 100644 --- a/src/Language/Yul.hs +++ b/src/Language/Yul.hs @@ -227,13 +227,16 @@ instance Pretty YulExp where instance Pretty YLiteral where ppr (YulNumber n) = integer n - ppr (YulString s) = doubleQuotes (text s) + ppr (YulString s) = pprQuotedString s ppr YulTrue = text "true" ppr YulFalse = text "false" instance Pretty YulData where - ppr (YulData name val) = hsep [text "data", doubleQuotes $ text name, ppr val] + ppr (YulData name val) = hsep [text "data", pprQuotedString name, ppr val] instance Pretty HexOrString where ppr (DHex s) = text "hex" <> doubleQuotes (text s) - ppr (DString s) = doubleQuotes (text s) + ppr (DString s) = pprQuotedString s + +pprQuotedString :: String -> Doc +pprQuotedString = text . show diff --git a/test/ParserTests.hs b/test/ParserTests.hs index 4058d362b..c47c92501 100644 --- a/test/ParserTests.hs +++ b/test/ParserTests.hs @@ -910,6 +910,15 @@ stmtTests = YLet ["second"] (Just (YMeta "interpolationHole")) ] ), + testCase "Yul string literals survive source pretty-printing" $ + mapM_ + roundTripsStmt + [ "assembly { let x := \"a\\\"b\" }", + "assembly { let x := \"a\\\\b\" }", + "assembly { let x := \"a\\nb\" }", + "assembly { let x := \"a\\tb\" }", + "assembly { let x := \"a\\rb\" }" + ], testCase "Yul let requires at least one name" $ parseFails stmtP From e3b8f1aeb5ea0793916fd4083c374b50390b8caf Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Fri, 24 Jul 2026 19:12:00 +0900 Subject: [PATCH 14/33] Preserve Yul meta expressions in pretty output --- src/Language/Yul.hs | 4 +++- test/ParserTests.hs | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Language/Yul.hs b/src/Language/Yul.hs index 43a77cd61..82552b224 100644 --- a/src/Language/Yul.hs +++ b/src/Language/Yul.hs @@ -223,7 +223,9 @@ instance Pretty YulExp where ppr (YCall name args) = ppr name >< parens (commaSepList args) ppr (YIdent name) = ppr name ppr (YLit lit) = ppr lit - ppr (YMeta s) = text s + ppr (YMeta s) + | '`' `elem` s = text "${" <> text s <> char '}' + | otherwise = char '`' <> text s <> char '`' instance Pretty YLiteral where ppr (YulNumber n) = integer n diff --git a/test/ParserTests.hs b/test/ParserTests.hs index c47c92501..0ef17fc4c 100644 --- a/test/ParserTests.hs +++ b/test/ParserTests.hs @@ -910,6 +910,14 @@ stmtTests = YLet ["second"] (Just (YMeta "interpolationHole")) ] ), + testCase "Yul metadata expressions survive source pretty-printing" $ + mapM_ + roundTripsStmt + [ "assembly { let x := `hole` }", + "assembly { let x := ${hole} }", + "assembly { let x := `a}b` }", + "assembly { let x := ${a`b} }" + ], testCase "Yul string literals survive source pretty-printing" $ mapM_ roundTripsStmt From a0ccb5c263de126ac0ac3099db4ca6265763b4c4 Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Fri, 24 Jul 2026 19:12:25 +0900 Subject: [PATCH 15/33] Preserve nested comments during syntax migration --- scripts/migrate_new_syntax.py | 13 +++++++++++-- scripts/test_migrate_new_syntax.py | 6 ++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/scripts/migrate_new_syntax.py b/scripts/migrate_new_syntax.py index c758ac0eb..f348fbe46 100755 --- a/scripts/migrate_new_syntax.py +++ b/scripts/migrate_new_syntax.py @@ -148,8 +148,17 @@ def tokenize(source: str) -> list[Token]: continue if source.startswith("/*", i): - close = source.find("*/", i + 2) - i = n if close < 0 else close + 2 + depth = 1 + i += 2 + while i < n and depth > 0: + if source.startswith("/*", i): + depth += 1 + i += 2 + elif source.startswith("*/", i): + depth -= 1 + i += 2 + else: + i += 1 tokens.append(Token("comment", source[start:i], start, i)) continue diff --git a/scripts/test_migrate_new_syntax.py b/scripts/test_migrate_new_syntax.py index 8be759160..af0ae7eda 100644 --- a/scripts/test_migrate_new_syntax.py +++ b/scripts/test_migrate_new_syntax.py @@ -60,6 +60,12 @@ def test_data_identifiers_are_stable(self) -> None: with self.subTest(source=source): self.assert_stable(source) + def test_nested_block_comments_are_stable(self) -> None: + self.assert_stable( + "/* outer /* inner */ type Word = word; */\n" + "function ok() {}\n" + ) + def test_transparent_aliases_are_stable(self) -> None: cases = ( "alias Word = uint256;\n", From 3aae89e2a9a3607019c52ad75735bb35337c990e Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Fri, 24 Jul 2026 19:12:56 +0900 Subject: [PATCH 16/33] Preserve function type visibility during migration --- scripts/migrate_new_syntax.py | 8 ++++++-- scripts/test_migrate_new_syntax.py | 9 +++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/scripts/migrate_new_syntax.py b/scripts/migrate_new_syntax.py index f348fbe46..55022bd5d 100755 --- a/scripts/migrate_new_syntax.py +++ b/scripts/migrate_new_syntax.py @@ -394,9 +394,13 @@ def parse_atom(self, index: int) -> tuple[str, int] | None: returns = self.parse_type_list(index + 2, ret_close) if returns is None: return None - attr_text = " ".join(attributes or ["internal"]) + attr_text = ( + " " + " ".join(attributes) + if attributes + else "" + ) rendered = ( - f"function({', '.join(args)}) {attr_text} " + f"function({', '.join(args)}){attr_text} " f"returns ({', '.join(returns)})" ) return self.parse_array_suffix(rendered, ret_close + 1) diff --git a/scripts/test_migrate_new_syntax.py b/scripts/test_migrate_new_syntax.py index af0ae7eda..4633253ff 100644 --- a/scripts/test_migrate_new_syntax.py +++ b/scripts/test_migrate_new_syntax.py @@ -66,6 +66,15 @@ def test_nested_block_comments_are_stable(self) -> None: "function ok() {}\n" ) + def test_function_type_visibility_is_stable(self) -> None: + for visibility in ("", " internal", " external"): + with self.subTest(visibility=visibility): + self.assert_stable( + "function apply(" + f"callback: function(word){visibility} returns (bool)" + ") {}\n" + ) + def test_transparent_aliases_are_stable(self) -> None: cases = ( "alias Word = uint256;\n", From 7c9b8fd24c7d434f6bd6cb4da1ca09b9ede99543 Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Fri, 24 Jul 2026 19:13:50 +0900 Subject: [PATCH 17/33] Preserve explicit unit returns during migration --- scripts/migrate_new_syntax.py | 18 ------------------ scripts/test_migrate_new_syntax.py | 12 ++++++++++++ 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/scripts/migrate_new_syntax.py b/scripts/migrate_new_syntax.py index 55022bd5d..6b6d47ef6 100755 --- a/scripts/migrate_new_syntax.py +++ b/scripts/migrate_new_syntax.py @@ -1269,23 +1269,6 @@ def transform_let_comptime(source: str) -> str: return apply_edits(source, edits) -def transform_return_unit(source: str) -> str: - tokens = significant(source) - edits: list[Edit] = [] - for index, token in enumerate(tokens): - if ( - token.text == "return" - and index + 3 < len(tokens) - and tokens[index + 1].text == "(" - and tokens[index + 2].text == ")" - and tokens[index + 3].text == ";" - ): - edits.append( - Edit(token.end, tokens[index + 2].end, "") - ) - return apply_edits(source, edits) - - def transform_matches(source: str) -> str: tokens = significant(source) edits: list[Edit] = [] @@ -2247,7 +2230,6 @@ def migrate_source(source: str) -> str: transform_traits_and_impls, transform_functions, transform_let_comptime, - transform_return_unit, transform_matches, transform_if_expressions, parenthesize_control_conditions, diff --git a/scripts/test_migrate_new_syntax.py b/scripts/test_migrate_new_syntax.py index 4633253ff..44e8f6e39 100644 --- a/scripts/test_migrate_new_syntax.py +++ b/scripts/test_migrate_new_syntax.py @@ -75,6 +75,18 @@ def test_function_type_visibility_is_stable(self) -> None: ") {}\n" ) + def test_explicit_unit_returns_are_stable(self) -> None: + cases = ( + "function unitValue() returns (()) { return (); }\n", + ( + "function named() returns (result: word) " + "{ result = 1; return (); }\n" + ), + ) + for source in cases: + with self.subTest(source=source): + self.assert_stable(source) + def test_transparent_aliases_are_stable(self) -> None: cases = ( "alias Word = uint256;\n", From 0a8804d124bcb3362edb0a15a2b51333faa30a30 Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Fri, 24 Jul 2026 19:14:19 +0900 Subject: [PATCH 18/33] Protect Yul meta payloads during migration --- scripts/migrate_new_syntax.py | 15 ++++++++++++++- scripts/test_migrate_new_syntax.py | 9 +++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/scripts/migrate_new_syntax.py b/scripts/migrate_new_syntax.py index 6b6d47ef6..00dd1ee31 100755 --- a/scripts/migrate_new_syntax.py +++ b/scripts/migrate_new_syntax.py @@ -3,7 +3,8 @@ The migration is deliberately token-aware: -* comments and string literals are never searched or rewritten as source code; +* comments, string literals, and Yul meta expressions are never searched or + rewritten as source code; * parenthesized calls are changed to angle-bracket type applications only in a syntactic type position; * only git-tracked ``.solc`` files and the explicitly listed Core ``.sol`` @@ -176,6 +177,18 @@ def tokenize(source: str) -> list[Token]: tokens.append(Token("string", source[start:i], start, i)) continue + if ch == "`": + close = source.find("`", i + 1) + i = n if close < 0 else close + 1 + tokens.append(Token("meta", source[start:i], start, i)) + continue + + if source.startswith("${", i): + close = source.find("}", i + 2) + i = n if close < 0 else close + 1 + tokens.append(Token("meta", source[start:i], start, i)) + continue + if ch.isalpha() or ch == "_": i += 1 while i < n and (source[i].isalnum() or source[i] == "_"): diff --git a/scripts/test_migrate_new_syntax.py b/scripts/test_migrate_new_syntax.py index 44e8f6e39..afce462bc 100644 --- a/scripts/test_migrate_new_syntax.py +++ b/scripts/test_migrate_new_syntax.py @@ -87,6 +87,15 @@ def test_explicit_unit_returns_are_stable(self) -> None: with self.subTest(source=source): self.assert_stable(source) + def test_yul_meta_payloads_are_stable(self) -> None: + cases = ( + "function f() { assembly { let x := `foo;bar` } }\n", + "function f() { assembly { let x := ${foo;bar} } }\n", + ) + for source in cases: + with self.subTest(source=source): + self.assert_stable(source) + def test_transparent_aliases_are_stable(self) -> None: cases = ( "alias Word = uint256;\n", From 1afc6fd6620f13153c0d35d3ea55f7c77895a870 Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Fri, 24 Jul 2026 19:31:05 +0900 Subject: [PATCH 19/33] Reject loop control outside loop bodies --- src/Language/Hull/TypeCheck.hs | 56 ++++++++------- src/Solcore/Frontend/Syntax/NameResolution.hs | 68 +++++++++++++++++-- test/HullCases.hs | 7 +- test/ModuleTypeCheckTests.hs | 50 ++++++++++++++ .../hull/13-err-break-outside-loop.hull | 5 ++ .../hull/14-err-continue-outside-loop.hull | 5 ++ test/examples/hull/15-loop-control.hull | 11 +++ 7 files changed, 170 insertions(+), 32 deletions(-) create mode 100644 test/examples/hull/13-err-break-outside-loop.hull create mode 100644 test/examples/hull/14-err-continue-outside-loop.hull create mode 100644 test/examples/hull/15-loop-control.hull diff --git a/src/Language/Hull/TypeCheck.hs b/src/Language/Hull/TypeCheck.hs index 070fc2437..94f9a2823 100644 --- a/src/Language/Hull/TypeCheck.hs +++ b/src/Language/Hull/TypeCheck.hs @@ -31,7 +31,10 @@ checkObject (Object _ code inners) = do -- Type-check a sequence of statements sequentially. checkBody :: Body -> HullTcM () -checkBody = mapM_ checkStmt +checkBody = checkBodyAt 0 + +checkBodyAt :: Int -> Body -> HullTcM () +checkBodyAt loopDepth = mapM_ (checkStmtAt loopDepth) -- Register every SFunction signature in a body without checking their bodies. -- Recurses into SBlock so nested scopes are also pre-scanned. @@ -48,46 +51,49 @@ preScanStmt _ = pure () -- Statements checkStmt :: Stmt -> HullTcM () -checkStmt (SAlloc x t) = +checkStmt = checkStmtAt 0 + +checkStmtAt :: Int -> Stmt -> HullTcM () +checkStmtAt _ (SAlloc x t) = extendVar x t -checkStmt (SAssign lhs rhs) = do +checkStmtAt _ (SAssign lhs rhs) = do lhsTy <- checkExpr lhs rhsTy <- checkExpr rhs expectType lhsTy rhsTy -checkStmt (SReturn e) = do +checkStmtAt _ (SReturn e) = do te <- checkExpr e mret <- getRetType case mret of Nothing -> hullError "return statement outside of a function" Just tr -> expectType tr te -checkStmt (SFunction name args ret body) = do +checkStmtAt _ (SFunction name args ret body) = do let sig = HullFunSig {hsig_args = map argType args, hsig_ret = ret} extendFun name sig withLocalEnv $ do forM_ args $ \(TArg n t) -> extendVar n t - withRetType ret (checkBody body) -checkStmt (SMatch ty e alts) = do + withRetType ret (checkBodyAt 0 body) +checkStmtAt loopDepth (SMatch ty e alts) = do te <- checkExpr e expectType ty te - mapM_ (checkAlt (stripTypeName ty)) alts -checkStmt (SBlock stmts) = - withLocalEnv (checkBody stmts) -checkStmt (SExpr e) = + mapM_ (checkAltAt loopDepth (stripTypeName ty)) alts +checkStmtAt loopDepth (SBlock stmts) = + withLocalEnv (checkBodyAt loopDepth stmts) +checkStmtAt _ (SExpr e) = checkExpr e >> pure () -checkStmt (SAssembly stmts) = do +checkStmtAt _ (SAssembly stmts) = do case validateYulControlFlow stmts of Left err -> hullError err Right () -> pure () withLocalEnv (checkAsmBlock stmts) -checkStmt (SFor initStmt cond post body) = +checkStmtAt loopDepth (SFor initStmt cond post body) = -- Variables declared in the init block are scoped over the entire for loop -- (cond, post, body), matching Yul's for-loop scoping rules. withLocalEnv $ do - checkBody (blockStmts initStmt) + checkBodyAt loopDepth (blockStmts initStmt) te <- checkExpr cond expectBoolType te - checkStmt post - checkStmt body + checkStmtAt loopDepth post + checkStmtAt (loopDepth + 1) body where blockStmts (SBlock ss) = ss blockStmts s = [s] @@ -95,10 +101,14 @@ checkStmt (SFor initStmt cond post body) = TBool -> pure () TSum TUnit TUnit -> pure () _ -> hullError ("for condition must be bool or sum () (), got " ++ show t) -checkStmt SBreak = pure () -checkStmt SContinue = pure () -checkStmt (SRevert _) = pure () -checkStmt (SComment _) = pure () +checkStmtAt loopDepth SBreak + | loopDepth > 0 = pure () + | otherwise = hullError "break statement outside of a loop" +checkStmtAt loopDepth SContinue + | loopDepth > 0 = pure () + | otherwise = hullError "continue statement outside of a loop" +checkStmtAt _ (SRevert _) = pure () +checkStmtAt _ (SComment _) = pure () argType :: Arg -> Type argType (TArg _ t) = t @@ -177,12 +187,12 @@ checkExpr (ECond ty cond e1 e2) = do -- Type-check one alternative of a match expression. -- The scrutinee type (already stripped of TNamed) is passed in. -checkAlt :: Type -> Alt -> HullTcM () -checkAlt scrutTy (Alt pat bindName body) = do +checkAltAt :: Int -> Type -> Alt -> HullTcM () +checkAltAt loopDepth scrutTy (Alt pat bindName body) = do payTy <- payloadType scrutTy pat withLocalEnv $ do extendVar bindName payTy - checkBody body + checkBodyAt loopDepth body -- Compute the type of the payload variable bound in an alternative. payloadType :: Type -> Pat -> HullTcM Type diff --git a/src/Solcore/Frontend/Syntax/NameResolution.hs b/src/Solcore/Frontend/Syntax/NameResolution.hs index 8b5700cd7..24c9e50ee 100644 --- a/src/Solcore/Frontend/Syntax/NameResolution.hs +++ b/src/Solcore/Frontend/Syntax/NameResolution.hs @@ -665,13 +665,32 @@ instance Resolve S.Stmt where resolve s@(S.If e blk1 blk2) = locatedLike s locatedStmt <$> (If <$> resolve e <*> resolve blk1 <*> resolve blk2) resolve s@(S.While cond body) = - locatedLike s locatedStmt <$> (For EmptyStmt <$> resolve cond <*> pure EmptyStmt <*> resolve body) + locatedLike s locatedStmt + <$> ( For EmptyStmt + <$> resolve cond + <*> pure EmptyStmt + <*> withLoopContext (resolve body) + ) resolve s@(S.Unchecked body) = locatedLike s locatedStmt <$> withLocalCtx (Block <$> resolve body) resolve s@(S.For initStmt cond postStmt body) = - locatedLike s locatedStmt <$> (For <$> resolve initStmt <*> resolve cond <*> resolve postStmt <*> resolve body) - resolve s@S.Break = pure (locatedLike s locatedStmt Break) - resolve s@S.Continue = pure (locatedLike s locatedStmt Continue) + locatedLike s locatedStmt + <$> ( For + <$> resolve initStmt + <*> resolve cond + <*> resolve postStmt + <*> withLoopContext (resolve body) + ) + resolve s@S.Break = do + depth <- gets loopDepth + if depth > 0 + then pure (locatedLike s locatedStmt Break) + else loopControlOutsideLoopError s "break" + resolve s@S.Continue = do + depth <- gets loopDepth + if depth > 0 + then pure (locatedLike s locatedStmt Continue) + else loopControlOutsideLoopError s "continue" resolve s@S.Revert = pure ( locatedLike @@ -874,7 +893,7 @@ resolveExp (S.Lit l) = Lit <$> resolve l resolveExp e@(S.ExpDotName n es) = Con (dotConstructorMarker n) <$> resolve es `wrapError` e resolveExp e@(S.Lam ps bd mt) = - withLocalCtx $ do + withLocalCtx . withLoopDepth 0 $ do ps' <- resolve ps `wrapError` e mt' <- resolve mt `wrapError` e let args = map paramName ps' @@ -1401,7 +1420,10 @@ data Env canonicalTypeNames :: Map Name Name, -- named-return value used when lowering a source-level bare return inside -- the current function. Nested lambdas and constructors reset it. - functionBareReturnValue :: Maybe (Exp Name) + functionBareReturnValue :: Maybe (Exp Name), + -- lexical loop nesting used to reject break/continue outside loop bodies. + -- Lambdas reset this depth because they cannot control an enclosing loop. + loopDepth :: Int } deriving (Show) @@ -1441,7 +1463,8 @@ emptyEnv = (QualName (Name "Int") "fromInteger", TFunction) ], canonicalTypeNames = Map.empty, - functionBareReturnValue = Nothing + functionBareReturnValue = Nothing, + loopDepth = 0 } globalEnv :: [S.TopDecl] -> Env @@ -1586,6 +1609,19 @@ withBareReturnValue returnValue m = do modify (\env -> env {functionBareReturnValue = previous}) pure result +withLoopContext :: ResolveM a -> ResolveM a +withLoopContext m = do + depth <- gets loopDepth + withLoopDepth (depth + 1) m + +withLoopDepth :: Int -> ResolveM a -> ResolveM a +withLoopDepth depth m = do + previous <- gets loopDepth + modify (\env -> env {loopDepth = depth}) + result <- m + modify (\env -> env {loopDepth = previous}) + pure result + lookupType :: Name -> ResolveM (Maybe DeclType) lookupType n = gets (Map.lookup n . typeEnv) @@ -1877,6 +1913,24 @@ unsupportedFunctionTypeError functionTy message label help = [] help +loopControlOutsideLoopError :: S.Stmt -> String -> ResolveM a +loopControlOutsideLoopError statement keyword = + diagnosticErrorWithLabels + "SC0125" + (keyword ++ " statement outside of a loop") + ( case sourceSpanOf statement of + Nothing -> [] + Just sourceSpan -> + [ Label + { labelSpan = sourceSpan, + labelStyle = Primary, + labelMessage = Just (keyword ++ " is only valid inside a loop body") + } + ] + ) + [] + ["move this statement inside a while or for loop body"] + diagnosticError :: String -> String -> [String] -> [String] -> ResolveM a diagnosticError code message notes help = diagnosticErrorWithLabels code message [] notes help diff --git a/test/HullCases.hs b/test/HullCases.hs index 4f1216a71..fc013eefc 100644 --- a/test/HullCases.hs +++ b/test/HullCases.hs @@ -21,7 +21,8 @@ hullTests = runHullTest "02-pair.hull", runHullTest "03-sum.hull", runHullTest "04-cond.hull", - runHullTest "05-forward-ref.hull" + runHullTest "05-forward-ref.hull", + runHullTest "15-loop-control.hull" ], testGroup "Programs with type errors" @@ -31,7 +32,9 @@ hullTests = runHullTestExpectingFailure "09-err-sum-payload.hull", runHullTestExpectingFailure "10-err-fst-non-pair.hull", runHullTestExpectingFailure "11-err-asm-sum-return.hull", - runHullTestExpectingFailure "12-err-asm-break-outside-loop.hull" + runHullTestExpectingFailure "12-err-asm-break-outside-loop.hull", + runHullTestExpectingFailure "13-err-break-outside-loop.hull", + runHullTestExpectingFailure "14-err-continue-outside-loop.hull" ] ] diff --git a/test/ModuleTypeCheckTests.hs b/test/ModuleTypeCheckTests.hs index c9fd95fdc..83319ded2 100644 --- a/test/ModuleTypeCheckTests.hs +++ b/test/ModuleTypeCheckTests.hs @@ -90,6 +90,42 @@ moduleTypeCheckTests = stdOpt (moduleInput [ModuleInferenceDecl ModuleLocalDecl badImportedFun]) assertLeft "local body should be checked" result, + testCase "break and continue outside loops are rejected with locations" $ do + breakResult <- + typecheckSource + "function badBreak() returns (()) { break; }" + continueResult <- + typecheckSource + "function badContinue() returns (()) { continue; }" + assertLocatedLoopControlError "break outside loop" "break" breakResult + assertLocatedLoopControlError "continue outside loop" "continue" continueResult, + testCase "break and continue remain valid in loop bodies" $ do + checked <- + typecheckSource $ + unlines + [ "function validLoopControl(flag: bool) returns (()) {", + " while (flag) {", + " break;", + " }", + " for (; flag; ) {", + " continue;", + " }", + " return;", + "}" + ] + assertRight "loop-local control statements" checked, + testCase "a lambda cannot control its enclosing loop" $ do + checked <- + typecheckSource $ + unlines + [ "function badLambdaBreak(flag: bool) returns (()) {", + " while (flag) {", + " let callback = lam() returns (()) { break; };", + " }", + " return;", + "}" + ] + assertLocatedLoopControlError "lambda break boundary" "break" checked, testCase "numeric fixed-array size survives resolution and kind checking" $ do parsedResult <- parseCompUnit $ @@ -918,6 +954,20 @@ assertLocatedFunctionTypeError label expectedMessage (Left err) = do assertLocatedFunctionTypeError label _ (Right _) = assertFailure (label ++ ": expected failure") +assertLocatedLoopControlError :: + String -> + String -> + Either CompilerError a -> + Assertion +assertLocatedLoopControlError label keyword (Left err) = do + assertLeftContaining label "SC0125" (Left err) + assertLeftContaining label (keyword ++ " statement outside of a loop") (Left err) + assertBool + (label ++ ": expected a source-located diagnostic") + (any ((/= Nothing) . diagnosticPrimarySpan) (compilerErrorDiagnostics err)) +assertLocatedLoopControlError label _ (Right _) = + assertFailure (label ++ ": expected failure") + assertContractKindLifecycle :: String -> ContractKind -> Bool -> String -> Assertion assertContractKindLifecycle contractName expectedKind shouldGenerateRuntime source = do parsedResult <- parseCompUnit source diff --git a/test/examples/hull/13-err-break-outside-loop.hull b/test/examples/hull/13-err-break-outside-loop.hull new file mode 100644 index 000000000..a82fa3f96 --- /dev/null +++ b/test/examples/hull/13-err-break-outside-loop.hull @@ -0,0 +1,5 @@ +object InvalidBreak { + code { + break + } +} diff --git a/test/examples/hull/14-err-continue-outside-loop.hull b/test/examples/hull/14-err-continue-outside-loop.hull new file mode 100644 index 000000000..05ea17519 --- /dev/null +++ b/test/examples/hull/14-err-continue-outside-loop.hull @@ -0,0 +1,5 @@ +object InvalidContinue { + code { + continue + } +} diff --git a/test/examples/hull/15-loop-control.hull b/test/examples/hull/15-loop-control.hull new file mode 100644 index 000000000..2f49f0561 --- /dev/null +++ b/test/examples/hull/15-loop-control.hull @@ -0,0 +1,11 @@ +object ValidLoopControl { + code { + function run(flag : bool) -> unit { + for ({}; flag; {}) { + continue + break + } + return () + } + } +} From 25d696c902973f3956d51acc137216e1c719e256 Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Fri, 24 Jul 2026 19:38:58 +0900 Subject: [PATCH 20/33] Evaluate compound-assignment lvalues once --- src/Solcore/Desugarer/FieldAccess.hs | 58 ++++ src/Solcore/Frontend/Syntax/NameResolution.hs | 123 +++++++- test/Cases.hs | 1 + test/ParserTests.hs | 281 ++++++++++++++++++ .../compound-assignment-single-eval.solc | 15 + 5 files changed, 472 insertions(+), 6 deletions(-) create mode 100644 test/examples/cases/compound-assignment-single-eval.solc diff --git a/src/Solcore/Desugarer/FieldAccess.hs b/src/Solcore/Desugarer/FieldAccess.hs index d1dd64f65..4690d23e2 100644 --- a/src/Solcore/Desugarer/FieldAccess.hs +++ b/src/Solcore/Desugarer/FieldAccess.hs @@ -177,6 +177,10 @@ transEquation :: NmEquation -> CEM NmEquation transEquation (pats, body) cenv = (pats, transBody body cenv) transAssignment :: NmExp -> NmExp -> ContractEnv -> NmStmt +transAssignment lhs (Call Nothing operator [readLhs, rhs]) cenv + | lhs == readLhs, + isCompoundOperator operator = + transCompoundAssignment lhs operator rhs cenv transAssignment lhs@(Var x) rhs cenv | isLocal x cenv = traces @@ -211,6 +215,60 @@ transAssignment lhs rhs cenv = where rhs' = transRhs rhs cenv +transCompoundAssignment :: NmExp -> Name -> NmExp -> ContractEnv -> NmStmt +transCompoundAssignment lhs@(Var x) operator rhs cenv + | isLocal x cenv = + lhs := Call Nothing operator [lhs, transRhs rhs cenv] +transCompoundAssignment (FieldAccess Nothing x) operator rhs cenv + | isLocal x cenv = + Var x := Call Nothing operator [Var x, transRhs rhs cenv] + | Just _ <- askFieldTy x cenv = + compoundReferenceAssignment + (lhsAccess (memberProxyFor x cenv)) + operator + (transRhs rhs cenv) +transCompoundAssignment (Indexed array index) operator rhs cenv = + compoundReferenceAssignment + (lhsIndex array index cenv) + operator + (transRhs rhs cenv) +transCompoundAssignment lhs operator rhs cenv = + lhs := transRhs (Call Nothing operator [lhs, rhs]) cenv + +-- Compute an address-like lvalue once, then use that same reference for both +-- its read and write. Indexed storage access may perform hashing and bounds +-- checks, so merely freezing its receiver and index is not enough. +compoundReferenceAssignment :: NmExp -> Name -> NmExp -> NmStmt +compoundReferenceAssignment reference operator rhs = + Block + [ Let False referenceName Nothing (Just reference), + StmtExp $ + Call + Nothing + (QualName (Name "Assign") "assign") + [ Var referenceName, + Call + Nothing + operator + [ Call Nothing (QualName (Name "CanStore") "load") [Var referenceName], + rhs + ] + ] + ] + where + referenceName = Name "$compound_lvalue" + +isCompoundOperator :: Name -> Bool +isCompoundOperator operator = + operator + `elem` [ QualName (Name "Add") "add", + QualName (Name "Sub") "sub", + QualName (Name "BitXor") "bxor", + QualName (Name "BitAnd") "band", + QualName (Name "BitOr") "bor", + QualName (Name "Mod") "mod" + ] + transContractFieldAssignment :: Name -> NmExp -> CEM NmStmt transContractFieldAssignment field rhs = do {- Desugaring scheme: diff --git a/src/Solcore/Frontend/Syntax/NameResolution.hs b/src/Solcore/Frontend/Syntax/NameResolution.hs index 24c9e50ee..fee6b8109 100644 --- a/src/Solcore/Frontend/Syntax/NameResolution.hs +++ b/src/Solcore/Frontend/Syntax/NameResolution.hs @@ -622,17 +622,23 @@ instance Resolve S.Stmt where rhs' <- resolve rhs `wrapError` s pure (lhs' := rhs') resolve s@(S.StmtPlusEq lhs rhs) = - locatedLike s locatedStmt <$> ((:=) <$> resolve lhs <*> resolve (S.ExpPlus lhs rhs)) + locatedLike s locatedStmt + <$> resolveCompoundAssignment s (QualName (Name "Add") "add") lhs rhs resolve s@(S.StmtMinusEq lhs rhs) = - locatedLike s locatedStmt <$> ((:=) <$> resolve lhs <*> resolve (S.ExpMinus lhs rhs)) + locatedLike s locatedStmt + <$> resolveCompoundAssignment s (QualName (Name "Sub") "sub") lhs rhs resolve s@(S.StmtBXorEq lhs rhs) = - locatedLike s locatedStmt <$> ((:=) <$> resolve lhs <*> resolve (S.ExpBXor lhs rhs)) + locatedLike s locatedStmt + <$> resolveCompoundAssignment s (QualName (Name "BitXor") "bxor") lhs rhs resolve s@(S.StmtBAndEq lhs rhs) = - locatedLike s locatedStmt <$> ((:=) <$> resolve lhs <*> resolve (S.ExpBAnd lhs rhs)) + locatedLike s locatedStmt + <$> resolveCompoundAssignment s (QualName (Name "BitAnd") "band") lhs rhs resolve s@(S.StmtBOrEq lhs rhs) = - locatedLike s locatedStmt <$> ((:=) <$> resolve lhs <*> resolve (S.ExpBOr lhs rhs)) + locatedLike s locatedStmt + <$> resolveCompoundAssignment s (QualName (Name "BitOr") "bor") lhs rhs resolve s@(S.StmtModEq lhs rhs) = - locatedLike s locatedStmt <$> ((:=) <$> resolve lhs <*> resolve (S.ExpModulo lhs rhs)) + locatedLike s locatedStmt + <$> resolveCompoundAssignment s (QualName (Name "Mod") "mod") lhs rhs resolve s@(S.Let c n mt me) = locatedLike s locatedStmt <$> do mt' <- resolve mt `wrapError` s @@ -700,6 +706,111 @@ instance Resolve S.Stmt where ) resolve s@S.EmptyStmt = pure (locatedLike s locatedStmt EmptyStmt) +-- Compound assignment must evaluate the address-producing parts of its +-- left-hand side exactly once. Expanding @a[i()] += rhs@ directly to +-- @a[i()] = Add.add(a[i()], rhs)@ duplicates @i()@, and likewise duplicates a +-- call used as an indexed base or member receiver. Bind those computations in +-- a lexical block before constructing the ordinary assignment consumed by the +-- rest of the pipeline. +-- +-- The generated names contain '$', which source identifiers cannot contain, +-- and every compound assignment gets its own lexical block. They therefore +-- cannot capture, or be captured by, user bindings. +resolveCompoundAssignment :: + S.Stmt -> + Name -> + S.Exp -> + S.Exp -> + ResolveM (Stmt Name) +resolveCompoundAssignment source operator lhs rhs = do + lhs' <- resolve lhs `wrapError` source + rhs' <- resolve rhs `wrapError` source + let (bindings, frozenLhs) = freezeCompoundLhs source lhs' + combined = + locatedLike + source + locatedExp + (Call Nothing operator [frozenLhs, rhs']) + assignment = locatedLike source locatedStmt (frozenLhs := combined) + pure $ + case bindings of + [] -> frozenLhs := combined + _ -> Block (bindings ++ [assignment]) + +freezeCompoundLhs :: S.Stmt -> Exp Name -> ([Stmt Name], Exp Name) +freezeCompoundLhs source lhs = + let (bindings, frozen, _) = freezeAddress source 0 lhs + in (bindings, frozen) + +freezeAddress :: + S.Stmt -> + Int -> + Exp Name -> + ([Stmt Name], Exp Name, Int) +freezeAddress source next (IndexedWithLocation location array index) = + let (arrayBindings, frozenArray, afterArray) = + freezeIndexedBase source next array + (indexBinding, frozenIndex, afterIndex) = + bindCompoundTemporary source "index" afterArray index + in ( arrayBindings ++ [indexBinding], + IndexedWithLocation location frozenArray frozenIndex, + afterIndex + ) +freezeAddress source next (FieldAccessWithLocation location (Just receiver) memberName) + | stableAddressPart receiver = + ([], FieldAccessWithLocation location (Just receiver) memberName, next) + | otherwise = + let (receiverBinding, frozenReceiver, afterReceiver) = + bindCompoundTemporary source "receiver" next receiver + in ( [receiverBinding], + FieldAccessWithLocation location (Just frozenReceiver) memberName, + afterReceiver + ) +freezeAddress _ next lhs = + ([], lhs, next) + +freezeIndexedBase :: + S.Stmt -> + Int -> + Exp Name -> + ([Stmt Name], Exp Name, Int) +freezeIndexedBase source next base@Indexed {} = + freezeAddress source next base +freezeIndexedBase source next base@(FieldAccess (Just _) _) = + freezeAddress source next base +freezeIndexedBase _ next base + | stableAddressPart base = + ([], base, next) +freezeIndexedBase source next base = + let (binding, frozen, afterBase) = + bindCompoundTemporary source "base" next base + in ([binding], frozen, afterBase) + +stableAddressPart :: Exp Name -> Bool +stableAddressPart Var {} = True +stableAddressPart (FieldAccess Nothing _) = True +stableAddressPart _ = False + +bindCompoundTemporary :: + S.Stmt -> + String -> + Int -> + Exp Name -> + (Stmt Name, Exp Name, Int) +bindCompoundTemporary source role next value = + let temporaryName = Name ("$compound_" ++ role ++ show next) + binding = + locatedLike + source + locatedStmt + (Let False temporaryName Nothing (Just value)) + temporary = + locatedLike + source + locatedExp + (Var temporaryName) + in (binding, temporary, next + 1) + instance Resolve S.Equation where type Result S.Equation = Equation Name diff --git a/test/Cases.hs b/test/Cases.hs index 863dcc3c0..aed49358f 100644 --- a/test/Cases.hs +++ b/test/Cases.hs @@ -318,6 +318,7 @@ cases = -- disabling the desugaring phases via noDesugarOpt; now that that helper is -- gone it is expected to fail. runTestExpectingFailure "compose_desugared.solc" caseFolder, + runTestForFile "compound-assignment-single-eval.solc" caseFolder, runTestForFile "comparisons.solc" caseFolder, runTestForFile "bitwise.solc" caseFolder, runTestForFile "match-bitwise.solc" caseFolder, diff --git a/test/ParserTests.hs b/test/ParserTests.hs index 0ef17fc4c..fead80d3f 100644 --- a/test/ParserTests.hs +++ b/test/ParserTests.hs @@ -6,6 +6,7 @@ import Common.LightYear (Parser, runParserE) import Data.List (isInfixOf) import Data.List.NonEmpty (NonEmpty ((:|))) import Language.Yul (YLiteral (..), YulExp (..), YulStmt (..)) +import Solcore.Desugarer.FieldAccess (fieldDesugarTopDecls) import Solcore.Diagnostics (compilerErrorText) import Solcore.Frontend.Lexer.SolcoreLexer (identifier, sc) import Solcore.Frontend.Parser.Decl (importP, topDeclP) @@ -149,6 +150,7 @@ parserTests = patternTests, exprTests, stmtTests, + compoundAssignmentResolutionTests, declTests, importTests, pragmaTests, @@ -157,6 +159,285 @@ parserTests = legacySyntaxTests ] +compoundAssignmentResolutionTests :: TestTree +compoundAssignmentResolutionTests = + testGroup + "Compound assignment resolution" + [ testCase "simple variable keeps direct assignment lowering" $ do + body <- + resolvedFunctionBody + "update" + "function update(value: word) { value += 1; }" + case body of + [ ResolvedStmt.AssignWithLocation + _ + lhs@(ResolvedStmt.Var "value") + (ResolvedStmt.Call Nothing (QualName "Add" "add") [readLhs, ResolvedStmt.Lit _]) + ] -> + assertEqual "compound read uses the assigned variable" lhs readLhs + other -> + assertFailure ("Unexpected simple compound assignment lowering: " ++ show other), + testCase "index expression and indexed base are each evaluated once" $ do + body <- + resolvedFunctionBody + "update" + ( unlines + [ "function collection() returns (word) { return 0; }", + "function index() returns (word) { return 0; }", + "function update() { collection()[index()] += 1; }" + ] + ) + case body of + [ ResolvedStmt.Block + [ ResolvedStmt.Let False baseName Nothing (Just baseValue), + ResolvedStmt.Let False indexName Nothing (Just indexValue), + ResolvedStmt.AssignWithLocation + _ + assignedLhs + (ResolvedStmt.Call Nothing (QualName "Add" "add") [readLhs, ResolvedStmt.Lit _]) + ] + ] -> do + assertEqual + "base is evaluated once before the index" + (ResolvedStmt.Call Nothing "collection" []) + baseValue + assertEqual + "index is evaluated once" + (ResolvedStmt.Call Nothing "index" []) + indexValue + let frozenLhs = + ResolvedStmt.Indexed + (ResolvedStmt.Var baseName) + (ResolvedStmt.Var indexName) + assertEqual "write uses frozen address components" frozenLhs assignedLhs + assertEqual "read uses the same frozen address components" frozenLhs readLhs + other -> + assertFailure ("Unexpected indexed compound assignment lowering: " ++ show other), + testCase "member receiver is evaluated once" $ do + body <- + resolvedFunctionBody + "update" + ( unlines + [ "function receiver() returns (word) { return 0; }", + "function update() { receiver().member += 1; }" + ] + ) + case body of + [ ResolvedStmt.Block + [ ResolvedStmt.Let False receiverName Nothing (Just receiverValue), + ResolvedStmt.AssignWithLocation + _ + assignedLhs + (ResolvedStmt.Call Nothing (QualName "Add" "add") [readLhs, ResolvedStmt.Lit _]) + ] + ] -> do + assertEqual + "receiver is evaluated once" + (ResolvedStmt.Call Nothing "receiver" []) + receiverValue + let frozenLhs = + ResolvedStmt.FieldAccess + (Just (ResolvedStmt.Var receiverName)) + "member" + assertEqual "write uses frozen receiver" frozenLhs assignedLhs + assertEqual "read uses the same frozen receiver" frozenLhs readLhs + other -> + assertFailure ("Unexpected member compound assignment lowering: " ++ show other), + testCase "field desugaring computes the indexed lvalue reference once" $ do + body <- + fieldDesugaredContractFunctionBody + "Container" + "update" + ( unlines + [ "contract Container {", + " function index() returns (word) { return 0; }", + " function update(collection: word) { collection[index()] += 1; }", + "}" + ] + ) + case body of + [ ResolvedStmt.Block + [ ResolvedStmt.Let False _ Nothing (Just indexValue), + ResolvedStmt.Block + [ ResolvedStmt.Let False referenceName Nothing (Just referenceValue), + ResolvedStmt.StmtExp + ( ResolvedStmt.Call + Nothing + (QualName "Assign" "assign") + [ writeReference, + ResolvedStmt.Call + Nothing + (QualName "Add" "add") + [readReference, ResolvedStmt.Lit _] + ] + ) + ] + ] + ] -> do + assertEqual + "index side effect is evaluated once" + (ResolvedStmt.Call Nothing "index" []) + indexValue + case referenceValue of + ResolvedStmt.Call + Nothing + "lidx" + [ResolvedStmt.Var "collection", ResolvedStmt.Var _] -> + pure () + other -> + assertFailure ("Expected one lidx address computation, got: " ++ show other) + assertEqual + "write uses the computed lvalue reference" + (ResolvedStmt.Var referenceName) + writeReference + assertEqual + "read loads through the same lvalue reference" + ( ResolvedStmt.Call + Nothing + (QualName "CanStore" "load") + [ResolvedStmt.Var referenceName] + ) + readReference + other -> + assertFailure ("Unexpected field-desugared compound assignment: " ++ show other), + testCase "field desugaring computes a contract-field reference once" $ do + body <- + fieldDesugaredContractFunctionBody + "Container" + "update" + ( unlines + [ "contract Container {", + " value: word;", + " function update() { value += 1; }", + "}" + ] + ) + case body of + [ ResolvedStmt.Block + [ ResolvedStmt.Let False referenceName Nothing (Just referenceValue), + ResolvedStmt.StmtExp + ( ResolvedStmt.Call + Nothing + (QualName "Assign" "assign") + [ writeReference, + ResolvedStmt.Call + Nothing + (QualName "Add" "add") + [readReference, ResolvedStmt.Lit _] + ] + ) + ] + ] -> do + case referenceValue of + ResolvedStmt.Call Nothing (QualName "LVA" "acc") [_] -> + pure () + other -> + assertFailure ("Expected one LVA.acc address computation, got: " ++ show other) + assertEqual + "contract-field write uses the computed reference" + (ResolvedStmt.Var referenceName) + writeReference + assertEqual + "contract-field read loads through the same reference" + ( ResolvedStmt.Call + Nothing + (QualName "CanStore" "load") + [ResolvedStmt.Var referenceName] + ) + readReference + other -> + assertFailure ("Unexpected contract-field compound assignment: " ++ show other), + testCase "all compound operators retain their semantic operation" $ do + body <- + resolvedFunctionBody + "update" + ( unlines + [ "function update(value: word) {", + " value += 1;", + " value -= 1;", + " value ^= 1;", + " value &= 1;", + " value |= 1;", + " value %= 1;", + "}" + ] + ) + assertEqual + "resolved compound operator targets" + [ QualName "Add" "add", + QualName "Sub" "sub", + QualName "BitXor" "bxor", + QualName "BitAnd" "band", + QualName "BitOr" "bor", + QualName "Mod" "mod" + ] + (map compoundOperator body) + ] + +resolvedFunctionBody :: Name -> String -> IO [ResolvedStmt.Stmt Name] +resolvedFunctionBody functionName source = do + parsedResult <- parseCompUnit source + parsed <- + case parsedResult of + Left err -> assertFailure ("Parse error:\n" ++ err) + Right unit -> pure unit + resolved <- nameResolution parsed + case resolved of + Left err -> assertFailure ("Name resolution failed: " ++ show err) + Right (Resolved.CompUnit _ topDecls) -> + case [ body + | Resolved.TFunDef (Resolved.FunDef _ signature body) <- topDecls, + Resolved.sigName signature == functionName + ] of + [body] -> pure body + bodies -> + assertFailure + ( "Expected one resolved function body for " + ++ show functionName + ++ ", got " + ++ show (length bodies) + ) + +compoundOperator :: ResolvedStmt.Stmt Name -> Name +compoundOperator + ( ResolvedStmt.AssignWithLocation + _ + _ + (ResolvedStmt.Call Nothing operator [_, _]) + ) = operator +compoundOperator stmt = + error ("Unexpected simple compound assignment lowering: " ++ show stmt) + +fieldDesugaredContractFunctionBody :: + Name -> + Name -> + String -> + IO [ResolvedStmt.Stmt Name] +fieldDesugaredContractFunctionBody contractName functionName source = do + parsedResult <- parseCompUnit source + parsed <- + case parsedResult of + Left err -> assertFailure ("Parse error:\n" ++ err) + Right unit -> pure unit + resolved <- nameResolution parsed + case resolved of + Left err -> assertFailure ("Name resolution failed: " ++ show err) + Right (Resolved.CompUnit _ topDecls) -> + case [ body + | Resolved.TContr contract <- fieldDesugarTopDecls topDecls, + Resolved.name contract == contractName, + Resolved.CFunDecl (Resolved.FunDef _ signature body) <- Resolved.decls contract, + Resolved.sigName signature == functionName + ] of + [body] -> pure body + bodies -> + assertFailure + ( "Expected one field-desugared function body for " + ++ show functionName + ++ ", got " + ++ show (length bodies) + ) + word :: Ty word = TyCon "word" [] diff --git a/test/examples/cases/compound-assignment-single-eval.solc b/test/examples/cases/compound-assignment-single-eval.solc new file mode 100644 index 000000000..dbab34c81 --- /dev/null +++ b/test/examples/cases/compound-assignment-single-eval.solc @@ -0,0 +1,15 @@ +import {*} from std; + +contract CompoundAssignmentSingleEval { + values: mapping(word => word); + indexCalls: word; + + function index() returns (word) { + indexCalls += 1; + return 0; + } + + function bump() { + values[index()] += 1; + } +} From fae7aab5a270492493cc10955089c10a0a36477d Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Fri, 24 Jul 2026 19:42:29 +0900 Subject: [PATCH 21/33] Reject qualified names in trait declarations --- src/Solcore/Frontend/Parser/Decl.hs | 5 ++++- test/ParserTests.hs | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Solcore/Frontend/Parser/Decl.hs b/src/Solcore/Frontend/Parser/Decl.hs index f4c5c7661..eb962e77c 100644 --- a/src/Solcore/Frontend/Parser/Decl.hs +++ b/src/Solcore/Frontend/Parser/Decl.hs @@ -359,7 +359,10 @@ traitSignatureP = do traitP :: Parser Class traitP = do keyword "trait" - traitName <- qualifiedName + -- Qualified names refer to traits imported from another module. A + -- declaration introduces a name in the current module and must therefore + -- use a source identifier, like functions, contracts, and data types do. + traitName <- simpleNameP vars <- typeParamsP (primaryVar, params) <- case vars of [] -> fail "a trait must declare at least one type parameter" diff --git a/test/ParserTests.hs b/test/ParserTests.hs index fead80d3f..e1a0e46e6 100644 --- a/test/ParserTests.hs +++ b/test/ParserTests.hs @@ -1469,6 +1469,10 @@ declTests = ] ) ), + testCase "trait declarations cannot introduce qualified names" $ + parseFails + topDeclP + "trait Imported.Eq { function eq(x:a, y:a) returns (bool); }", testCase "trait with where clause" $ parsesAs topDeclP From 96fd8bfdc0ec06c51c751758d608f4b4de82f71f Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Fri, 24 Jul 2026 22:07:28 +0900 Subject: [PATCH 22/33] Implement explicit Typedef conversions --- src/Solcore/Frontend/TypeInference/TcEnv.hs | 2 + src/Solcore/Frontend/TypeInference/TcMonad.hs | 19 ++ src/Solcore/Frontend/TypeInference/TcStmt.hs | 164 +++++++++++++++++- test/Cases.hs | 81 +++++++++ .../cases/as-conversion-ambiguous-fail.solc | 36 ++++ test/examples/cases/as-conversion-fail.solc | 5 + .../as-conversion-identity-instance-fail.solc | 22 +++ test/examples/cases/as-conversion.solc | 32 ++++ 8 files changed, 353 insertions(+), 8 deletions(-) create mode 100644 test/examples/cases/as-conversion-ambiguous-fail.solc create mode 100644 test/examples/cases/as-conversion-fail.solc create mode 100644 test/examples/cases/as-conversion-identity-instance-fail.solc create mode 100644 test/examples/cases/as-conversion.solc diff --git a/src/Solcore/Frontend/TypeInference/TcEnv.hs b/src/Solcore/Frontend/TypeInference/TcEnv.hs index 698f5cc7e..d35e28e15 100644 --- a/src/Solcore/Frontend/TypeInference/TcEnv.hs +++ b/src/Solcore/Frontend/TypeInference/TcEnv.hs @@ -112,6 +112,7 @@ data TcEnv synTable :: SynTable, -- Type synonym environment classTable :: ClassTable, -- Class information table contract :: Maybe Name, -- current contract name + givenPredicates :: [Pred], -- constraints assumed while checking the current function body -- used to type check calls. subst :: Subst, -- Current substitution nameSupply :: NameSupply, -- Fresh name supply @@ -146,6 +147,7 @@ initTcEnv opts = synTable = Map.empty, classTable = primClassEnv, contract = Nothing, + givenPredicates = [], subst = mempty, nameSupply = namePool, uniqueTypes = primDataType, diff --git a/src/Solcore/Frontend/TypeInference/TcMonad.hs b/src/Solcore/Frontend/TypeInference/TcMonad.hs index 6c452de52..3c6d2234a 100644 --- a/src/Solcore/Frontend/TypeInference/TcMonad.hs +++ b/src/Solcore/Frontend/TypeInference/TcMonad.hs @@ -565,6 +565,25 @@ withLocalCtx envPairs m = a <- m pure a +withGivenPredicates :: [Pred] -> TcM a -> TcM a +withGivenPredicates predicates action = do + savedPredicates <- gets givenPredicates + modify + ( \env -> + env + { givenPredicates = + predicates `union` savedPredicates + } + ) + outcome <- + (Right <$> action) + `catchError` (pure . Left) + modify (\env -> env {givenPredicates = savedPredicates}) + either throwError pure outcome + +getGivenPredicates :: TcM [Pred] +getGivenPredicates = gets givenPredicates + -- Updating the environment putEnv :: Env -> TcM () diff --git a/src/Solcore/Frontend/TypeInference/TcStmt.hs b/src/Solcore/Frontend/TypeInference/TcStmt.hs index 69b381c75..4b180d424 100644 --- a/src/Solcore/Frontend/TypeInference/TcStmt.hs +++ b/src/Solcore/Frontend/TypeInference/TcStmt.hs @@ -483,13 +483,103 @@ tcExpWithExpected' mExpected (Lam args bd _) = else do (exp1, t) <- closureConversion vs (apply s args') (apply s bd') ps1 ty withCurrentSubst (exp1, ps1, t) -tcExpWithExpected' _ e1@(TyExp e ty) = - do - ty1 <- kindCheck ty `wrapError` e1 - (e', ps, ty') <- tcExpWithExpected (Just ty1) e - s <- tcmMatch ty' ty1 - _ <- extSubst s - withCurrentSubst (TyExp e' ty1, ps, ty1) +tcExpWithExpected' _ conversion@(TyExp expression targetTy) = do + checkedTargetTy <- kindCheck targetTy `wrapError` conversion + (typedExpression, expressionPreds, inferredSourceTy) <- tcExp expression + sourceTy <- maybeExpandSynonym =<< withCurrentSubst inferredSourceTy + targetTy' <- maybeExpandSynonym =<< withCurrentSubst checkedTargetTy + if sourceTy == targetTy' + then + elaboratedIdentity + mempty + typedExpression + expressionPreds + checkedTargetTy + else do + let typedefClass = Name "Typedef" + abstractConversion = InCls typedefClass targetTy' [sourceTy] + representationConversion = InCls typedefClass sourceTy [targetTy'] + givens <- withCurrentSubst =<< getGivenPredicates + classEnv <- getClassEnv + instanceEnv <- getInstEnv + canAbstract <- + conversionEntailed + typedefClass + classEnv + instanceEnv + givens + abstractConversion + canExposeRepresentation <- + conversionEntailed + typedefClass + classEnv + instanceEnv + givens + representationConversion + case (canAbstract, canExposeRepresentation) of + (True, False) -> + elaboratedConversion + (QualName typedefClass "abs") + abstractConversion + sourceTy + targetTy' + typedExpression + expressionPreds + (False, True) -> + elaboratedConversion + (QualName typedefClass "rep") + representationConversion + sourceTy + targetTy' + typedExpression + expressionPreds + (True, True) -> + tcDiagnosticErrorAtSource + "SC0230" + ( "ambiguous explicit conversion from " + ++ pretty sourceTy + ++ " to " + ++ pretty targetTy' + ) + conversion + "ambiguous conversion" + [ "both " + ++ pretty targetTy' + ++ ": Typedef<" + ++ pretty sourceTy + ++ "> and " + ++ pretty sourceTy + ++ ": Typedef<" + ++ pretty targetTy' + ++ "> are available" + ] + [ "call Typedef.abs or Typedef.rep explicitly to select the intended direction" + ] + (False, False) -> do + identityMatch <- + (Just <$> tcmMatch sourceTy targetTy') + `catchError` const (pure Nothing) + case identityMatch of + Just matchingSubst -> + elaboratedIdentity + matchingSubst + typedExpression + expressionPreds + checkedTargetTy + Nothing -> + tcDiagnosticErrorAtSource + "SC0230" + ( "no explicit conversion from " + ++ pretty sourceTy + ++ " to " + ++ pretty targetTy' + ) + conversion + "invalid conversion" + [ "`as` accepts identical types or a conversion witnessed by Typedef" + ] + [ "provide a matching Typedef instance or call a conversion function explicitly" + ] tcExpWithExpected' mExpected e@(Cond e1 e2 e3) = do (e1', ps1, t1) <- tcExpWithExpected Nothing e1 `wrapError` e @@ -536,6 +626,59 @@ tcExpWithExpected' _ e@(Indexed arrExp idx) = s <- unify tArr (tIdx :-> tRes) `wrapError` e withCurrentSubst (Indexed arr' idx', psArr ++ psIdx, apply s tRes) +elaboratedIdentity :: + Subst -> + Exp Id -> + [Pred] -> + Ty -> + TcM (Exp Id, [Pred], Ty) +elaboratedIdentity matchingSubst expression preds targetTy = do + _ <- extSubst matchingSubst + withCurrentSubst + (TyExp expression targetTy, preds, targetTy) + +conversionEntailed :: + Name -> + ClassTable -> + InstTable -> + [Pred] -> + Pred -> + TcM Bool +conversionEntailed predicateClassName classEnv instanceEnv givens wanted = + entailM classEnv consistentInstanceEnv givens wanted + where + consistentInstanceEnv = + Map.adjust + (filter (`instanceHeadConsistentWith` wanted)) + predicateClassName + instanceEnv + +instanceHeadConsistentWith :: Inst -> Pred -> Bool +instanceHeadConsistentWith instanceRule@(_ :=> instanceHead) wanted = + case byInst instanceRule wanted of + Nothing -> + False + Just (_, matchingSubst, _) -> + apply matchingSubst instanceHead == apply matchingSubst wanted + +elaboratedConversion :: + Name -> + Pred -> + Ty -> + Ty -> + Exp Id -> + [Pred] -> + TcM (Exp Id, [Pred], Ty) +elaboratedConversion methodName conversionPred sourceTy targetTy expression preds = + withCurrentSubst + ( Call + Nothing + (Id methodName (sourceTy :-> targetTy)) + [expression], + preds `union` [conversionPred], + targetTy + ) + closureConversion :: [Tyvar] -> [Param Id] -> @@ -792,7 +935,12 @@ tcFunDef incl vs' qs d@(FunDef isPub sig@(Signature vs ps n _ _ _ _) _) -- building the typing context with new assumptions let lctx' = if incl then (n, monotype nt) : lctx else lctx -- typing function body - (bd1', ps1', t1') <- withLocalCtx lctx' (tcBodyWithExpectedReturn (Just rt1') bd1) `wrapError` d + (bd1', ps1', t1') <- + ( withLocalCtx lctx' $ + withGivenPredicates (qs1 `union` ps1) $ + tcBodyWithExpectedReturn (Just rt1') bd1 + ) + `wrapError` d -- checking if the type checking have changed the type -- due to unique type creation. let tynames = tyconNames t1' diff --git a/test/Cases.hs b/test/Cases.hs index aed49358f..22ddd9f73 100644 --- a/test/Cases.hs +++ b/test/Cases.hs @@ -1,6 +1,8 @@ module Cases where import Control.Exception (try) +import Data.List (isInfixOf) +import Language.Hull qualified as Hull import Solcore.Pipeline.Options import Solcore.Pipeline.SolcorePipeline import System.Exit (ExitCode (..)) @@ -500,6 +502,16 @@ cases = runTestForFile "tuple-trick.solc" caseFolder, runTestForFile "tuva.solc" caseFolder, runTestForFile "tyexp.solc" caseFolder, + runAsConversionTest, + runAsConversionFailureTest + "as-conversion-fail.solc" + "no explicit conversion from bool to uint256", + runAsConversionFailureTest + "as-conversion-ambiguous-fail.solc" + "ambiguous explicit conversion from Left to Right", + runAsConversionFailureTest + "as-conversion-identity-instance-fail.solc" + "no explicit conversion from word to Wrapped", runTestForFile "typedef.solc" caseFolder, runTestForFile "Uncurry.solc" caseFolder, runTestExpectingFailure "unconstrained-instance.solc" caseFolder, @@ -599,6 +611,75 @@ cases = caseFolder = "./test/examples/cases" dispatchOpt = emptyOption mempty +runAsConversionTest :: TestTree +runAsConversionTest = + testCase "as-conversion.solc" $ do + let folder = "./test/examples/cases" + filePath = folder "as-conversion.solc" + opts = + stdOpt + { fileName = filePath, + optRootDir = folder, + optNoGenDispatch = True + } + result <- compile opts + case result of + Left err -> assertFailure err + Right [object] -> do + assertReturnsCall object (== "wrap") "concrete abstraction conversion" + assertReturnsCall object (== "unwrap") "concrete representation conversion" + assertReturnsCall object ("genericWrap" `isInfixOf`) "generic abstraction conversion" + assertReturnsCall object ("genericUnwrap" `isInfixOf`) "generic representation conversion" + assertReturnsVariable object (== "identity") "same-type identity conversion" + Right objects -> + assertFailure ("expected one Hull object, got " ++ show (length objects)) + where + assertReturnsCall object matches label = + assertBool + (label ++ " was erased instead of calling its Typedef method") + (any (functionReturnsCall matches) (Hull.objCode object)) + + assertReturnsVariable object matches label = + assertBool + (label ++ " unexpectedly introduced a conversion call") + (any (functionReturnsVariable matches) (Hull.objCode object)) + + functionReturnsCall matches (Hull.SFunction name _ _ body) = + matches name && any isCallReturn body + functionReturnsCall _ _ = False + + functionReturnsVariable matches (Hull.SFunction name _ _ body) = + matches name && any isVariableReturn body + functionReturnsVariable _ _ = False + + isCallReturn (Hull.SReturn (Hull.ECall _ [_])) = True + isCallReturn _ = False + + isVariableReturn (Hull.SReturn (Hull.EVar _)) = True + isVariableReturn _ = False + +runAsConversionFailureTest :: FilePath -> String -> TestTree +runAsConversionFailureTest file expectedMessage = + testCase file $ do + let folder = "./test/examples/cases" + filePath = folder file + opts = + stdOpt + { fileName = filePath, + optRootDir = folder, + optNoGenDispatch = True + } + result <- compile opts + case result of + Left err -> + assertBool + ("expected explicit-conversion diagnostic SC0230, got:\n" ++ err) + ( "error[SC0230]" `isInfixOf` err + && expectedMessage `isInfixOf` err + ) + Right _ -> + assertFailure (file ++ " unexpectedly compiled") + tabledResolution :: TestTree tabledResolution = testGroup diff --git a/test/examples/cases/as-conversion-ambiguous-fail.solc b/test/examples/cases/as-conversion-ambiguous-fail.solc new file mode 100644 index 000000000..4e0da2b29 --- /dev/null +++ b/test/examples/cases/as-conversion-ambiguous-fail.solc @@ -0,0 +1,36 @@ +trait Typedef { + function abs(value: representation) returns (abstract); + function rep(value: abstract) returns (representation); +} + +enum Left { + Left(word) +} + +enum Right { + Right(word) +} + +impl Typedef { + function abs(value: Right) returns (Left) { + return Left(0); + } + + function rep(value: Left) returns (Right) { + return Right(0); + } +} + +impl Typedef { + function abs(value: Left) returns (Right) { + return Right(0); + } + + function rep(value: Right) returns (Left) { + return Left(0); + } +} + +function invalid(value: Left) returns (Right) { + return value as Right; +} diff --git a/test/examples/cases/as-conversion-fail.solc b/test/examples/cases/as-conversion-fail.solc new file mode 100644 index 000000000..3bd40abaa --- /dev/null +++ b/test/examples/cases/as-conversion-fail.solc @@ -0,0 +1,5 @@ +import {uint256} from std; + +function invalid(value: bool) returns (uint256) { + return value as uint256; +} diff --git a/test/examples/cases/as-conversion-identity-instance-fail.solc b/test/examples/cases/as-conversion-identity-instance-fail.solc new file mode 100644 index 000000000..1fa573a65 --- /dev/null +++ b/test/examples/cases/as-conversion-identity-instance-fail.solc @@ -0,0 +1,22 @@ +trait Typedef { + function abs(value: representation) returns (abstract); + function rep(value: abstract) returns (representation); +} + +impl Typedef { + function abs(value: t) returns (t) { + return value; + } + + function rep(value: t) returns (t) { + return value; + } +} + +enum Wrapped { + Wrapped(word) +} + +function invalid(value: word) returns (Wrapped) { + return value as Wrapped; +} diff --git a/test/examples/cases/as-conversion.solc b/test/examples/cases/as-conversion.solc new file mode 100644 index 000000000..73396e9ee --- /dev/null +++ b/test/examples/cases/as-conversion.solc @@ -0,0 +1,32 @@ +import {Typedef, uint256} from std; + +function wrap(raw: word) returns (uint256) { + return raw as uint256; +} + +function unwrap(value: uint256) returns (word) { + return value as word; +} + +function identity(value: word) returns (word) { + return value as word; +} + +function genericWrap(raw: rep) returns (a) where a: Typedef { + return raw as a; +} + +function genericUnwrap(value: a) returns (rep) where a: Typedef { + return value as rep; +} + +function genericRoundtrip(raw: word) returns (word) { + let wrapped: uint256 = genericWrap(raw); + return genericUnwrap(wrapped); +} + +contract AsConversion { + function main(raw: word) public returns (word) { + return genericRoundtrip(identity(unwrap(wrap(raw)))); + } +} From 05ab3ce434143549a1ab8e8085a53618cf6acdb7 Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Sat, 25 Jul 2026 00:53:30 +0900 Subject: [PATCH 23/33] Preserve complete qualified name paths --- src/Solcore/Frontend/Syntax/NameResolution.hs | 35 +++++++++++++++++++ test/ModuleTypeCheckTests.hs | 19 ++++++++++ .../imports/qualified_nested_type_shadow.solc | 14 ++++++++ 3 files changed, 68 insertions(+) create mode 100644 test/imports/qualified_nested_type_shadow.solc diff --git a/src/Solcore/Frontend/Syntax/NameResolution.hs b/src/Solcore/Frontend/Syntax/NameResolution.hs index fee6b8109..ad2b0847c 100644 --- a/src/Solcore/Frontend/Syntax/NameResolution.hs +++ b/src/Solcore/Frontend/Syntax/NameResolution.hs @@ -1281,7 +1281,42 @@ resolveExp (S.ExpAt t) = do ) resolveVariableReference :: Maybe (Exp Name) -> Name -> ResolveM (Exp Name) +resolveVariableReference me'@(Just (Var qualifier)) n = do + qualified <- resolveQualifiedVariableReference qualifier n + maybe (resolveVariableReferenceByLeaf me' n) pure qualified resolveVariableReference me' n = + resolveVariableReferenceByLeaf me' n + +-- Resolve the complete declaration path before consulting the final segment +-- on its own. Every receiver reaching this function has already been ruled +-- out as a runtime value, so a registered @Module.name@, @Class.name@, or +-- @Type.Constructor@ is the intended declaration even when @name@ is also a +-- local/top-level binding. This also preserves intermediate module and +-- contract-type qualifiers while resolving paths such as @pkg.api.value@ and +-- @Contract.Type.Constructor@. +resolveQualifiedVariableReference :: Name -> Name -> ResolveM (Maybe (Exp Name)) +resolveQualifiedVariableReference qualifier leaf = do + let qualifiedName = qualifyName qualifier leaf + qualifiedType <- lookupName qualifiedName + case qualifiedType of + Just TFunction -> + Just . Var <$> canonicalFunctionName qualifiedName + Just TDataCon -> do + constructorName <- resolveQualifiedConstructorName qualifier leaf + pure (Just (Con constructorName [])) + Just TTyCon -> + Just . Var <$> canonicalTypeName qualifiedName + Just TContract -> + pure (Just (Var qualifiedName)) + Just TClass -> + pure (Just (Var qualifiedName)) + Just TModule -> + pure (Just (Var qualifiedName)) + _ -> + pure Nothing + +resolveVariableReferenceByLeaf :: Maybe (Exp Name) -> Name -> ResolveM (Exp Name) +resolveVariableReferenceByLeaf me' n = do dt <- lookupName n case (me', dt) of diff --git a/test/ModuleTypeCheckTests.hs b/test/ModuleTypeCheckTests.hs index 83319ded2..491c43efd 100644 --- a/test/ModuleTypeCheckTests.hs +++ b/test/ModuleTypeCheckTests.hs @@ -222,6 +222,25 @@ moduleTypeCheckTests = got -> assertFailure ("struct kind or ordered fields were lost: " ++ show got), + testCase "qualified nested type paths beat same-named global declarations" $ do + source <- readFile "./test/imports/qualified_nested_type_shadow.solc" + CompUnit _ resolvedDecls <- resolvedSourceOrFail source + let consumer = findSemanticContract "QualifiedNestedType" resolvedDecls + nestedType = QualName (Name "C") "T" + constructorName = QualName nestedType "Inner" + resolvedBindings = + [ (ty, value) + | CFunDecl (FunDef _ sig body) <- decls consumer, + sigName sig == Name "main", + Let _ _ ty (Just value) <- body + ] + assertEqual + "the complete C.T.Inner path remains canonical" + [ ( Just (TyCon nestedType []), + Con constructorName [Lit (IntLit 7)] + ) + ] + resolvedBindings, testCase "same-spelled contract-local data types keep distinct canonical identities" $ do resolved@(CompUnit _ resolvedDecls) <- resolvedSourceOrFail sameNamedLocalTypesSource diff --git a/test/imports/qualified_nested_type_shadow.solc b/test/imports/qualified_nested_type_shadow.solc new file mode 100644 index 000000000..3ee5cf1a9 --- /dev/null +++ b/test/imports/qualified_nested_type_shadow.solc @@ -0,0 +1,14 @@ +contract T { +} + +contract C { + enum T { + Inner(word) + } +} + +contract QualifiedNestedType { + function main() { + let value: C.T = C.T.Inner(7); + } +} From 44c90b7bb3f0b8e3a804390567d1e8a009adc7c2 Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Sat, 25 Jul 2026 01:35:09 +0900 Subject: [PATCH 24/33] Validate Yul literal representations --- src/Language/Hull/TypeCheck.hs | 3 + src/Language/Yul.hs | 100 ++++++++++++++++++ src/Solcore/Frontend/TypeInference/TcStmt.hs | 21 +++- test/HullCases.hs | 8 +- test/ModuleTypeCheckTests.hs | 27 +++++ .../hull/20-asm-literal-boundaries.hull | 14 +++ .../hull/21-err-asm-number-range.hull | 10 ++ .../examples/hull/22-err-asm-string-size.hull | 10 ++ .../hull/23-err-asm-utf8-string-size.hull | 10 ++ 9 files changed, 199 insertions(+), 4 deletions(-) create mode 100644 test/examples/hull/20-asm-literal-boundaries.hull create mode 100644 test/examples/hull/21-err-asm-number-range.hull create mode 100644 test/examples/hull/22-err-asm-string-size.hull create mode 100644 test/examples/hull/23-err-asm-utf8-string-size.hull diff --git a/src/Language/Hull/TypeCheck.hs b/src/Language/Hull/TypeCheck.hs index 94f9a2823..aec68d7bb 100644 --- a/src/Language/Hull/TypeCheck.hs +++ b/src/Language/Hull/TypeCheck.hs @@ -84,6 +84,9 @@ checkStmtAt _ (SAssembly stmts) = do case validateYulControlFlow stmts of Left err -> hullError err Right () -> pure () + case validateYulLiterals stmts of + Left err -> hullError err + Right () -> pure () withLocalEnv (checkAsmBlock stmts) checkStmtAt loopDepth (SFor initStmt cond post body) = -- Variables declared in the init block are scoped over the entire for loop diff --git a/src/Language/Yul.hs b/src/Language/Yul.hs index 82552b224..fb44805e7 100644 --- a/src/Language/Yul.hs +++ b/src/Language/Yul.hs @@ -5,7 +5,10 @@ module Language.Yul where import Common.Pretty +import Data.ByteString qualified as BS import Data.Generics (Data, Typeable) +import Data.Text qualified as Text +import Data.Text.Encoding qualified as Text import Solcore.Frontend.Syntax.Name data YulObject = YulObject String YulCode [YulInner] @@ -134,6 +137,103 @@ validateYulControlFlow = validateBlock 0 False _ -> Right () +-- | Validate the target-level representation of Yul literals. Ordinary string +-- literals are word values and therefore contain at most 32 UTF-8 bytes. +-- Object-name operands are handled specially by Yul and are not word-sized. +validateYulLiterals :: YulBlock -> Either String () +validateYulLiterals = validateBlock + where + validateBlock :: YulBlock -> Either String () + validateBlock = mapM_ validateStmt + + validateStmt :: YulStmt -> Either String () + validateStmt stmt = + case stmt of + YBlock body -> + validateBlock body + YFun _ _ _ body -> + validateBlock body + YLet _ initializer -> + mapM_ validateExp initializer + YAssign _ value -> + validateExp value + YIf condition body -> do + validateExp condition + validateBlock body + YSwitch scrutinee cases defaultBody -> do + validateExp scrutinee + mapM_ validateCase cases + mapM_ validateBlock defaultBody + YFor pre condition post body -> do + validateBlock pre + validateExp condition + validateBlock post + validateBlock body + YExp expression -> + validateExp expression + YBreak -> + pure () + YContinue -> + pure () + YLeave -> + pure () + YComment _ -> + pure () + + validateCase :: YulCase -> Either String () + validateCase (literal, body) = do + validateLiteral False literal + validateBlock body + + validateExp :: YulExp -> Either String () + validateExp expression = + case expression of + YCall function arguments -> + mapM_ + (uncurry (validateArgument function)) + (zip [0 ..] arguments) + YLit literal -> + validateLiteral False literal + YIdent _ -> + pure () + YMeta _ -> + pure () + + validateArgument :: Name -> Int -> YulExp -> Either String () + validateArgument function index expression = + case expression of + YLit literal@(YulString _) + | isYulObjectNameArgument function index -> + validateLiteral True literal + _ -> + validateExp expression + + validateLiteral :: Bool -> YLiteral -> Either String () + validateLiteral _ (YulNumber number) + | number < 0 || number >= 2 ^ (256 :: Int) = + Left + ( "Yul numeric literal is outside the 256-bit word range: " + ++ show number + ) + validateLiteral allowLongString (YulString value) + | not allowLongString && byteLength > 32 = + Left + ( "Yul string literal exceeds 32 UTF-8 bytes (got " + ++ show byteLength + ++ ")" + ) + where + byteLength = BS.length (Text.encodeUtf8 (Text.pack value)) + validateLiteral _ _ = + pure () + +-- | Whether an argument is interpreted by Yul as an object or immutable name +-- rather than as an ordinary word value. +isYulObjectNameArgument :: Name -> Int -> Bool +isYulObjectNameArgument function index = + (function `elem` ["datasize", "dataoffset", "loadimmutable", "linkersymbol"] && index == 0) + || (function == "setimmutable" && index == 1) + data YLiteral = YulNumber Integer | YulString String diff --git a/src/Solcore/Frontend/TypeInference/TcStmt.hs b/src/Solcore/Frontend/TypeInference/TcStmt.hs index 4b180d424..13f80f17f 100644 --- a/src/Solcore/Frontend/TypeInference/TcStmt.hs +++ b/src/Solcore/Frontend/TypeInference/TcStmt.hs @@ -124,6 +124,9 @@ tcStmtWithExpectedReturn' mExpectedReturn stmt@(Asm yblk) = do case validateYulControlFlow yblk of Left err -> tcmError err `wrapError` stmt Right () -> pure () + case validateYulLiterals yblk of + Left err -> tcmError err `wrapError` stmt + Right () -> pure () if isEmptyRevertBlock yblk then do resultTy <- maybe freshTyVar pure mExpectedReturn @@ -2096,15 +2099,29 @@ tcYulExp e@(YCall n es) = do sch <- askEnv n `wrapError` e (_ :=> t) <- freshInst sch - ts <- mapM tcYulExp es + ts <- zipWithM (tcYulCallArgument n) [0 ..] es t' <- freshTyVar s <- unify t (funtype ts t') `wrapError` e _ <- extSubst s withCurrentSubst t' tcYulExp (YMeta _) = pure word +tcYulCallArgument :: Name -> Int -> YulExp -> TcM Ty +tcYulCallArgument function index expression + | isYulObjectNameArgument function index = + case expression of + YLit (YulString _) -> pure string + YMeta _ -> pure string + _ -> + tcmError + ( "Yul object-name argument must be a string literal or metadata reference: " + ++ pretty expression + ) + | otherwise = + tcYulExp expression + tcYLit :: YLiteral -> TcM Ty -tcYLit (YulString _) = return string +tcYLit (YulString _) = return word tcYLit (YulNumber _) = return word -- Yul has no boolean type: 'true'/'false' are word literals (1/0). tcYLit YulTrue = return word diff --git a/test/HullCases.hs b/test/HullCases.hs index fc013eefc..8116d4612 100644 --- a/test/HullCases.hs +++ b/test/HullCases.hs @@ -22,7 +22,8 @@ hullTests = runHullTest "03-sum.hull", runHullTest "04-cond.hull", runHullTest "05-forward-ref.hull", - runHullTest "15-loop-control.hull" + runHullTest "15-loop-control.hull", + runHullTest "20-asm-literal-boundaries.hull" ], testGroup "Programs with type errors" @@ -34,7 +35,10 @@ hullTests = runHullTestExpectingFailure "11-err-asm-sum-return.hull", runHullTestExpectingFailure "12-err-asm-break-outside-loop.hull", runHullTestExpectingFailure "13-err-break-outside-loop.hull", - runHullTestExpectingFailure "14-err-continue-outside-loop.hull" + runHullTestExpectingFailure "14-err-continue-outside-loop.hull", + runHullTestExpectingFailure "21-err-asm-number-range.hull", + runHullTestExpectingFailure "22-err-asm-string-size.hull", + runHullTestExpectingFailure "23-err-asm-utf8-string-size.hull" ] ] diff --git a/test/ModuleTypeCheckTests.hs b/test/ModuleTypeCheckTests.hs index 491c43efd..8ecb8f182 100644 --- a/test/ModuleTypeCheckTests.hs +++ b/test/ModuleTypeCheckTests.hs @@ -629,6 +629,33 @@ moduleTypeCheckTests = "}" ] assertRight "well-scoped Yul control transfer" checked, + testCase "Yul literals honor word and UTF-8 byte bounds" $ do + validBoundaries <- + typecheckSource $ + unlines + [ "function validYulLiterals() {", + " assembly {", + " let maxWord := 115792089237316195423570985008687907853269984665640564039457584007913129639935", + " mstore(0, \"12345678901234567890123456789012\")", + " mstore(32, \"éééééééééééééééé\")", + " pop(datasize(\"this-object-name-is-definitely-longer-than-thirty-two-bytes\"))", + " }", + " return;", + "}" + ] + tooLargeNumber <- + typecheckSource + "function tooLargeNumber() { assembly { let x := 115792089237316195423570985008687907853269984665640564039457584007913129639936 } return; }" + tooLongAscii <- + typecheckSource + "function tooLongAscii() { assembly { mstore(0, \"123456789012345678901234567890123\") } return; }" + tooLongUtf8 <- + typecheckSource + "function tooLongUtf8() { assembly { mstore(0, \"ééééééééééééééééé\") } return; }" + assertRight "maximum word and 32-byte strings" validBoundaries + assertLeft "number larger than a word" tooLargeNumber + assertLeft "33-byte ASCII string" tooLongAscii + assertLeft "34-byte UTF-8 string" tooLongUtf8, testCase "omitted returns clause is a fully annotated unit return" $ do checked <- typecheckSource $ diff --git a/test/examples/hull/20-asm-literal-boundaries.hull b/test/examples/hull/20-asm-literal-boundaries.hull new file mode 100644 index 000000000..88e4264c8 --- /dev/null +++ b/test/examples/hull/20-asm-literal-boundaries.hull @@ -0,0 +1,14 @@ +object AsmLiteralBoundaries { + code { + function run() -> word { + let result : word + assembly { + let maxWord := 115792089237316195423570985008687907853269984665640564039457584007913129639935 + let fullWord := "12345678901234567890123456789012" + let longObjectName := datasize("this-object-name-is-definitely-longer-than-thirty-two-bytes") + result := add(add(maxWord, fullWord), longObjectName) + } + return result + } + } +} diff --git a/test/examples/hull/21-err-asm-number-range.hull b/test/examples/hull/21-err-asm-number-range.hull new file mode 100644 index 000000000..83dfd46e1 --- /dev/null +++ b/test/examples/hull/21-err-asm-number-range.hull @@ -0,0 +1,10 @@ +object AsmNumberRange { + code { + function bad() -> unit { + assembly { + let tooLarge := 115792089237316195423570985008687907853269984665640564039457584007913129639936 + } + return () + } + } +} diff --git a/test/examples/hull/22-err-asm-string-size.hull b/test/examples/hull/22-err-asm-string-size.hull new file mode 100644 index 000000000..ab86ac7e0 --- /dev/null +++ b/test/examples/hull/22-err-asm-string-size.hull @@ -0,0 +1,10 @@ +object AsmStringSize { + code { + function bad() -> unit { + assembly { + let tooLong := "123456789012345678901234567890123" + } + return () + } + } +} diff --git a/test/examples/hull/23-err-asm-utf8-string-size.hull b/test/examples/hull/23-err-asm-utf8-string-size.hull new file mode 100644 index 000000000..f9e14bb1d --- /dev/null +++ b/test/examples/hull/23-err-asm-utf8-string-size.hull @@ -0,0 +1,10 @@ +object AsmUtf8StringSize { + code { + function bad() -> unit { + assembly { + let tooLong := "ééééééééééééééééé" + } + return () + } + } +} From 06f98363fac68344750883b8571dc0831f9304a2 Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Sat, 25 Jul 2026 02:10:15 +0900 Subject: [PATCH 25/33] Support UFCS on arbitrary value receivers --- sol-core.cabal | 1 + src/Solcore/Frontend/Syntax/NameResolution.hs | 71 +++----- test/Main.hs | 4 +- test/UfcsTests.hs | 157 ++++++++++++++++++ test/examples/cases/ufcs-no-conflict.solc | 8 +- test/examples/dispatch/ufcs_array.solc | 4 +- test/examples/ufcs/value-receivers.solc | 38 +++++ 7 files changed, 228 insertions(+), 55 deletions(-) create mode 100644 test/UfcsTests.hs create mode 100644 test/examples/ufcs/value-receivers.solc diff --git a/sol-core.cabal b/sol-core.cabal index 004a3b4d4..2dec718c9 100644 --- a/sol-core.cabal +++ b/sol-core.cabal @@ -195,6 +195,7 @@ test-suite sol-core-tests MatchCompilerTests ModuleTypeCheckTests SpecialiseTests + UfcsTests YulEvalTests ParserTests diff --git a/src/Solcore/Frontend/Syntax/NameResolution.hs b/src/Solcore/Frontend/Syntax/NameResolution.hs index ad2b0847c..6f3e2d8ef 100644 --- a/src/Solcore/Frontend/Syntax/NameResolution.hs +++ b/src/Solcore/Frontend/Syntax/NameResolution.hs @@ -963,28 +963,6 @@ unwrapQualifierReceiver (Just (Con (QualName d conName) [])) | constructorLeafName d == Name conName = Just (Var d) unwrapQualifierReceiver me = me --- UFCS receiver test. --- --- A receiver is eligible for UFCS method-call rewriting only when it is an --- unqualified contract-field access (FieldAccess Nothing _), i.e. a bare --- field name like members used as members.push(addr). Restricting the --- rule to that single shape is what keeps UFCS from colliding with the other --- meanings of dot syntax: --- --- - Class.method(args) / Module.func(args): the receiver resolves to a --- class/module name (Var c), matched by the qualified-name cases in --- resolveExp (S.ExpName ...) before the UFCS case is reached. --- - Type.Constructor: parsed as S.ExpDotName, a different surface node. --- - a plain field read members: no receiver at all. --- - a call on a local variable x.m(args): x resolves to Var, not a --- field access, so UFCS does not apply. --- --- So UFCS is a strict fallback: it only kicks in for field.method(args) once --- every qualified interpretation has been ruled out. -isUfcsReceiver :: Exp Name -> Bool -isUfcsReceiver (FieldAccess Nothing _) = True -isUfcsReceiver _ = False - -- Only declaration-like receivers participate in qualified-name lookup. -- Every other expression is a runtime value whose member access must remain -- explicit in the semantic AST; otherwise a same-spelled local can capture it. @@ -1033,12 +1011,21 @@ resolveExp x@(S.ExpName me n es) = do me' <- unwrapQualifierReceiver <$> (resolve me `wrapError` x) es' <- resolve es `wrapError` x - forM_ me' $ \receiver -> - unless (isUfcsReceiver receiver) $ do - valueReceiver <- isValueReceiver receiver - when valueReceiver (unsupportedValueMemberCall n) + valueReceiver <- maybe (pure False) isValueReceiver me' dt <- lookupName n case (me', dt) of + -- UFCS-style method call on any runtime value: + -- value.method(args) -> Class.method(value, args) when a unique class + -- exposes a method named n. Handle this before consulting the member's + -- unqualified declaration kind: a same-spelled local or top-level name + -- must not steal a member call from its receiver. Declaration-like + -- receivers (Class, Module, Type, Contract/Library) are excluded by + -- isValueReceiver and continue through the qualified-name cases below. + (Just receiver, _) | valueReceiver -> do + mClass <- findClassWithMethod n + case mClass of + Just c -> pure (Call Nothing (qualifyName c n) (receiver : es')) + Nothing -> unresolvedValueMemberCall n -- normal function call (Nothing, Just TFunction) -> pure (Call Nothing n es') @@ -1144,18 +1131,6 @@ resolveExp x@(S.ExpName me n es) = Just TDataCon -> Con <$> resolveQualifiedConstructorName d n <*> pure es' _ -> undefinedName n _ -> pure (Call Nothing n es') - -- UFCS-style method call on a contract-field receiver: - -- field.method(args) -> Class.method(field, args) when a unique class - -- exposes a method named n. This is the last case before the error - -- fallback, so it only fires once every qualified interpretation has been - -- ruled out (see isUfcsReceiver). Ambiguity across several classes - -- makes findClassWithMethod return Nothing and falls through to the - -- undefined-name error rather than guessing. - (Just receiver, _) | isUfcsReceiver receiver -> do - mClass <- findClassWithMethod n - case mClass of - Just c -> pure (Call Nothing (qualifyName c n) (receiver : es')) - Nothing -> undefinedName n -- error _ -> do sameName <- isSameNameConstructor n @@ -1300,7 +1275,7 @@ resolveQualifiedVariableReference qualifier leaf = do qualifiedType <- lookupName qualifiedName case qualifiedType of Just TFunction -> - Just . Var <$> canonicalFunctionName qualifiedName + pure (Just (Var qualifiedName)) Just TDataCon -> do constructorName <- resolveQualifiedConstructorName qualifier leaf pure (Just (Con constructorName [])) @@ -1795,8 +1770,8 @@ lookupName n = -- For UFCS-style method calls (`value.method(args)`): find a class that has -- a method named `m` so we can rewrite the call as `Class.method(value,args)`. --- Returns the first match; ambiguity across multiple classes falls back to --- the regular undefined-name path. +-- Returns the sole match; ambiguity across multiple classes is rejected so +-- name resolution never guesses which class owns the call. findClassWithMethod :: Name -> ResolveM (Maybe Name) findClassWithMethod m = do @@ -1853,7 +1828,7 @@ contextLabelMessage diagnostic = Just (DiagnosticCode "SC0107") -> "invalid pattern" Just (DiagnosticCode "SC0122") -> "unsupported function type" Just (DiagnosticCode "SC0123") -> "unsupported mixed return mode" - Just (DiagnosticCode "SC0124") -> "unsupported member call" + Just (DiagnosticCode "SC0124") -> "unresolved member call" _ -> "diagnostic reported here" contextSourceSpan :: (Data a) => a -> Maybe SourceSpan @@ -1995,15 +1970,15 @@ undefinedName n = [] [] -unsupportedValueMemberCall :: Name -> ResolveM a -unsupportedValueMemberCall n = +unresolvedValueMemberCall :: Name -> ResolveM a +unresolvedValueMemberCall n = diagnosticErrorAtName "SC0124" - ("member calls on value receivers are not supported: " ++ pretty n) + ("no unique trait method for value member call: " ++ pretty n) n - "unsupported member call" - ["value-member dispatch has no runtime representation yet"] - ["use an explicit function call"] + "unresolved member call" + ["value-member syntax requires exactly one trait to declare the named method"] + ["use an explicit qualified trait call to disambiguate"] unqualifiedConstructorError :: Name -> ResolveM a unqualifiedConstructorError n = diff --git a/test/Main.hs b/test/Main.hs index c5a06387e..fbe25489d 100644 --- a/test/Main.hs +++ b/test/Main.hs @@ -11,6 +11,7 @@ import ModuleTypeCheckTests import ParserTests import SpecialiseTests import Test.Tasty +import UfcsTests import YulEvalTests main :: IO () @@ -38,5 +39,6 @@ tests = matchTests, yulEvalTests, hullTests, - specialiseTests + specialiseTests, + ufcsTests ] diff --git a/test/UfcsTests.hs b/test/UfcsTests.hs new file mode 100644 index 000000000..e6f338998 --- /dev/null +++ b/test/UfcsTests.hs @@ -0,0 +1,157 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PatternSynonyms #-} + +module UfcsTests (ufcsTests) where + +import Data.Data (Data) +import Data.Generics (everything, mkQ) +import Data.List (isInfixOf) +import Data.Maybe (mapMaybe) +import Solcore.Desugarer.FieldAccess (fieldDesugarTopDecls) +import Solcore.Desugarer.IndirectCall (indirectCallTopDecls) +import Solcore.Diagnostics (compilerErrorText) +import Solcore.Frontend.Parser.SolcoreParser (parseCompUnit) +import Solcore.Frontend.Syntax +import Solcore.Frontend.Syntax.NameResolution (nameResolution) +import Solcore.Frontend.Syntax.SyntaxTree qualified as Surface +import Solcore.Pipeline.Options (Option (..), emptyOption) +import Solcore.Pipeline.SolcorePipeline (compile) +import System.FilePath (()) +import Test.Tasty +import Test.Tasty.HUnit + +ufcsTests :: TestTree +ufcsTests = + testGroup + "Value-receiver UFCS" + [ testCase "rewrites parameters, locals, indexed values, and computed values" $ do + resolved <- resolveSource valueReceiverSource + let receiverArgs = + mapMaybe firstArgument (callsNamed receiverMethodName resolved) + length receiverArgs @?= 4 + assertBool "parameter receiver was not prepended" (any (isVar "param") receiverArgs) + assertBool "local receiver was not prepended" (any (isVar "local") receiverArgs) + assertBool "indexed receiver was not prepended" (any isIndexedReceiver receiverArgs) + assertBool "computed receiver was not prepended" (any isComputedReceiver receiverArgs), + testCase "field and indirect-call lowering preserve an indexed UFCS receiver" $ do + CompUnit resolvedImports resolvedDecls <- resolveSource fieldReceiverSource + let fieldLowered = fieldDesugarTopDecls resolvedDecls + fieldReceiverArgs = + mapMaybe firstArgument (callsNamed receiverMethodName fieldLowered) + assertBool + "field lowering did not lower the indexed receiver before the method call" + (any isLoweredIndexReceiver fieldReceiverArgs) + (directDecls, _) <- indirectCallTopDecls fieldLowered + let directMethodCalls = callsNamed receiverMethodName (CompUnit resolvedImports directDecls) + length directMethodCalls @?= 1, + testCase "rejects a value member with no matching trait method" $ do + parsed <- parseSource invalidMemberSource + resolved <- nameResolution parsed + case resolved of + Left err -> + assertBool + ("unexpected diagnostic:\n" ++ compilerErrorText err) + ( "SC0124" `isInfixOf` compilerErrorText err + && "value member call: missing" `isInfixOf` compilerErrorText err + ) + Right unit -> + assertFailure ("invalid member unexpectedly resolved: " ++ show unit), + testCase "compiles every value receiver form end to end" $ do + let folder = "test" "examples" "ufcs" + fixture = folder "value-receivers.solc" + options = + (emptyOption fixture) + { optRootDir = folder, + optNoGenDispatch = True + } + result <- compile options + case result of + Left err -> assertFailure err + Right _ -> pure () + ] + +receiverMethodName :: Name +receiverMethodName = QualName "ReceiverMethod" "project" + +firstArgument :: [Exp Name] -> Maybe (Exp Name) +firstArgument [] = Nothing +firstArgument (argument : _) = Just argument + +callsNamed :: (Data a) => Name -> a -> [[Exp Name]] +callsNamed expected = + everything (++) ([] `mkQ` collect) + where + collect (Call _ actual arguments) + | actual == expected = [arguments] + collect _ = [] + +isVar :: Name -> Exp Name -> Bool +isVar expected (Var actual) = actual == expected +isVar _ _ = False + +isIndexedReceiver :: Exp Name -> Bool +isIndexedReceiver (Indexed (Var values) (Var index)) = + values == "values" && index == "index" +isIndexedReceiver _ = False + +isComputedReceiver :: Exp Name -> Bool +isComputedReceiver (Call Nothing calleeName _) = + calleeName == QualName "Add" "add" +isComputedReceiver _ = False + +isLoweredIndexReceiver :: Exp Name -> Bool +isLoweredIndexReceiver (Call Nothing calleeName _) = + calleeName == "ridx" +isLoweredIndexReceiver _ = False + +parseSource :: String -> IO Surface.CompUnit +parseSource source = do + parsed <- parseCompUnit source + case parsed of + Left err -> assertFailure ("parse failed:\n" ++ err) >> error "unreachable" + Right unit -> pure unit + +resolveSource :: String -> IO (CompUnit Name) +resolveSource source = do + parsed <- parseSource source + resolved <- nameResolution parsed + case resolved of + Left err -> + assertFailure ("name resolution failed:\n" ++ compilerErrorText err) + >> error "unreachable" + Right unit -> pure unit + +valueReceiverSource :: String +valueReceiverSource = + unlines + [ "trait ReceiverMethod {", + " function project(value: a, salt: word) returns (word);", + "}", + "function make(value: word) returns (word) { return value; }", + "function use(values: word, index: word, param: word) returns (word) {", + " let project: word = 99;", + " let local: word = param;", + " let fromParam: word = param.project(1);", + " let fromLocal: word = local.project(2);", + " let fromIndex: word = values[index].project(3);", + " return (make(param) + 4).project(5);", + "}" + ] + +fieldReceiverSource :: String +fieldReceiverSource = + unlines + [ "trait ReceiverMethod {", + " function project(value: a, salt: word) returns (word);", + "}", + "contract ReceiverContract {", + " values: word;", + " function use(index: word) returns (word) {", + " return values[index].project(1);", + " }", + "}" + ] + +invalidMemberSource :: String +invalidMemberSource = + "function bad(value: word) returns (word) { return value.missing(); }" diff --git a/test/examples/cases/ufcs-no-conflict.solc b/test/examples/cases/ufcs-no-conflict.solc index 9953ba246..b67def76c 100644 --- a/test/examples/cases/ufcs-no-conflict.solc +++ b/test/examples/cases/ufcs-no-conflict.solc @@ -9,10 +9,10 @@ import {*} from std; // * qualified constructor Color.Red (dotted constructor) // * plain field read val // -// UFCS only fires when the receiver is an (unqualified) contract field, so a -// receiver that resolves to a class/module name (`Combiner.combine(...)`) or a -// type name (`Color.Red`) is handled by the earlier qualified-name cases and -// never reaches the UFCS rule. +// UFCS only fires when the receiver is a runtime value. A receiver that +// resolves to a class/module name (`Combiner.combine(...)`) or a type name +// (`Color.Red`) is handled by qualified-name resolution and never reaches the +// UFCS rule. trait Combiner { function combine(x : a, y : word) returns (word); diff --git a/test/examples/dispatch/ufcs_array.solc b/test/examples/dispatch/ufcs_array.solc index 33d58cfce..5566c6891 100644 --- a/test/examples/dispatch/ufcs_array.solc +++ b/test/examples/dispatch/ufcs_array.solc @@ -7,8 +7,8 @@ import {mload, mstore} from std.opcodes; // This contract is byte-for-byte equivalent in behaviour to // dispatch/storage_array.solc, but exercises the receiver-style method-call // sugar resolved by NameResolution: when the receiver of recv.method(args) -// is an (unqualified) contract field and a unique class exposes method, the -// call is rewritten to Class.method(recv, args). So: +// is a runtime value and a unique class exposes method, the call is rewritten +// to Class.method(recv, args). Here each receiver is a contract field, so: // // members.push(addr) ==> ArrayPush.push(members, addr) // members.length() ==> Array.length(members) diff --git a/test/examples/ufcs/value-receivers.solc b/test/examples/ufcs/value-receivers.solc new file mode 100644 index 000000000..233d7ea40 --- /dev/null +++ b/test/examples/ufcs/value-receivers.solc @@ -0,0 +1,38 @@ +import {*} from std; + +trait ReceiverMethod { + function project(value: a, salt: word) returns (word); +} + +impl ReceiverMethod { + function project(value: word, salt: word) returns (word) { + return value + salt; + } +} + +function makeValue(value: word) returns (word) { + return value; +} + +contract ValueReceivers { + values: word[]; + + constructor() {} + + function fromParam(value: word) public returns (word) { + return value.project(1); + } + + function fromLocal(value: word) public returns (word) { + let local: word = value; + return local.project(2); + } + + function fromIndex(index: uint256) public returns (word) { + return values[index].project(3); + } + + function fromComputed(value: word) public returns (word) { + return (makeValue(value) + 4).project(5); + } +} From 8c191de7d2d279ec59e11e1a47c07b47d337cc42 Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Sat, 25 Jul 2026 11:07:46 +0900 Subject: [PATCH 26/33] Allow qualified generic-derivation pragma targets --- src/Solcore/Frontend/Parser/Decl.hs | 2 +- test/ParserTests.hs | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Solcore/Frontend/Parser/Decl.hs b/src/Solcore/Frontend/Parser/Decl.hs index eb962e77c..a83144963 100644 --- a/src/Solcore/Frontend/Parser/Decl.hs +++ b/src/Solcore/Frontend/Parser/Decl.hs @@ -200,7 +200,7 @@ pragmaTypeP = -- defaults to 'DisableAll'. pragmaStatusForP :: PragmaType -> Parser PragmaStatus pragmaStatusForP NoGenericInstanceFor = do - names <- simpleNameP `sepBy1` comma + names <- qualifiedName `sepBy1` comma return (DisableFor (NE.fromList names)) pragmaStatusForP _ = option DisableAll $ do names <- simpleNameP `sepBy1` comma diff --git a/test/ParserTests.hs b/test/ParserTests.hs index e1a0e46e6..95b893eac 100644 --- a/test/ParserTests.hs +++ b/test/ParserTests.hs @@ -1858,7 +1858,20 @@ pragmaTests = parsesAs topDeclP "pragma solcore noGenericInstanceFor MyType;" - (TPragmaDecl (Pragma NoGenericInstanceFor (DisableFor ("MyType" :| [])))) + (TPragmaDecl (Pragma NoGenericInstanceFor (DisableFor ("MyType" :| [])))), + testCase "disable generic instance generation for a nested type" $ do + let source = + "pragma solcore noGenericInstanceFor Capsule.Token;" + parsesAs + topDeclP + source + ( TPragmaDecl + ( Pragma + NoGenericInstanceFor + (DisableFor (QualName "Capsule" "Token" :| [])) + ) + ) + roundTripsTopDecl source ] legacySyntaxTests :: TestTree From 267d23b5f42eee317be8504b5db22f7f41ee9ada Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Sat, 25 Jul 2026 11:17:28 +0900 Subject: [PATCH 27/33] Complete rich syntax constructor migration --- src/Solcore/Backend/EmitHull.hs | 11 ++--- src/Solcore/Desugarer/ContractDispatch.hs | 21 +++++++--- src/Solcore/Desugarer/DeriveGeneric.hs | 49 ++++++++++++++++------- test/ContractAbiTests.hs | 7 +++- 4 files changed, 61 insertions(+), 27 deletions(-) diff --git a/src/Solcore/Backend/EmitHull.hs b/src/Solcore/Backend/EmitHull.hs index 6035fc4ac..2873b796e 100644 --- a/src/Solcore/Backend/EmitHull.hs +++ b/src/Solcore/Backend/EmitHull.hs @@ -13,7 +13,7 @@ import Language.Hull qualified as Hull import Language.Yul import Solcore.Backend.Mast import Solcore.Frontend.Pretty.SolcorePretty -import Solcore.Frontend.Syntax.Contract (Constr (..), DataTy (..)) +import Solcore.Frontend.Syntax.Contract (Constr (..), DataTy (..), DataTyKind (..)) import Solcore.Frontend.Syntax.Name import Solcore.Frontend.Syntax.Stmt (Literal (..)) import Solcore.Frontend.Syntax.Ty (Ty (..), Tyvar (..)) @@ -75,8 +75,9 @@ type DataTable = Map.Map Name DataTy sumDataTy :: DataTy sumDataTy = - DataTy - { dataName = "sum", + DataTyWithKind + { dataTyKind = EnumKind, + dataName = "sum", dataParams = [TVar "a", TVar "b"], dataConstrs = [ Constr "inl" [tyvar "a"], @@ -198,7 +199,7 @@ translateTCon (Name "pair") tas = translateProductType tas translateTCon tycon tas = do mti <- gets (Map.lookup tycon . ecDT) case mti of - Just (DataTy _n tvs cs) -> do + Just (DataTyWithKind _ _n tvs cs) -> do let subst = zip tvs (map mastToTy tas) tys <- mapM (translateDCon subst) cs Hull.TNamed (show tycon) <$> buildSumType tys @@ -228,7 +229,7 @@ emitConApp (MastId n ty) as = (MastTyCon tcname tas) -> do mti <- gets (Map.lookup tcname . ecDT) case mti of - Just (DataTy _ _tvs allCons) -> do + Just (DataTyWithKind _ _ _tvs allCons) -> do (prod, code) <- translateProduct as hullTargetType <- translateTCon tcname tas let result = encodeCon n allCons hullTargetType prod diff --git a/src/Solcore/Desugarer/ContractDispatch.hs b/src/Solcore/Desugarer/ContractDispatch.hs index 75989aca0..6782ddbf8 100644 --- a/src/Solcore/Desugarer/ContractDispatch.hs +++ b/src/Solcore/Desugarer/ContractDispatch.hs @@ -153,25 +153,31 @@ transformConstructor contractName cons argsTuple = (tupleTyFromList (mapMaybe getTy params)) initFun = CFunDecl (FunDef False initSig (constrBody cons)) initSig = - Signature + SignatureWithReturnNames { sigVars = mempty, sigContext = mempty, sigName = initFunName, sigParams = params, sigRetComptime = False, sigReturn = Just unit, - sigPayable = False + sigPayable = False, + sigReturnNames = [], + sigReturnItems = [], + sigModifiers = [] } copySig = - Signature + SignatureWithReturnNames { sigVars = mempty, sigContext = mempty, sigName = "copy_arguments_for_constructor", sigParams = mempty, sigRetComptime = False, sigReturn = Just argsTuple, - sigPayable = False + sigPayable = False, + sigReturnNames = [], + sigReturnItems = [], + sigModifiers = [] } contractString = show contractName yulContractName = YLit $ YulString contractString @@ -207,14 +213,17 @@ transformConstructor contractName cons copyArgsFun = CFunDecl (FunDef False copySig copyBody) startSig = - Signature + SignatureWithReturnNames { sigVars = mempty, sigContext = mempty, sigName = deployerName, sigParams = mempty, sigRetComptime = False, sigReturn = Just unit, - sigPayable = False + sigPayable = False, + sigReturnNames = [], + sigReturnItems = [], + sigModifiers = [] } -- A non-payable constructor must reject any incoming value transfer -- during deployment. A payable constructor skips this check. This mirrors diff --git a/src/Solcore/Desugarer/DeriveGeneric.hs b/src/Solcore/Desugarer/DeriveGeneric.hs index 435df7034..ad9ee13d0 100644 --- a/src/Solcore/Desugarer/DeriveGeneric.hs +++ b/src/Solcore/Desugarer/DeriveGeneric.hs @@ -229,14 +229,17 @@ buildFrom dt = FunDef False sig (fromBody dt) mainT = TyCon (dataName dt) (map TyVar (dataParams dt)) repT = sopRep dt sig = - Signature + SignatureWithReturnNames { sigVars = [], sigContext = [], sigName = Name "from", sigParams = [Typed False (Name "_x") mainT], sigRetComptime = False, sigReturn = Just repT, - sigPayable = False + sigPayable = False, + sigReturnNames = [], + sigReturnItems = [], + sigModifiers = [] } buildTo :: DataTy -> FunDef Name @@ -245,14 +248,17 @@ buildTo dt = FunDef False sig (toBody dt) mainT = TyCon (dataName dt) (map TyVar (dataParams dt)) repT = sopRep dt sig = - Signature + SignatureWithReturnNames { sigVars = [], sigContext = [], sigName = Name "to", sigParams = [Typed False (Name "_r") repT], sigRetComptime = False, sigReturn = Just mainT, - sigPayable = False + sigPayable = False, + sigReturnNames = [], + sigReturnItems = [], + sigModifiers = [] } buildInstance :: DataTy -> Instance Name @@ -312,14 +318,17 @@ buildStorageSize dt = } where sig = - Signature + SignatureWithReturnNames { sigVars = [], sigContext = [], sigName = Name "size", sigParams = [Typed False (Name "_x") (proxyTyOf (mainTyOf dt))], sigRetComptime = False, sigReturn = Just wordTy, - sigPayable = False + sigPayable = False, + sigReturnNames = [], + sigReturnItems = [], + sigModifiers = [] } body = [Return (methodCall "StorageSize" "size" [proxyExpOf (sopRep dt)])] @@ -359,7 +368,7 @@ buildCanStore dt = -- storage(Typedef.rep(_r)) : storage() repSlot = TyExp (Con (Name "storage") [methodCall "Typedef" "rep" [Var (Name "_r")]]) (storageTyOf repT) storeSig = - Signature + SignatureWithReturnNames { sigVars = [], sigContext = [], sigName = Name "store", @@ -369,18 +378,24 @@ buildCanStore dt = ], sigRetComptime = False, sigReturn = Just unitTy, - sigPayable = False + sigPayable = False, + sigReturnNames = [], + sigReturnItems = [], + sigModifiers = [] } storeBody = [StmtExp (methodCall "CanStore" "store" [repSlot, methodCall "Generic" "from" [Var (Name "_v")]])] loadSig = - Signature + SignatureWithReturnNames { sigVars = [], sigContext = [], sigName = Name "load", sigParams = [Typed False (Name "_r") (storageTyOf mainT)], sigRetComptime = False, sigReturn = Just mainT, - sigPayable = False + sigPayable = False, + sigReturnNames = [], + sigReturnItems = [], + sigModifiers = [] } loadBody = [ Let False (Name "_x") (Just repT) (Just (methodCall "CanStore" "load" [repSlot])), @@ -415,14 +430,17 @@ buildABIAttribs dt = where repT = sopRep dt sig method ret = - Signature + SignatureWithReturnNames { sigVars = [], sigContext = [], sigName = Name method, sigParams = [Typed False (Name "_ty") (proxyTyOf (mainTyOf dt))], sigRetComptime = False, sigReturn = Just ret, - sigPayable = False + sigPayable = False, + sigReturnNames = [], + sigReturnItems = [], + sigModifiers = [] } body method = [Return (methodCall "ABIAttribs" method [proxyExpOf repT])] @@ -466,7 +484,7 @@ buildABIDecode dt = readerTv = TVar (Name "_reader") readerTy = TyVar readerTv sig = - Signature + SignatureWithReturnNames { sigVars = [], sigContext = [], sigName = Name "decode", @@ -476,7 +494,10 @@ buildABIDecode dt = ], sigRetComptime = False, sigReturn = Just mainT, - sigPayable = False + sigPayable = False, + sigReturnNames = [], + sigReturnItems = [], + sigModifiers = [] } body = [ Match diff --git a/test/ContractAbiTests.hs b/test/ContractAbiTests.hs index 3ff920688..fc2e817a1 100644 --- a/test/ContractAbiTests.hs +++ b/test/ContractAbiTests.hs @@ -38,14 +38,17 @@ tyCon n = TyCon (Name n) [] sig :: String -> [Param Name] -> Maybe Ty -> Bool -> Signature Name sig fname params ret payable = - Signature + SignatureWithReturnNames { sigVars = [], sigContext = [], sigName = Name fname, sigParams = params, sigRetComptime = False, sigReturn = ret, - sigPayable = payable + sigPayable = payable, + sigReturnNames = [], + sigReturnItems = [], + sigModifiers = [MutabilityModifier MutabilityPayable | payable] } fun :: Bool -> Signature Name -> ContractDecl Name From 9e74a3b60885337c9ba4f69572f40938ab2f87f7 Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Sat, 25 Jul 2026 11:17:50 +0900 Subject: [PATCH 28/33] Make compound-assignment tests self-contained --- test/ParserTests.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/test/ParserTests.hs b/test/ParserTests.hs index 95b893eac..6bb880638 100644 --- a/test/ParserTests.hs +++ b/test/ParserTests.hs @@ -12,6 +12,7 @@ import Solcore.Frontend.Lexer.SolcoreLexer (identifier, sc) import Solcore.Frontend.Parser.Decl (importP, topDeclP) import Solcore.Frontend.Parser.Expr (exprP) import Solcore.Frontend.Parser.Patterns (patP) +import Solcore.Frontend.Parser.SolcoreParser (parseCompUnit) import Solcore.Frontend.Parser.SolcoreTypes (predP, typeP) import Solcore.Frontend.Parser.Stmt (bodyP, stmtP) import Solcore.Frontend.Pretty.SolcorePretty qualified as SolcorePretty From 202d1d851fc00611ce1af8c827146b6b5af608f8 Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Sat, 25 Jul 2026 11:18:31 +0900 Subject: [PATCH 29/33] Derive instances for nested syntax declarations --- src/Solcore/Pipeline/SolcorePipeline.hs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Solcore/Pipeline/SolcorePipeline.hs b/src/Solcore/Pipeline/SolcorePipeline.hs index f6333e573..85bb67464 100644 --- a/src/Solcore/Pipeline/SolcorePipeline.hs +++ b/src/Solcore/Pipeline/SolcorePipeline.hs @@ -21,7 +21,7 @@ import Solcore.Backend.MastEval (defaultFuel, eliminateDeadCode, evalCompUnit) import Solcore.Backend.Specialise (specialiseCompUnit) import Solcore.Desugarer.ContractDispatch (contractDispatchTopDecls, writeContractAbis) import Solcore.Desugarer.DecisionTreeCompiler (matchCompiler, warningDiagnostic) -import Solcore.Desugarer.DeriveGeneric (deriveGenericTopDecls) +import Solcore.Desugarer.DeriveGeneric (collectDataDefs, deriveGenericTopDecls) import Solcore.Desugarer.FieldAccess (fieldDesugarTopDecls) import Solcore.Desugarer.IfDesugarer (ifDesugarer) import Solcore.Desugarer.IndirectCall (indirectCallTopDecls) @@ -858,8 +858,10 @@ prepareInferenceDeclsForTypeInference opts emitOutput imps inferenceDecls = do putStrLn "> Dispatch:" putStrLn $ prettyInferenceDecls dispatched - -- Generic instance derivation (only for locally-defined data types) - let localData = [dt | ModuleInferenceDecl ModuleLocalDecl (TDataDef dt) <- dispatched] + -- Generic/storage/ABI instance derivation (only for locally-defined data + -- types). Contract- and library-local declarations remain nested at this + -- stage, so collect both top-level TDataDef declarations and CDataDecls. + let localData = localDataDefsForDeriving dispatched derived <- ExceptT $ fmap (first compilerErrorFromString) $ @@ -915,6 +917,13 @@ prepareInferenceDeclsForTypeInference opts emitOutput imps inferenceDecls = do pure withFromInt +localDataDefsForDeriving :: [ModuleInferenceDecl] -> [DataTy] +localDataDefsForDeriving inferenceDecls = + collectDataDefs + [ topDecl + | ModuleInferenceDecl ModuleLocalDecl topDecl <- inferenceDecls + ] + parseExternalLibSpecs :: [String] -> Either String [(Name, FilePath)] parseExternalLibSpecs = fmap reverse . foldM step [] From 168410d092dc662e094f5f7e0930e898f76932fb Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Sat, 25 Jul 2026 11:23:33 +0900 Subject: [PATCH 30/33] Preserve located field accesses during lowering --- src/Solcore/Desugarer/FieldAccess.hs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Solcore/Desugarer/FieldAccess.hs b/src/Solcore/Desugarer/FieldAccess.hs index 4690d23e2..e0bf437c7 100644 --- a/src/Solcore/Desugarer/FieldAccess.hs +++ b/src/Solcore/Desugarer/FieldAccess.hs @@ -299,6 +299,11 @@ transRhs expr@(FieldAccess Nothing x) cenv fieldMap = Con "MemberAccessProxy" [cxt, fieldSel] result = rhsAccess fieldMap in traces ["< transRhs", pretty expr, "~>", pretty result] result +transRhs (FieldAccessWithLocation location (Just receiver) memberName) cenv = + FieldAccessWithLocation + location + (Just (transRhs receiver cenv)) + memberName transRhs expr@FieldAccess {} _ = notImplemented "transRhs" expr transRhs expr cenv = go expr cenv where From a6ad6088fc5048f601170286393e63a694a07b39 Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Sat, 25 Jul 2026 11:25:41 +0900 Subject: [PATCH 31/33] Preserve contract shell semantics through lowering --- src/Solcore/Backend/Specialise.hs | 20 +++++------ src/Solcore/Desugarer/ContractDispatch.hs | 33 +++++++++++-------- src/Solcore/Desugarer/DecisionTreeCompiler.hs | 4 +-- src/Solcore/Desugarer/FieldAccess.hs | 13 +++++--- src/Solcore/Desugarer/IndirectCall.hs | 4 +-- src/Solcore/Pipeline/SolcorePipeline.hs | 4 +-- 6 files changed, 45 insertions(+), 33 deletions(-) diff --git a/src/Solcore/Backend/Specialise.hs b/src/Solcore/Backend/Specialise.hs index 80233e55e..f568501f7 100644 --- a/src/Solcore/Backend/Specialise.hs +++ b/src/Solcore/Backend/Specialise.hs @@ -253,13 +253,10 @@ addInstResolutions :: Instance Id -> SM () addInstResolutions inst = forM_ (instFunctions inst) (addMethodResolution (instDefault inst) (instName inst) (mainTy inst)) specialiseTopDecl :: TopDecl Id -> SM [TopDecl Id] -specialiseTopDecl (TContr (Contract _ _ decls)) - | any isSignatureDecl decls = pure [] - where - isSignatureDecl CSignatureDecl {} = True - isSignatureDecl _ = False -specialiseTopDecl (TContr (Contract name args decls)) = withLocalState do - addContractResolutions (Contract name args decls) +specialiseTopDecl (TContr (ContractWithKind InterfaceKind _ _ _)) = pure [] +specialiseTopDecl (TContr (ContractWithKind LibraryKind _ _ _)) = pure [] +specialiseTopDecl (TContr (ContractWithKind ContractKind name args decls)) = withLocalState do + addContractResolutions (ContractWithKind ContractKind name args decls) -- Runtime code runtimeDecls <- withLocalState do forM_ entries specEntry @@ -273,7 +270,7 @@ specialiseTopDecl (TContr (Contract name args decls)) = withLocalState do -- use mutual to group constructor with its dependencies pure [CMutualDecl depDecls] Nothing -> pure [] - return [TContr (Contract name args (deployDecls ++ runtimeDecls))] + return [TContr (ContractWithKind ContractKind name args (deployDecls ++ runtimeDecls))] where entries = ["main"] -- Eventually all public methods getSpecialisedDecls :: SM [ContractDecl Id] @@ -307,7 +304,7 @@ specEntryOpt name = withLocalState do Nothing -> pure Nothing addContractResolutions :: Contract Id -> SM () -addContractResolutions (Contract _name _args cdecls) = do +addContractResolutions (ContractWithKind _ _name _args cdecls) = do forM_ cdecls addCDeclResolution addCDeclResolution :: ContractDecl Id -> SM () @@ -1126,7 +1123,10 @@ toMastTopDecl (TDataDef dt) = MastTDataDef dt toMastTopDecl d = error $ "toMastTopDecl: unexpected " ++ show d toMastContract :: Contract Id -> MastContract -toMastContract (Contract n _tyParams ds) = MastContract n (map toMastContractDecl ds) +toMastContract (ContractWithKind ContractKind n _tyParams ds) = + MastContract n (map toMastContractDecl ds) +toMastContract c = + error $ "toMastContract: non-runtime declaration kind: " ++ show (contractKind c) toMastContractDecl :: ContractDecl Id -> MastContractDecl toMastContractDecl (CDataDecl dt) = MastCDataDecl dt diff --git a/src/Solcore/Desugarer/ContractDispatch.hs b/src/Solcore/Desugarer/ContractDispatch.hs index 6782ddbf8..cfba907a0 100644 --- a/src/Solcore/Desugarer/ContractDispatch.hs +++ b/src/Solcore/Desugarer/ContractDispatch.hs @@ -38,7 +38,7 @@ contractDispatchTopDecls topdecls = Set.toList extras <> topdecls' where (extras, topdecls') = mapAccumL go Set.empty topdecls go acc (TContr c) - | isInterfaceContract c = (acc, TContr c) + | contractKind c /= ContractKind = (acc, TContr c) | "main" `notElem` functionNames c = (Set.union acc (genNameDecls c), TContr (genMainFn True c)) | otherwise = (acc, TContr (genMainFn False c)) go acc v = (acc, v) @@ -64,31 +64,27 @@ functionNames = foldr go [] . decls go (CSignatureDecl _ sig) = (sigName sig :) go _ = id -isInterfaceContract :: Contract a -> Bool -isInterfaceContract = any isSignature . decls - where - isSignature CSignatureDecl {} = True - isSignature _ = False - -- | Returns the (at most one) user-defined fallback function for a contract. findFallback :: Contract a -> Maybe (FunDef a) findFallback c = listToMaybe [fd | CFunDecl fd <- decls c, isFallback fd] genNameDecls :: Contract Name -> Set (TopDecl Name) -genNameDecls (Contract cname _ cdecls) = foldl go Set.empty cdecls +genNameDecls (ContractWithKind ContractKind cname _ cdecls) = foldl go Set.empty cdecls where - go acc (CFunDecl (FunDef True sig _)) + go acc (CFunDecl (FunDef legacyVisibility sig _)) + | not (externallyVisible legacyVisibility sig) = acc | sigName sig == fallbackName = acc | otherwise = let dataTy = mkNameTy cname (sigName sig) instDef = mkNameInst dataTy (sigName sig) in Set.union (Set.fromList [TDataDef dataTy, TInstDef instDef]) acc go acc _ = acc +genNameDecls _ = Set.empty genMainFn :: Bool -> Contract Name -> Contract Name -genMainFn addMain c@(Contract cname tys cdecls) - | addMain = Contract cname tys (CFunDecl mainfn : Set.toList cdecls') - | otherwise = Contract cname tys (Set.toList cdecls') +genMainFn addMain c@(ContractWithKind ContractKind cname tys cdecls) + | addMain = ContractWithKind ContractKind cname tys (CFunDecl mainfn : Set.toList cdecls') + | otherwise = ContractWithKind ContractKind cname tys (Set.toList cdecls') where cdecls'' = if hasConstructor cdecls then cdecls else cdecls ++ [defaultConstructor] cdecls' = Set.unions (map (transformCDecl cname) cdecls'') @@ -128,7 +124,8 @@ genMainFn addMain c@(Contract cname tys cdecls) mkMethod s = error $ "Internal Error: contract methods must be fully typed: " <> show s -- skip the optional fallback function and non-public methods in the methods tuple - unwrapSigs (CFunDecl (FunDef True s _)) + unwrapSigs (CFunDecl (FunDef legacyVisibility s _)) + | not (externallyVisible legacyVisibility s) = Nothing | sigName s == fallbackName = Nothing | otherwise = Just s unwrapSigs _ = Nothing @@ -138,6 +135,16 @@ genMainFn addMain c@(Contract cname tys cdecls) getTy (Typed _ _ t) = Just t getTy (Untyped {}) = Nothing +genMainFn _ c = c + +externallyVisible :: Bool -> Signature a -> Bool +externallyVisible legacyVisibility sig = + case sigVisibility sig of + Just VisibilityPublic -> True + Just VisibilityExternal -> True + Just VisibilityInternal -> False + Just VisibilityPrivate -> False + Nothing -> legacyVisibility transformCDecl :: Name -> ContractDecl Name -> Set (ContractDecl Name) transformCDecl contractName (CConstrDecl c) = transformConstructor contractName c diff --git a/src/Solcore/Desugarer/DecisionTreeCompiler.hs b/src/Solcore/Desugarer/DecisionTreeCompiler.hs index f09ee618b..ada05d70c 100644 --- a/src/Solcore/Desugarer/DecisionTreeCompiler.hs +++ b/src/Solcore/Desugarer/DecisionTreeCompiler.hs @@ -64,8 +64,8 @@ instance Compile (TopDecl Id) where compile d = pure d instance Compile (Contract Id) where - compile (Contract n vs ds) = - Contract n vs + compile (ContractWithKind kind n vs ds) = + ContractWithKind kind n vs <$> local (\(te, ctx, warnSpan) -> (Map.union env' te, ctx ++ ["contract " ++ pretty n], warnSpan)) (compile ds) where ds' = [d | (CDataDecl d) <- ds] diff --git a/src/Solcore/Desugarer/FieldAccess.hs b/src/Solcore/Desugarer/FieldAccess.hs index e0bf437c7..42f005da6 100644 --- a/src/Solcore/Desugarer/FieldAccess.hs +++ b/src/Solcore/Desugarer/FieldAccess.hs @@ -48,9 +48,13 @@ fieldDesugarTopDecls topdecls = extras <> topdecls' ] (extras, topdecls') = mapAccumL go mempty topdecls go acc (TContr c) = - let hasSingletonCollision = - singletonNameForContract (Contract.name c) `Set.member` existingDataTypes - in (acc <> extraTopDeclsForContract (not hasSingletonCollision) c, TContr (transContract c)) + case Contract.contractKind c of + ContractKind -> + let hasSingletonCollision = + singletonNameForContract (Contract.name c) `Set.member` existingDataTypes + in (acc <> extraTopDeclsForContract (not hasSingletonCollision) c, TContr (transContract c)) + InterfaceKind -> (acc, TContr c) + LibraryKind -> (acc, TContr c) go acc v = (acc, v) -------------------------------- @@ -58,7 +62,7 @@ fieldDesugarTopDecls topdecls = extras <> topdecls' -------------------------------- extraTopDeclsForContract :: Bool -> NmContract -> [NmTopDecl] -extraTopDeclsForContract includeSingleton (Contract cname _ts cdecls) = do +extraTopDeclsForContract includeSingleton (ContractWithKind ContractKind cname _ts cdecls) = do let singName = singletonNameForContract cname let contractSingDecl = TDataDef $ DataTy singName [] [Constr singName []] @@ -74,6 +78,7 @@ extraTopDeclsForContract includeSingleton (Contract cname _ts cdecls) = do tys' = tys ++ [fieldTy field] topdecls' = topdecls ++ extraTopDeclsForContractField cname field offset offset = foldr pair unit tys +extraTopDeclsForContract _ _ = [] extraTopDeclsForContractField :: ContractName -> NmField -> Ty -> [NmTopDecl] extraTopDeclsForContractField cname (Field fname fty _minit) offset = [selDecl, TInstDef sfInstance] diff --git a/src/Solcore/Desugarer/IndirectCall.hs b/src/Solcore/Desugarer/IndirectCall.hs index cdee4d10f..b853941f7 100644 --- a/src/Solcore/Desugarer/IndirectCall.hs +++ b/src/Solcore/Desugarer/IndirectCall.hs @@ -54,8 +54,8 @@ instance Desugar (TopDecl Name) where desugar (TMutualDef ms) = TMutualDef <$> desugar ms instance Desugar (Contract Name) where - desugar (Contract n vs ds) = - Contract n vs <$> desugar ds + desugar (ContractWithKind kind n vs ds) = + ContractWithKind kind n vs <$> desugar ds instance Desugar (FunDef Name) where desugar (FunDef p sig bdy) = diff --git a/src/Solcore/Pipeline/SolcorePipeline.hs b/src/Solcore/Pipeline/SolcorePipeline.hs index 85bb67464..949b44e91 100644 --- a/src/Solcore/Pipeline/SolcorePipeline.hs +++ b/src/Solcore/Pipeline/SolcorePipeline.hs @@ -979,8 +979,8 @@ moveData (CompUnit imps decls1) = step d ac = d : ac extractData :: Contract Name -> ([DataTy], Contract Name) -extractData (Contract n ts ds) = - (ds1, Contract n ts ds0) +extractData (ContractWithKind kind n ts ds) = + (ds1, ContractWithKind kind n ts ds0) where (ds1, ds0) = foldr step ([], []) ds step (CDataDecl dt) (dts, cs) = (dt : dts, cs) From 9e4ef6da500271d4f665900a0a0e7aef72d5c9ae Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Sat, 25 Jul 2026 11:28:48 +0900 Subject: [PATCH 32/33] Emit rich signature metadata in contract ABIs --- src/Solcore/Desugarer/ContractDispatch.hs | 51 +++++++---- test/ContractAbiTests.hs | 107 ++++++++++++++++++++++ 2 files changed, 142 insertions(+), 16 deletions(-) diff --git a/src/Solcore/Desugarer/ContractDispatch.hs b/src/Solcore/Desugarer/ContractDispatch.hs index cfba907a0..d404547d6 100644 --- a/src/Solcore/Desugarer/ContractDispatch.hs +++ b/src/Solcore/Desugarer/ContractDispatch.hs @@ -354,24 +354,24 @@ contractAbiEntries = mapMaybe entry . decls where entry (CConstrDecl con) = Just (AbiConstructor (map abiParam (constrParams con)) (stateMutability (constrPayable con))) - entry (CFunDecl (FunDef isPublic sig _)) - | sigName sig == fallbackName = Just (AbiFallback (stateMutability (sigPayable sig))) - | isPublic = + entry (CFunDecl (FunDef legacyVisibility sig _)) + | sigName sig == fallbackName = Just (AbiFallback (functionStateMutability sig)) + | externallyVisible legacyVisibility sig = Just $ AbiFunction (nameStr (sigName sig)) (map abiParam (sigParams sig)) - (abiOutputs (sigReturn sig)) - (stateMutability (sigPayable sig)) + (abiOutputs sig) + (functionStateMutability sig) | otherwise = Nothing - entry (CSignatureDecl isPublic sig) - | isPublic = + entry (CSignatureDecl legacyVisibility sig) + | externallyVisible legacyVisibility sig = Just $ AbiFunction (nameStr (sigName sig)) (map abiParam (sigParams sig)) - (abiOutputs (sigReturn sig)) - (stateMutability (sigPayable sig)) + (abiOutputs sig) + (functionStateMutability sig) | otherwise = Nothing entry _ = Nothing @@ -382,17 +382,36 @@ contractAbiEntries = mapMaybe entry . decls stateMutability :: Bool -> String stateMutability payable = if payable then "payable" else "nonpayable" +functionStateMutability :: Signature a -> String +functionStateMutability sig = + case sigMutability sig of + Just MutabilityPure -> "pure" + Just MutabilityView -> "view" + Just MutabilityPayable -> "payable" + Nothing -> stateMutability (sigPayable sig) + abiParam :: Param Name -> AbiParam abiParam (Typed _ pname t) = mkAbiParam (nameStr pname) t abiParam (Untyped _ pname) = AbiParam (nameStr pname) "" [] --- | A comma-separated return list @(a, b, c)@ desugars to nested pairs; the ABI --- represents it as one output per element. A unit return has no outputs. -abiOutputs :: Maybe Ty -> [AbiParam] -abiOutputs Nothing = [] -abiOutputs (Just t) - | t == unit = [] - | otherwise = map (mkAbiParam "") (flattenTuple t) +-- | Preserve the source return-item boundary recorded by the rich signature. +-- Legacy and compiler-generated signatures have no return items, so retain the +-- historical tuple-flattening fallback for them. +abiOutputs :: Signature Name -> [AbiParam] +abiOutputs sig = + case sigReturnItems sig of + items@(_ : _) -> map itemOutput items + [] -> maybe [] legacyOutputs (sigReturn sig) + where + outputNames = map (maybe "" nameStr) (sigReturnNames sig) ++ repeat "" + itemOutput item = + mkAbiParam + (maybe "" nameStr (signatureReturnItemName item)) + (signatureReturnItemType item) + legacyOutputs t + | t == unit = [] + | [_] <- sigReturnNames sig = zipWith mkAbiParam outputNames [t] + | otherwise = zipWith mkAbiParam outputNames (flattenTuple t) -- | Flatten a right-nested @pair@ chain into its element list. Because every -- comma-tuple desugars to right-nested pairs, a flat tuple @(a, b, c)@ and a diff --git a/test/ContractAbiTests.hs b/test/ContractAbiTests.hs index fc2e817a1..6814f623d 100644 --- a/test/ContractAbiTests.hs +++ b/test/ContractAbiTests.hs @@ -3,7 +3,10 @@ module ContractAbiTests where import Control.Exception (ErrorCall (..), evaluate, try) import Data.List (isInfixOf) import Solcore.Desugarer.ContractDispatch (contractAbiJson) +import Solcore.Diagnostics (compilerErrorText) +import Solcore.Frontend.Parser.SolcoreParser (parseCompUnit) import Solcore.Frontend.Syntax +import Solcore.Frontend.Syntax.NameResolution (nameResolution) import Solcore.Primitives.Primitives (word) import Test.Tasty import Test.Tasty.HUnit @@ -16,6 +19,69 @@ contractAbiTests = contractAbiJson onlyPublicContract @?= onlyPublicExpected, testCase "constructor, payable, word and tuple returns" $ contractAbiJson richContract @?= richExpected, + testCase "one named tuple return stays one ABI tuple output" $ do + contractDef <- + resolvedContractFromSource + "contract Reader { function read() external returns (result: (word, bool)) { result = (1, true); return; } }" + let abi = contractAbiJson contractDef + assertBool "tuple output keeps its name" ("\"name\": \"result\"" `isInfixOf` abi) + assertBool "tuple output is not flattened" ("\"type\": \"tuple\"" `isInfixOf` abi), + testCase "two scalar returns stay two ABI outputs" $ do + contractDef <- + resolvedContractFromSource + "contract Reader { function read() external returns (left: word, right: bool) { left = 1; right = true; return; } }" + let abi = contractAbiJson contractDef + assertBool "first scalar name survives" ("\"name\": \"left\"" `isInfixOf` abi) + assertBool "second scalar name survives" ("\"name\": \"right\"" `isInfixOf` abi) + assertBool "scalar outputs are not wrapped in a tuple ABI item" (not ("\"type\": \"tuple\"" `isInfixOf` abi)), + testCase "contract ABI preserves all four function mutability values" $ do + contractDef <- + resolvedContractFromSource $ + unlines + [ "contract Modes {", + " function compute() external pure returns (word) { return 0; }", + " function inspect() public view returns (word) { return 0; }", + " function deposit() external payable { return; }", + " function update() public { return; }", + "}" + ] + let abi = contractAbiJson contractDef + assertAbiFunctionMutability "compute" "pure" abi + assertAbiFunctionMutability "inspect" "view" abi + assertAbiFunctionMutability "deposit" "payable" abi + assertAbiFunctionMutability "update" "nonpayable" abi, + testCase "interface and library ABI preserve source mutability" $ do + interfaceDef <- + resolvedContractFromSource + "interface Reader { function read() external view returns (word); }" + libraryDef <- + resolvedContractFromSource + "library Math { function twice(x: word) public pure returns (word) { return x + x; } }" + assertAbiFunctionMutability "read" "view" (contractAbiJson interfaceDef) + assertAbiFunctionMutability "twice" "pure" (contractAbiJson libraryDef), + testCase "exact visibility controls ABI exposure when legacy flags disagree" $ do + let externalSig = + (sig "externalFn" [] (Just word) False) + { sigModifiers = [VisibilityModifier VisibilityExternal] + } + privateSig = + (sig "privateFn" [] (Just word) False) + { sigModifiers = [VisibilityModifier VisibilityPrivate] + } + abi = + contractAbiJson $ + Contract + (Name "Visibility") + [] + [ fun False externalSig, + fun True privateSig + ] + assertBool + "external metadata exposes a function even if the legacy flag is false" + ("\"name\": \"externalFn\"" `isInfixOf` abi) + assertBool + "private metadata hides a function even if the legacy flag is true" + (not ("\"name\": \"privateFn\"" `isInfixOf` abi)), testCase "parameterized parameter type fails loudly" $ do -- A public function whose parameter is a parameterized type -- (e.g. `mapping(word, word)`) has no ABI spelling. Dropping the type @@ -31,6 +97,47 @@ contractAbiTests = assertFailure "expected ABI emission to fail for a parameterized parameter type" ] +resolvedContractFromSource :: String -> IO (Contract Name) +resolvedContractFromSource source = do + parsedResult <- parseCompUnit source + parsed <- + case parsedResult of + Left err -> assertFailure ("unexpected parse failure:\n" <> err) + Right compUnit -> pure compUnit + resolvedResult <- nameResolution parsed + case resolvedResult of + Left err -> + assertFailure + ("unexpected name-resolution failure:\n" <> compilerErrorText err) + Right (CompUnit _ topDecls) -> + case [contractDef | TContr contractDef <- topDecls] of + [contractDef] -> pure contractDef + _ -> assertFailure ("unexpected resolved shape: " <> show topDecls) + +assertAbiFunctionMutability :: String -> String -> String -> Assertion +assertAbiFunctionMutability functionName expectedMutability abi = + case dropWhile (not . isInfixOf nameLine) (lines abi) of + [] -> + assertFailure + ("ABI did not contain function " <> show functionName <> ":\n" <> abi) + functionTail -> + let functionEntry = + takeWhile + (not . isInfixOf "\"type\": \"function\"") + functionTail + in assertBool + ( "ABI function " + <> show functionName + <> " did not have stateMutability " + <> show expectedMutability + <> ":\n" + <> unlines functionEntry + ) + (any (isInfixOf mutabilityLine) functionEntry) + where + nameLine = "\"name\": \"" <> functionName <> "\"" + mutabilityLine = "\"stateMutability\": \"" <> expectedMutability <> "\"" + -- Helpers for building sample contracts tyCon :: String -> Ty From dc2527bbea8db74266d707abea5268e5529f6579 Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Sat, 25 Jul 2026 11:29:39 +0900 Subject: [PATCH 33/33] Align omitted-return syntax expectations --- test/Cases.hs | 2 +- test/DiagnosticCliTests.hs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Cases.hs b/test/Cases.hs index 22ddd9f73..913a8ffde 100644 --- a/test/Cases.hs +++ b/test/Cases.hs @@ -593,7 +593,7 @@ cases = "multi-stmt-var-leaf.solc" caseFolder, runTestForFile "ltimp.solc" caseFolder, - runTestExpectingFailure "class-return-type-miss.solc" caseFolder, + runTestForFile "class-return-type-miss.solc" caseFolder, runTestExpectingFailure "catenable-err.solc" caseFolder, runTestForFile "pars.solc" caseFolder, runTestForFile "bug-rep-name-capture.solc" caseFolder, diff --git a/test/DiagnosticCliTests.hs b/test/DiagnosticCliTests.hs index 593ce2d6f..b6f74d7ad 100644 --- a/test/DiagnosticCliTests.hs +++ b/test/DiagnosticCliTests.hs @@ -76,7 +76,7 @@ diagnosticCliTests = " |", "1 | function foo(value) {", " | ^^^ incomplete signature", - "note: signature: function foo(value) returns (())", + "note: signature: function foo(value)", "note: module typecheck failed for /test/diagnostics/missing-signature.solc", "help: annotate every parameter (name: Type); omit returns only for a unit-returning function" ],