From caf547115a77fd65570d35a3f069a11c4933a7c0 Mon Sep 17 00:00:00 2001 From: Kristian Larsson Date: Wed, 19 Aug 2026 14:45:42 +0200 Subject: [PATCH] Scan __init__ try blocks in runtime order scanSelfAssigns decides which self attributes a constructor is guaranteed to have assigned before self can escape, and stops at the first statement that reads self. For a try statement it scanned the finally block first and treated its assignments as already made while scanning the try body. finally runs last, so this accepted a constructor that reads an attribute in try which is only assigned in finally: def __init__(self): try: self.first = self.second + 1 finally: self.second = 2 That compiled, and at runtime the read of self.second saw an uninitialized field. The scan now follows execution order: the try body, then each handler and the else block as alternative continuations, then finally. Along the way it keeps the number of leading statements that lie before the first self escape, so the same walk can report where the declarative prefix of the constructor ends. The example above is now rejected with "Attribute 'first' is not initialized", and is the first snapshot test for that check. --- .../typeerrors/init_try_finally_order.act | 8 + .../typeerrors/init_try_finally_order.golden | 20 ++ compiler/lib/src/Acton/Types.hs | 296 +++++++++++++----- 3 files changed, 249 insertions(+), 75 deletions(-) create mode 100644 compiler/acton/test/typeerrors/init_try_finally_order.act create mode 100644 compiler/acton/test/typeerrors/init_try_finally_order.golden diff --git a/compiler/acton/test/typeerrors/init_try_finally_order.act b/compiler/acton/test/typeerrors/init_try_finally_order.act new file mode 100644 index 000000000..47f742274 --- /dev/null +++ b/compiler/acton/test/typeerrors/init_try_finally_order.act @@ -0,0 +1,8 @@ +class Holder(object): + first: int + second: int + def __init__(self): + try: + self.first = self.second + 1 + finally: + self.second = 2 diff --git a/compiler/acton/test/typeerrors/init_try_finally_order.golden b/compiler/acton/test/typeerrors/init_try_finally_order.golden new file mode 100644 index 000000000..9e9782cc7 --- /dev/null +++ b/compiler/acton/test/typeerrors/init_try_finally_order.golden @@ -0,0 +1,20 @@ +Building file test/typeerrors/init_try_finally_order.act using temporary scratch directory + init_try_finally_order Parse done 0.000 s +[error Type error]: Attribute 'first' is not initialized in Holder.__init__ + +--> init_try_finally_order.act@4:5-8:1 + | + 1 | class Holder(object): + : ^-------------------- + : `- In class Holder + 2 | first: int + : ^---- + : `- Attribute 'first' is defined here + : + 4 | +> def __init__(self): + 5 | | try: + 6 | | self.first = self.second + 1 + 7 | | finally: + 8 | |> self.second = 2 + : | + : `- Attribute 'first' is not initialized in __init__ +-----+ diff --git a/compiler/lib/src/Acton/Types.hs b/compiler/lib/src/Acton/Types.hs index 300f97f91..d86879823 100644 --- a/compiler/lib/src/Acton/Types.hs +++ b/compiler/lib/src/Acton/Types.hs @@ -1321,6 +1321,27 @@ matchActorAssumption env n0 p k te = do --traceM ("## matchActorAssumption findInitMethod :: Suite -> Maybe (Name, Suite, SrcLoc) findInitMethod b = listToMaybe [ (x, dbody d, loc d) | Decl _ ds <- b, d <- ds, dname d == initKW, Just x <- [selfPar d] ] +-- Type reconstruction may add one or more explicit type applications around +-- a selected method. Constructor analysis cares about the underlying direct +-- receiver, not those inferred arguments. +directDotCall :: Expr -> Maybe (QName, Name) +directDotCall (Call _ f _ _) = directDot (stripTypeApps f) + where + directDot (Dot _ e n) + | Var _ qn <- stripTypeApps e = Just (qn, n) + directDot _ = Nothing +directDotCall _ = Nothing + +isDotCall :: Expr -> Bool +isDotCall (Call _ f _ _) = case stripTypeApps f of + Dot{} -> True + _ -> False +isDotCall _ = False + +stripTypeApps :: Expr -> Expr +stripTypeApps (TApp _ f _) = stripTypeApps f +stripTypeApps f = f + -- Scan __init__ for "self.x = ..." to find which attributes are definitely -- assigned before any references to `self` escape externally. We allow most -- statements in the constructor as long as `self` does not escape although @@ -1331,8 +1352,19 @@ findInitMethod b = listToMaybe [ (x, dbody d, loc d) | De -- attributes since we cannot statically determine if the loop executes. The -- loop is scanned to ensure `self` does not escape. scanSelfAssigns :: Env -> Name -> Suite -> Suite -> [Name] -scanSelfAssigns env self classBody stmts = scanSuite [] stmts +scanSelfAssigns env self classBody stmts = attrs where + (attrs, _, _) = scanSuite [] stmts + + continue assigns crossesBoundary seen rest + | crossesBoundary = (assigns, 0, True) + | otherwise = let (assigns', count', restCrossesBoundary) = scanSuite seen rest + in (assigns ++ assigns', count' + 1, restCrossesBoundary) + + scanAttrs (assigns, _, _) = assigns + scanCrossesBoundary (_, _, crossesBoundary) + = crossesBoundary + -- Check if a method in the class has NotImplemented body isNotImplMethod :: Name -> Bool isNotImplMethod methodName = case [ d | Decl _ ds <- classBody, d@Def{dname=n} <- ds, n == methodName ] of @@ -1358,27 +1390,29 @@ scanSelfAssigns env self classBody stmts = scanSuite [] stmts all (\(Handler _ hbody) -> branchExitsEarly hbody) handlers && (null elseBranch || branchExitsEarly elseBranch) _ -> False - scanSuite seen [] = [] + scanSuite seen [] = ([], 0, False) -- Handle assignments scanSuite seen (MutAssign _ (Dot _ (Var _ qn) n) e : rest) | noq qn == self = if checkNoSelfReference self seen e - then n : scanSuite (n:seen) rest - else [] -- Stop at self reference - scanSuite seen (MutAssign _ _ _ : rest) - = -- Continue: local assignment is OK - scanSuite seen rest + then continue [n] False (n:seen) rest + else ([], 0, True) -- Stop at self reference + scanSuite seen (MutAssign _ target e : rest) + | checkNoSelfReference self seen target && checkNoSelfReference self seen e + = continue [] False seen rest + | otherwise = ([], 0, True) scanSuite seen (Assign _ _ e : rest) = -- Continue past local assignments, unless RHS contains self reference if checkNoSelfReference self seen e - then scanSuite seen rest -- Continue past assignment - else [] -- Stop at self reference - scanSuite seen (AugAssign _ (Dot _ (Var _ qn) n) _ _ : rest) - | noq qn == self && n `elem` seen -- OK: augmenting already-initialized attribute - = scanSuite seen rest - | noq qn == self = [] -- STOP: can't augment uninitialized attribute - scanSuite seen (AugAssign _ _ _ _ : rest) - = -- Continue: local augmented assignment is OK - scanSuite seen rest + then continue [] False seen rest -- Continue past assignment + else ([], 0, True) -- Stop at self reference + scanSuite seen (AugAssign _ (Dot _ (Var _ qn) n) _ e : rest) + | noq qn == self && n `elem` seen && checkNoSelfReference self seen e + = continue [] False seen rest + | noq qn == self = ([], 0, True) -- STOP: can't augment uninitialized attribute + scanSuite seen (AugAssign _ target _ e : rest) + | checkNoSelfReference self seen target && checkNoSelfReference self seen e + = continue [] False seen rest + | otherwise = ([], 0, True) -- Handle if/elif/else statements. Count assignments that happen in all -- branches as unconditional. Note how we must have an else branch in order -- to consider it exhaustive. Each branch either: @@ -1407,12 +1441,17 @@ scanSelfAssigns env self classBody stmts = scanSuite [] stmts elseResult = (scanSuite seen elseBranch, branchExitsEarly elseBranch) -- Branches that complete normally must be considered - normalBranches = [assigns | (assigns, exits) <- branchResults, not exits] + normalBranches = [scanAttrs result | (result, exits) <- branchResults, not exits] -- Else branch if it completes normally normalElse = case elseResult of - (assigns, False) -> [assigns] -- Else completes normally + (result, False) -> [scanAttrs result] -- Else completes normally (_, True) -> [] -- Else exits early + conditionCrossesBoundary = any (\(Branch cond _) -> + not $ checkNoSelfReference self seen cond) branches + crossesBoundary = conditionCrossesBoundary || + any (scanCrossesBoundary . fst) (elseResult : branchResults) + -- All normal branches that do not exit early allNormalBranches = normalBranches ++ normalElse @@ -1422,23 +1461,30 @@ scanSelfAssigns env self classBody stmts = scanSuite [] stmts True -> case allNormalBranches of [] -> [] -- All branches exit: no object returned branches -> foldl1 intersect branches -- Require intersection - in newAssigns ++ scanSuite (newAssigns ++ seen) rest + assignsBeforeBoundary = if conditionCrossesBoundary then [] else newAssigns + in continue assignsBeforeBoundary crossesBoundary + (assignsBeforeBoundary ++ seen) rest -- Only continue past simple statements - scanSuite seen (Pass _ : rest) = scanSuite seen rest + scanSuite seen (Pass _ : rest) = continue [] False seen rest -- Parent init calls are special - they initialize parent attributes - scanSuite seen (Expr _ (Call _ (Dot _ (Var _ c) n) _ _) : rest) - | isClass env c, n == initKW = scanSuite seen rest + scanSuite seen (Expr _ e : rest) + | Just (c, n) <- directDotCall e + , isClass env c, n == initKW = continue [] False seen rest -- Allow calling self methods that have NotImplemented body (C implementations) - scanSuite seen (Expr _ (Call _ (Dot _ (Var _ (NoQ x)) methodName) _ _) : rest) - | x == self && isNotImplMethod methodName - = scanSuite seen rest -- Continue: NotImplemented method on self - -- Stop at other self method calls - scanSuite seen (Expr _ (Call _ (Dot _ _ _) _ _) : _) - = [] -- STOP: method call + scanSuite seen (Expr _ e : rest) + | Just (NoQ x, methodName) <- directDotCall e + , x == self && isNotImplMethod methodName + = continue [] False seen rest -- Continue: NotImplemented method on self + -- Stop at method calls that expose self; calls on an initialized attribute + -- are safe because they pass only that attribute value. + scanSuite seen (Expr _ e : rest) + | isDotCall e && checkNoSelfReference self seen e + = continue [] False seen rest + | isDotCall e = ([], 0, True) -- Allow expressions without references to `self` scanSuite seen (Expr _ e : rest) | checkNoSelfReference self seen e - = scanSuite seen rest + = continue [] False seen rest -- Handle try/except/else/finally -- -- The possible execution paths are: @@ -1471,29 +1517,37 @@ scanSelfAssigns env self classBody stmts = scanSuite [] stmts -- it doesn't contribute to the intersection (like if/else branches that exit). -- Finally assignments always count since finally always executes. scanSuite seen (Try _ tryBody handlers elseBranch finallyBlock : rest) - = let -- Finally always executes - finallyAssigns = scanSuite seen finallyBlock - seenAfterFinally = finallyAssigns ++ seen - - -- Scan all code paths - tryAssigns = scanSuite seenAfterFinally tryBody - elseAssigns = scanSuite seenAfterFinally elseBranch - handlerAssigns = map (\(Handler _ hbody) -> - scanSuite seenAfterFinally hbody) handlers + = let -- Scan paths in runtime order. The else block sees + -- assignments made by a completed try; handlers cannot + -- assume where the exception occurred. + tryResult = scanSuite seen tryBody + tryAssigns = scanAttrs tryResult + elseResult = scanSuite (tryAssigns ++ seen) elseBranch + handlerResults = map (\(Handler _ hbody) -> + scanSuite seen hbody) handlers + elseAssigns = scanAttrs elseResult + handlerAssigns = map scanAttrs handlerResults -- Check which paths exit early tryExits = branchExitsEarly tryBody elseExits = branchExitsEarly elseBranch handlerExits = map (\(Handler _ hbody) -> branchExitsEarly hbody) handlers + pathResults = tryResult : handlerResults ++ + [elseResult | not tryExits] + pathCrossesBoundary = any scanCrossesBoundary pathResults -- Build list of paths that complete normally: -- 1. try+else path (if try doesn't exit) -- 2. each handler that doesn't exit + tryCrossesBoundary = scanCrossesBoundary tryResult tryElsePath = if not tryExits - then [tryAssigns ++ if elseExits then [] else elseAssigns] + then [tryAssigns ++ if tryCrossesBoundary || elseExits + then [] else elseAssigns] else [] - handlerPaths = [assigns | (assigns, exits) <- zip handlerAssigns handlerExits, not exits] + handlerPaths = [ if tryCrossesBoundary then [] else assigns + | (assigns, exits) <- zip handlerAssigns handlerExits + , not exits ] normalPaths = tryElsePath ++ handlerPaths -- Intersect all normal paths (or empty if all exit) @@ -1501,41 +1555,60 @@ scanSelfAssigns env self classBody stmts = scanSuite [] stmts [] -> [] paths -> foldl1 intersect paths - in finallyAssigns ++ guaranteedFromPaths ++ - scanSuite (finallyAssigns ++ guaranteedFromPaths ++ seen) rest + -- Finally runs after every path, so it may use only + -- attributes guaranteed by the preceding paths. If a + -- path already crossed the boundary, finally is too late + -- to contribute initialized attributes. + finallyResult = scanSuite seen finallyBlock + finallyAssigns = scanAttrs finallyResult + finallyCrossesBoundary = scanCrossesBoundary finallyResult + assignsBeforeBoundary + | pathCrossesBoundary = guaranteedFromPaths + | finallyCrossesBoundary = finallyAssigns + | otherwise = finallyAssigns ++ guaranteedFromPaths + + in continue assignsBeforeBoundary + (pathCrossesBoundary || finallyCrossesBoundary) + (assignsBeforeBoundary ++ seen) rest -- Handle loops - skip over them if they don't leak self references scanSuite seen (While _ cond body elseBranch : rest) - | checkNoSelfReference self seen cond && - checkNoSelfReferenceInSuite self seen body && - checkNoSelfReferenceInSuite self seen elseBranch - = scanSuite seen rest -- Continue past safe loop - | otherwise = [] -- STOP: loop references self + | checkNoSelfReference self seen cond && not crossesBoundary + = continue [] False seen rest -- Continue past safe loop + | otherwise = ([], 0, True) -- STOP: loop references self + where crossesBoundary = any scanCrossesBoundary + [scanSuite seen body, scanSuite seen elseBranch] scanSuite seen (For _ pat expr body elseBranch : rest) - | checkNoSelfReference self seen expr && - checkNoSelfReferenceInSuite self seen body && - checkNoSelfReferenceInSuite self seen elseBranch - = scanSuite seen rest -- Continue past safe loop - | otherwise = [] -- STOP: loop references self - scanSuite _ (With _ _ _ : _) = [] -- STOP: with statements not supported + | checkNoSelfReference self seen expr && not crossesBoundary + = continue [] False seen rest -- Continue past safe loop + | otherwise = ([], 0, True) -- STOP: loop references self + where crossesBoundary = any scanCrossesBoundary + [scanSuite seen body, scanSuite seen elseBranch] + scanSuite _ (With _ _ _ : _) = ([], 0, True) -- STOP: with statements not supported -- Handle assert like if & raise - continue if test doesn't reference self scanSuite seen (Assert _ test msg : rest) | checkNoSelfReference self seen test && maybe True (checkNoSelfReference self seen) msg - = scanSuite seen rest -- Continue: assert doesn't leak self - | otherwise = [] -- STOP: assert references self - scanSuite _ (Return _ _ : _) = [] -- STOP: early exit - scanSuite _ (Raise _ _ : _) = [] -- STOP: raises exception + = continue [] False seen rest -- Continue: assert doesn't leak self + | otherwise = ([], 0, True) -- STOP: assert references self + scanSuite _ (Return _ _ : _) = ([], 0, True) -- STOP: returns an incompletely initialized object + scanSuite seen (Raise _ e : _) + | checkNoSelfReference self seen e + = ([], 0, False) -- STOP: no object is returned + | otherwise = ([], 0, True) -- Skip past break and continue - they are loop control flow, and we skip loops anyway - scanSuite seen (Break _ : rest) = scanSuite seen rest - scanSuite seen (Continue _ : rest) = scanSuite seen rest - scanSuite seen (Delete _ _ : rest) = scanSuite seen rest - scanSuite _ (After _ _ _ : _) = [] -- STOP: after statement (async) + scanSuite seen (Break _ : rest) = continue [] False seen rest + scanSuite seen (Continue _ : rest) = continue [] False seen rest + scanSuite seen (Delete _ target : rest) + | checkNoSelfReference self seen target + = continue [] False seen rest + | otherwise = ([], 0, True) + scanSuite _ (After _ _ _ : _) = ([], 0, True) -- STOP: after statement (async) -- Check nested function declarations for self references scanSuite seen (Decl _ decls : rest) | all (checkDeclNoSelfReference self seen) decls - = scanSuite seen rest -- Continue: nested functions don't capture self - | otherwise = [] -- STOP: nested function captures self - scanSuite _ (_ : _) = [] -- STOP: unhandled statement type (safe default) + = continue [] False seen rest -- Continue: nested functions don't capture self + | otherwise = ([], 0, True) -- STOP: nested function captures self + scanSuite _ (_ : _) = ([], 0, True) -- STOP: unhandled statement type (safe default) -- Check if a suite (list of statements) contains disallowed references to self @@ -1546,8 +1619,14 @@ checkNoSelfReferenceInSuite self seen stmts = all checkStmt stmts checkStmt (Assign _ _ e) = checkNoSelfReference self seen e checkStmt (MutAssign _ target e) = checkNoSelfReference self seen e && checkNoSelfReference self seen target checkStmt (AugAssign _ target _ e) = checkNoSelfReference self seen e && checkNoSelfReference self seen target + checkStmt (Assert _ test msg) = checkNoSelfReference self seen test && maybe True (checkNoSelfReference self seen) msg + checkStmt Pass{} = True + checkStmt (Delete _ target) = checkNoSelfReference self seen target checkStmt (Return _ Nothing) = True checkStmt (Return _ (Just e)) = checkNoSelfReference self seen e + checkStmt (Raise _ e) = checkNoSelfReference self seen e + checkStmt Break{} = True + checkStmt Continue{} = True checkStmt (If _ branches elseBranch) = all (\(Branch cond body) -> checkNoSelfReference self seen cond && checkNoSelfReferenceInSuite self seen body) branches && checkNoSelfReferenceInSuite self seen elseBranch checkStmt (While _ cond body elseBranch) = checkNoSelfReference self seen cond && @@ -1561,7 +1640,14 @@ checkNoSelfReferenceInSuite self seen stmts = all checkStmt stmts all (\(Handler _ hbody) -> checkNoSelfReferenceInSuite self seen hbody) handlers && checkNoSelfReferenceInSuite self seen elseBranch && checkNoSelfReferenceInSuite self seen finallyBlock - checkStmt _ = True -- Conservative: allow other statements + checkStmt (With _ items body) = all checkWithItem items && checkNoSelfReferenceInSuite self seen body + checkStmt (Data _ _ body) = checkNoSelfReferenceInSuite self seen body + checkStmt (VarAssign _ _ e) = checkNoSelfReference self seen e + checkStmt (After _ delay e) = checkNoSelfReference self seen delay && checkNoSelfReference self seen e + checkStmt Signature{} = True + checkStmt (Decl _ decls) = all (checkDeclNoSelfReference self seen) decls + + checkWithItem (WithItem e _) = checkNoSelfReference self seen e -- Check if an expression contains disallowed references to self -- We allow self.x since it can be seen as just a lone variable, which is valid @@ -1575,10 +1661,16 @@ checkNoSelfReference self seen expr = checkExpr expr checkExpr (Await _ _) = False -- Await is not allowed checkExpr (Var _ qn) | noq qn == self = False -- Direct reference to self not allowed - checkExpr e@(Dot _ (Var _ qn) attr) + checkExpr Var{} = True + checkExpr (Dot _ (Var _ qn) attr) | noq qn == self = attr `elem` seen -- self.attr is OK only if attr is initialized + checkExpr (Rest _ (Var _ qn) attr) + | noq qn == self = attr `elem` seen -- For other expressions, recursively check subexpressions checkExpr (Call _ func args kwds) = checkExpr func && checkPosArgs args && checkKwdArgs kwds + checkExpr (Let _ ss e) = checkNoSelfReferenceInSuite self seen ss && checkExpr e + checkExpr (TApp _ func _) = checkExpr func + checkExpr (Async _ e) = checkExpr e checkExpr (BinOp _ e1 _ e2) = checkExpr e1 && checkExpr e2 checkExpr (CompOp _ e1 ops) = checkExpr e1 && all checkOpArg ops where checkOpArg (OpArg _ e) = checkExpr e @@ -1587,6 +1679,7 @@ checkNoSelfReference self seen expr = checkExpr expr checkExpr (Tuple _ args kwds) = checkPosArgs args && checkKwdArgs kwds checkExpr (Paren _ e) = checkExpr e checkExpr (Cond _ cond thenE elseE) = checkExpr cond && checkExpr thenE && checkExpr elseE + checkExpr (IsInstance _ e _) = checkExpr e checkExpr (Index _ base idx) = checkExpr base && checkExpr idx checkExpr (Slice _ base (Sliz _ start stop step)) = checkExpr base && @@ -1596,20 +1689,29 @@ checkNoSelfReference self seen expr = checkExpr expr checkExpr (Dict _ items) = all checkItem items checkExpr (Set _ elems) = all checkElem elems checkExpr (ListComp _ elem comp) = checkElem elem && checkComp comp - checkExpr (DictComp _ (Assoc k v) comp) - = checkExpr k && checkExpr v && checkComp comp + checkExpr (DictComp _ item comp) = checkItem item && checkComp comp checkExpr (SetComp _ elem comp) = checkElem elem && checkComp comp - checkExpr (Lambda _ _ _ body _) = checkExpr body + checkExpr (Lambda _ ps ks body _) = checkPosPars ps && checkKwdPars ks && + (self `elem` (bound ps ++ bound ks) || checkNoSelfReference self [] body) checkExpr (Yield _ e) = maybe True checkExpr e checkExpr (YieldFrom _ e) = checkExpr e checkExpr (Dot _ e _) = checkExpr e -- Check base expression + checkExpr (Rest _ e _) = checkExpr e + checkExpr (DotI _ e _) = checkExpr e + checkExpr (RestI _ e _) = checkExpr e + checkExpr (Opt _ e _) = checkExpr e + checkExpr (OptChain _ e) = checkExpr e + checkExpr (Box _ e) = checkExpr e + checkExpr (UnBox _ e) = checkExpr e checkExpr (Int _ _ _) = True -- Integer literals are OK checkExpr (Float _ _ _) = True -- Float literals are OK + checkExpr (Imaginary _ _ _) = True checkExpr (Strings _ _) = True -- String literals are OK checkExpr (BStrings _ _) = True -- Byte string literals are OK checkExpr (Bool _ _) = True -- Boolean literals are OK checkExpr None{} = True -- None is OK - checkExpr _ = True -- Other expressions are OK (for now) + checkExpr NotImplemented{} = True + checkExpr Ellipsis{} = True checkPosArgs (PosArg e rest) = checkExpr e && checkPosArgs rest checkPosArgs (PosStar e) = checkExpr e @@ -1619,27 +1721,70 @@ checkNoSelfReference self seen expr = checkExpr expr checkKwdArgs (KwdStar e) = checkExpr e checkKwdArgs KwdNil = True + checkPosPars (PosPar _ _ e rest) = maybe True checkExpr e && checkPosPars rest + checkPosPars PosSTAR{} = True + checkPosPars PosNIL = True + + checkKwdPars (KwdPar _ _ e rest) = maybe True checkExpr e && checkKwdPars rest + checkKwdPars KwdSTAR{} = True + checkKwdPars KwdNIL = True + checkElem (Elem e) = checkExpr e checkElem (Star e) = checkExpr e checkItem (Assoc k v) = checkExpr k && checkExpr v + checkItem (StarStar e) = checkExpr e - checkComp (CompFor _ _ iter c) = checkExpr iter && checkComp c + checkComp (CompFor _ pat iter c) = checkPattern pat && checkExpr iter && checkComp c checkComp (CompIf _ test c) = checkExpr test && checkComp c checkComp NoComp = True + checkPattern PWild{} = True + checkPattern PVar{} = True + checkPattern (PParen _ pat) = checkPattern pat + checkPattern (PTuple _ ps ks) = checkPosPatterns ps && checkKwdPatterns ks + checkPattern (PList _ ps tailPat) = all checkPattern ps && maybe True checkPattern tailPat + checkPattern (PData _ _ indices) = all checkExpr indices + + checkPosPatterns (PosPat pat rest) = checkPattern pat && checkPosPatterns rest + checkPosPatterns (PosPatStar pat) = checkPattern pat + checkPosPatterns PosPatNil = True + + checkKwdPatterns (KwdPat _ pat rest)= checkPattern pat && checkKwdPatterns rest + checkKwdPatterns (KwdPatStar pat) = checkPattern pat + checkKwdPatterns KwdPatNil = True + -- Check if a declaration (nested function) references self -- We conservatively stop if ANY nested function references self at all, -- even if it only accesses already-initialized attributes checkDeclNoSelfReference :: Name -> [Name] -> Decl -> Bool checkDeclNoSelfReference self seen decl = case decl of - Def _ _ _ _ _ _ body _ _ _ -> checkNoSelfReferenceInSuite self [] body -- Pass empty list to disallow ANY self reference - Actor _ _ _ _ _ body _ -> checkNoSelfReferenceInSuite self [] body -- Pass empty list to disallow ANY self reference + Def _ _ _ ps ks _ body _ _ _ -> + checkParameterDefaults ps ks && + (self `elem` (bound ps ++ bound ks) || checkNoSelfReferenceInSuite self [] body) + Actor _ _ _ ps ks body _ -> + checkParameterDefaults ps ks && + (self == selfKW || self `elem` (bound ps ++ bound ks) || + checkNoSelfReferenceInSuite self [] body) Class _ _ _ _ body _ -> True -- Nested classes don't capture self by default Protocol _ _ _ _ body _ -> True -- Nested protocols don't capture self Typedef _ _ _ _ _ -> True -- Type aliases don't capture self Extension _ _ _ _ body _ -> True -- Extensions don't capture self + where + checkParameterDefaults ps ks = checkPosDefaults ps && checkKwdDefaults ks + + checkPosDefaults (PosPar _ _ value rest) + = maybe True (checkNoSelfReference self seen) value && + checkPosDefaults rest + checkPosDefaults PosSTAR{} = True + checkPosDefaults PosNIL = True + + checkKwdDefaults (KwdPar _ _ value rest) + = maybe True (checkNoSelfReference self seen) value && + checkKwdDefaults rest + checkKwdDefaults KwdSTAR{} = True + checkKwdDefaults KwdNIL = True -- Infer all class attributes by scanning the entire __init__ method for any -- self.x assignments, regardless of control flow. This is used for attribute @@ -1677,8 +1822,9 @@ inferClassAttributes self stmts = nub $ scanAll stmts -- Get all parent classes whose __init__ methods are called getCalledParentInits :: Env -> Suite -> [QName] getCalledParentInits _ [] = [] -getCalledParentInits env (Expr _ (Call _ (Dot _ (Var _ c) n) _ _) : rest) - | isClass env c, n == initKW = c : getCalledParentInits env rest +getCalledParentInits env (Expr _ e : rest) + | Just (c, n) <- directDotCall e + , isClass env c, n == initKW = c : getCalledParentInits env rest getCalledParentInits env (_ : rest) = getCalledParentInits env rest -- Get attributes that would be initialized by calling a parent's __init__