diff --git a/src/Solcore/Frontend/Syntax/ElabTree.hs b/src/Solcore/Frontend/Syntax/ElabTree.hs index a30a6637..81b271be 100644 --- a/src/Solcore/Frontend/Syntax/ElabTree.hs +++ b/src/Solcore/Frontend/Syntax/ElabTree.hs @@ -16,7 +16,7 @@ import Text.Pretty.Simple import Solcore.Frontend.Pretty.SolcorePretty import Solcore.Frontend.Syntax.Contract hiding (contracts, decls) import Solcore.Frontend.Syntax.Name -import Solcore.Frontend.Syntax.Stmt +import Solcore.Frontend.Syntax.Stmt hiding (paramName) import qualified Solcore.Frontend.Syntax.SyntaxTree as S import Solcore.Frontend.Syntax.Ty @@ -375,12 +375,16 @@ instance Elab S.Field where where env = mempty +paramName :: S.Param -> Name +paramName (S.Typed n _) = n +paramName (S.Untyped n) = n + instance Elab S.FunDef where type Res S.FunDef = FunDef Name elab (S.FunDef sig bd) = do - let vs = names (S.sigVars sig) + let vs = map paramName (S.sigParams sig) pushVarsInScope vs sig' <- elab sig bd' <- elab bd @@ -459,7 +463,8 @@ instance Elab S.Exp where me' <- elab me isF <- isField n isCon <- isDefinedConstr n - if isF then -- TODO: check if not shadowed + isVar <- isDefinedVar n + if isF && not isVar then do pure $ FieldAccess me' n else if isCon && isNothing me then pure (Con n []) else pure $ Var n diff --git a/src/Solcore/Frontend/Syntax/Ty.hs b/src/Solcore/Frontend/Syntax/Ty.hs index 7e7d69e8..9a66f965 100644 --- a/src/Solcore/Frontend/Syntax/Ty.hs +++ b/src/Solcore/Frontend/Syntax/Ty.hs @@ -30,7 +30,7 @@ newtype MetaTv deriving (Eq, Ord, Show, Data, Typeable) tyconNames :: Ty -> [Name] -tyconNames (TyCon n ts) +tyconNames (TyCon n ts) = nub (n : concatMap tyconNames ts) tyconNames _ = [] @@ -135,6 +135,7 @@ class HasMeasure a where instance HasMeasure Ty where measure (TyVar _) = 1 + measure (Meta _) = 1 measure (TyCon _ ts) = 1 + sum (map measure ts) instance HasMeasure Pred where diff --git a/src/Solcore/Frontend/TypeInference/TcContract.hs b/src/Solcore/Frontend/TypeInference/TcContract.hs index 21fd688e..61724460 100644 --- a/src/Solcore/Frontend/TypeInference/TcContract.hs +++ b/src/Solcore/Frontend/TypeInference/TcContract.hs @@ -166,10 +166,10 @@ tcDecl (CFunDecl d) case d' of [] -> throwError "Impossible! Empty function binding!" (x : _) -> pure (CFunDecl x) -tcDecl (CMutualDecl ds) +tcDecl d@(CMutualDecl ds) = do let f (CFunDecl fd) = fd - ds' <- tcBindGroup (map f ds) + ds' <- tcBindGroup (map f ds) `wrapError` d pure (CMutualDecl (map CFunDecl ds')) tcDecl (CConstrDecl cd) = CConstrDecl <$> tcConstructor cd tcDecl (CDataDecl d) = CDataDecl <$> tcDataDecl d @@ -290,7 +290,7 @@ checkClass icls@(Class bvs ps n vs v sigs) let p = InCls n (TyVar v) (TyVar <$> vs) ms' = map sigName sigs bound <- askBoundVariableCondition n - unless bound (checkBoundVariable ps (v:vs) `wrapError` icls) + unless bound (checkBoundVariable ps (map TyVar (v:vs)) `wrapError` icls) addClassInfo n (length vs) ms' ps p mapM_ (checkSignature p) sigs where diff --git a/src/Solcore/Frontend/TypeInference/TcMonad.hs b/src/Solcore/Frontend/TypeInference/TcMonad.hs index effb9ed0..a8fabeed 100644 --- a/src/Solcore/Frontend/TypeInference/TcMonad.hs +++ b/src/Solcore/Frontend/TypeInference/TcMonad.hs @@ -76,7 +76,7 @@ addUniqueType :: Name -> DataTy -> TcM () addUniqueType n dt = do modify (\ ctx -> ctx{ uniqueTypes = Map.insert n dt (uniqueTypes ctx)}) - checkDataType dt + checkDataType dt lookupUniqueTy :: Name -> TcM (Maybe DataTy) lookupUniqueTy n @@ -85,7 +85,9 @@ lookupUniqueTy n isUniqueTyName :: Name -> TcM Bool isUniqueTyName n = do uenv <- gets uniqueTypes - pure $ any (\ d -> dataName d == n) (Map.elems uenv) + gend <- gets generated + let gd' = [d | (TDataDef d) <- gend] + pure $ any (\ d -> dataName d == n) ((Map.elems uenv) ++ gd') typeInfoFor :: DataTy -> TypeInfo typeInfoFor (DataTy n vs cons) @@ -163,10 +165,10 @@ isDirectCall n checkDataType :: DataTy -> TcM () checkDataType d@(DataTy n vs constrs) = do - -- check if the type is already defined. - r <- maybeAskTypeInfo n - unless (isNothing r) $ - typeAlreadyDefinedError d n + -- check if the type is already defined. + r <- maybeAskTypeInfo n + unless (isNothing r) $ + typeAlreadyDefinedError d n let vals' = map (\ (n, ty) -> (n, Forall (bv ty) ([] :=> ty))) vals mapM_ (uncurry extEnv) vals' modifyTypeInfo n ti @@ -358,7 +360,9 @@ validConstr n ti = n `elem` constrNames ti || isPair n extEnv :: Name -> Scheme -> TcM () extEnv n t - = modify (\ sig -> sig {ctx = Map.insert n t (ctx sig)}) + = do + modify (\ sig -> sig{ uniqueTypes = Map.delete n (uniqueTypes sig)}) + modify (\ sig -> sig {ctx = Map.insert n t (ctx sig)}) withExtEnv :: Name -> Scheme -> TcM a -> TcM a withExtEnv n s m @@ -644,28 +648,28 @@ undefinedClass :: Name -> TcM a undefinedClass n = throwError $ unlines ["Undefined class:", pretty n] -typeAlreadyDefinedError :: DataTy -> Name -> TcM a -typeAlreadyDefinedError d n - = do - -- get type info +typeAlreadyDefinedError :: DataTy -> Name -> TcM a +typeAlreadyDefinedError d n + = do + -- get type info di <- askTypeInfo n - d' <- dataTyFromInfo n di `wrapError` d + d' <- dataTyFromInfo n di `wrapError` d throwError $ unlines ["Duplicated type definition for " ++ pretty n ++ ":" , pretty d , "and" , pretty d'] -dataTyFromInfo :: Name -> TypeInfo -> TcM DataTy -dataTyFromInfo n (TypeInfo ar cs _) - = do - -- getting data constructor types +dataTyFromInfo :: Name -> TypeInfo -> TcM DataTy +dataTyFromInfo n (TypeInfo ar cs _) + = do + -- getting data constructor types (constrs, vs) <- unzip <$> mapM constrsFromEnv cs pure (DataTy n (concat vs) constrs) constrsFromEnv :: Name -> TcM (Constr, [Tyvar]) -constrsFromEnv n - = do - (Forall vs (_ :=> ty)) <- askEnv n +constrsFromEnv n + = do + (Forall vs (_ :=> ty)) <- askEnv n let (ts, _) = splitTy ty pure (Constr n ts, vs) diff --git a/src/Solcore/Frontend/TypeInference/TcStmt.hs b/src/Solcore/Frontend/TypeInference/TcStmt.hs index b1129f4a..43551045 100644 --- a/src/Solcore/Frontend/TypeInference/TcStmt.hs +++ b/src/Solcore/Frontend/TypeInference/TcStmt.hs @@ -494,7 +494,7 @@ annotatedScheme vs' sig@(Signature vs ps n args rt) tcFunDef :: Bool -> [Tyvar] -> [Pred] -> FunDef Name -> TcM (FunDef Id, Scheme) tcFunDef incl vs' qs d@(FunDef sig@(Signature vs ps n args rt) bd) | hasAnn sig = do - info ["\n# tcFunDef ", pretty d] + info ["\n# tcFunDef ", pretty sig] let vars = vs `union` vs' -- check if all variables are bound in signature. when (any (\ v -> v `notElem` vars) (bv sig)) $ do @@ -541,6 +541,7 @@ tcFunDef incl vs' qs d@(FunDef sig@(Signature vs ps n args rt) bd) -- elaborating function body let ann' = if changeTy then inf else ann fdt <- elabFunDef vs' sig1 bd1' inf ann' `wrapError` d + info ["Finishing typing for:", pretty (funSignature fdt)] withCurrentSubst (fdt, ann') | otherwise = tiFunDef d @@ -559,6 +560,7 @@ elabFunDef vs sig bdy inf@(Forall _ (_ :=> tinf)) ann@(Forall _ (_ :=> tann)) tann' = everywhere (mkT toMeta) tann s <- unify tinf' tann' sig2 <- elabSignature vs sig ann + info ["Elaborated signature:", pretty sig2] let fd2 = everywhere (mkT (apply @Ty s)) (FunDef sig2 bdy) pure (everywhere (mkT gen) fd2) @@ -827,7 +829,7 @@ checkInstance idef@(Instance d vs ctx n ts t funs) unless patterson (checkMeasure ctx ipred `wrapError` idef) -- checking bound variable condition bound <- askBoundVariableCondition n - unless bound (checkBoundVariable ctx (bv (t : ts)) `wrapError` idef) + unless bound (checkBoundVariable ctx (t : ts) `wrapError` idef) -- checking instance methods mapM_ (checkMethod ipred) funs `wrapError` idef let ninst = anfInstance $ ctx :=> InCls n t ts @@ -847,9 +849,9 @@ isTyVar _ = False -- bound variable check -checkBoundVariable :: [Pred] -> [Tyvar] -> TcM () -checkBoundVariable ps vs - = unless (all (`elem` vs) (bv ps)) $ do +checkBoundVariable :: [Pred] -> [Ty] -> TcM () +checkBoundVariable ps ts + = unless (all (`elem` (bv ts)) (bv ps)) $ do throwError "Bounded variable condition fails!" diff --git a/src/Solcore/Pipeline/SolcorePipeline.hs b/src/Solcore/Pipeline/SolcorePipeline.hs index 4c124c3d..3fd6c851 100644 --- a/src/Solcore/Pipeline/SolcorePipeline.hs +++ b/src/Solcore/Pipeline/SolcorePipeline.hs @@ -116,12 +116,12 @@ compile opts = runExceptT $ do putStrLn "> Pattern wildcard desugaring:" putStrLn $ pretty noWild - -- Eliminate function type arguments + -- Eliminate function type arguments - let noFun = if noDesugarCalls then noWild else replaceFunParam noWild - liftIO $ when verbose $ do - putStrLn "> Eliminating argments with function types" - putStrLn $ pretty noFun + let noFun = if noDesugarCalls then noWild else replaceFunParam noWild + liftIO $ when verbose $ do + putStrLn "> Eliminating arguments with function types" + putStrLn $ pretty noFun -- Type inference (typed, typeEnv) <- ExceptT $ timeItNamed "Typecheck " diff --git a/test/Cases.hs b/test/Cases.hs index 29921d1a..21ad8983 100644 --- a/test/Cases.hs +++ b/test/Cases.hs @@ -232,6 +232,8 @@ cases = , runTestExpectingFailure "xref.solc" caseFolder , runTestForFile "yul-function-typing.solc" caseFolder , runTestForFile "yul-return.solc" caseFolder + , runTestForFile "field-var.solc" caseFolder + , runTestForFile "local-variable-mess.solc" caseFolder ] where caseFolder = "./test/examples/cases" diff --git a/test/examples/cases/app.solc b/test/examples/cases/app.solc index 6ec82d63..199d2def 100644 --- a/test/examples/cases/app.solc +++ b/test/examples/cases/app.solc @@ -1,3 +1,4 @@ +import dispatch; function app () { return lam (f, x) {return f(x);}; } diff --git a/test/examples/cases/dispatch.solc b/test/examples/cases/dispatch.solc deleted file mode 100644 index e504f13c..00000000 --- a/test/examples/cases/dispatch.solc +++ /dev/null @@ -1,272 +0,0 @@ -pragma no-patterson-condition RunDispatch, MethodLevelCallvalueCheck, TopLevelCallvalueCheck; - -// --- Preliminaries --- - -data Bool = True | False; -data Proxy(a) = Proxy; - -// --- Core Data Types --- - -// A contract contains a tuple of methods and a single fallback -// TODO: implement receive() -data Contract(methods, fallback) = Contract(methods,fallback); - -// 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); - -// Contains the implementation for the fallback (fn) as well as it's type signature -data Fallback(args, rets, fn) = 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; -} - -// 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)); - } -} - -// --- Method Execution --- - -// Describes how to execute a given method / fallback -forall ty callvalueCheckStatus . class ty:ExecMethod { - function exec(x: ty, pstatus : Proxy(callvalueCheckStatus)) -> (); -} - -// 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) => - // check callvalue - MethodLevelCallvalueCheck.checkCallvalue(Proxy : Proxy(Method(name,args,rets,fn)), pstatus); - - // check we have enough calldata for the head of args - // abi decode args from calldata - // call fn with args - // abi encode rets to memory - // returndata copy encoded returns - // evm 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) => - // check callvalue - MethodLevelCallvalueCheck.checkCallvalue(Proxy : Proxy(Fallback(args,rets,fn)), pstatus); - - // check we have enough calldata for the head of args - // abi decode args from calldata - // call fn with args - // abi encode rets to memory - // returndata copy encoded returns - // evm 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)) -> (); -} - -// 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)) { - | True => ExecMethod.exec(method, pstatus); - | 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)) { - | True => ExecMethod.exec(method_n); - | False => match selector_matches(Proxy : Proxy(m)) { - | True => ExecMethod.exec(method_m, pstatus); - | 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)) { - | True => ExecMethod.exec(method_n, pstatus); - | 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 { - let hash = Selector.hash(prx); - let res : word; - assembly { - let sel := shr(224, calldataload(0)); - res := eq(sel, hash); - } - match res { - | 0 => return False; - | _ => return 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 {} - - -data CallvalueChecked; - -data CallvalueUnchecked; -forall ty . class ty:MethodsMustCheckCalldata {} -instance CallvalueUnchecked: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); -} - -forall methods . default instance methods:TopLevelCallvalueCheck(CallvalueUnchecked) { - function checkCallvalue(prx : Proxy(methods)) -> Proxy(CallvalueUnchecked) { return Proxy : Proxy(CallvalueUnchecked); } -} - -forall methods . methods:AllNonPayable => instance methods:TopLevelCallvalueCheck(CallvalueChecked) { - function checkCallvalue(prx : Proxy(methods)) -> Proxy(CallvalueChecked) { - assembly { - if gt(callvalue(), 0) { - mstore(0,0x2) - revert(0,32); - } - } - return Proxy : Proxy(CallvalueChecked); - } -} - -// 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)) -> (); -} - -forall method status . default instance method:MethodLevelCallvalueCheck { - function checkCallvalue(pty : Proxy(method), pstatus : Proxy(status)) -> () { } -} - -forall method status . method:NonPayable, status:MethodsMustCheckCalldata => instance method:MethodLevelCallvalueCheck { - function checkCallvalue(pty : Proxy(method), pstatus : Proxy(status)) -> (){ - assembly { - if gt(callvalue(), 0) { - mstore(0, 0x1); - revert(0, 32); - } - } - } -} - -// --- Contract Execution --- - -// Describes how to execute a given contract -forall c . class c:RunContract { - function exec(v : c) -> (); -} - -// 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 fallback . methods:RunDispatch, fallback:ExecMethod => instance Contract(methods, fallback):RunContract { - function exec(c : Contract(methods, fallback)) -> () { - match c { - | 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((fallback, methods))); - - // check that we have at least 4 bytes of calldata - let haveSelector : word; - assembly { - haveSelector := lt(3, calldatasize()); - } - - match haveSelector { - | 0 => assembly { revert(0,0); } - | _ => - // dispatch to method based on selector - RunDispatch.go(ms, callvalueChecked); - // run fallback if no methods matched - ExecMethod.exec(fb); - } - } - } -} - -// --- Manually Desugared Example --- - -// compiler generated - -function revert_handler() -> () { - assembly { revert(0,0) } -} - -data C_Add2_Selector = C_Add2_Selector; - -instance C_Add2_Selector:Selector { - function hash(prx: Proxy(C_Add2_Selector)) -> word { - // This would be keccak256("add2(uint256,uint256)") >> 224 - // Compiler computes this at compile time - return 0x29fcda33; // placeholder value - } -} - -// transform - -contract C { - function add2(x : word, y : word) -> word { - let ret : word; - assembly { ret := add(x,y) } - return ret; - } - - function main() -> word { - let c = Contract( - Method(C_Add2_Selector, Proxy : Proxy((word,word)), Proxy : Proxy(word), add2), - Fallback(Proxy : Proxy(()),Proxy : Proxy(()),revert_handler) - ); - - RunContract.exec(c); - return 0; - } -} diff --git a/test/examples/cases/field-var.solc b/test/examples/cases/field-var.solc new file mode 100644 index 00000000..649722ce --- /dev/null +++ b/test/examples/cases/field-var.solc @@ -0,0 +1,18 @@ +import dispatch; +contract Foo { + start : word; + + function g(start:word) -> word { + // let start : word = 0; + return Sub.sub(9, start); + } + + function f() -> word { + return start; + } + + function h(start:word) -> word { + let start : word = 0; + return Sub.sub(9, start); + } +} diff --git a/test/examples/cases/local-variable-mess.solc b/test/examples/cases/local-variable-mess.solc new file mode 100644 index 00000000..65ba76ab --- /dev/null +++ b/test/examples/cases/local-variable-mess.solc @@ -0,0 +1,25 @@ +forall t . class t:Sub { + function sub(l: t, r: t) -> t; +} + +instance word:Sub { + function sub(l: word, r: word) -> word { + let rw : word; + assembly { + rw := sub(l,r); + } + return rw; + } +} + +function start() -> () {} + +function g(start : word) -> word { + // let start : word = 0; + return Sub.sub(9, start); +} + +function g1(start : word) -> word { + let start : word = 0; + return Sub.sub(9, start); +} diff --git a/test/examples/spec/128minierc20.solc b/test/examples/spec/128minierc20.solc index 9e17dbd9..718b7da2 100644 --- a/test/examples/spec/128minierc20.solc +++ b/test/examples/spec/128minierc20.solc @@ -1,4 +1,4 @@ -import std; +import dispatch; function caller() -> address { let res: word;