Skip to content

Commit 90372f0

Browse files
Improve exception logic for missing Python models (#714)
### Changes - Improve handling of missing models by also assigning a hole to `maybe_except` - Improve Laurel's type inference for holes ### Testing - Update expect file for `test_missing_models` By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. --------- Co-authored-by: keyboardDrummer-bot <keyboardDrummer-bot@users.noreply.github.com> Co-authored-by: keyboardDrummer-bot <keyboarddrummer.bot@gmail.com>
1 parent b5879b9 commit 90372f0

4 files changed

Lines changed: 48 additions & 185 deletions

File tree

Strata/Languages/Python/PythonRuntimeLaurelPart.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace Python
1313

1414
/--
1515
Python prelude declarations expressed in Laurel grammar.
16-
Converted from PythonRuntimeCorePart.lean (Core dialect) to Laurel dialect.
16+
Converted from PythonLaurelCorePrelude.lean (Core dialect) to Laurel dialect.
1717
1818
Core-specific constructs that Laurel does not support:
1919
- `inline` keyword: noted in comments

Strata/Languages/Python/PythonToLaurel.lean

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,13 +1055,20 @@ partial def translateAssign (ctx : TranslationContext)
10551055
stmts := stmts ++ eltStmts
10561056
return (curCtx, stmts)
10571057
let rhs_trans ← translateExpr ctx rhs
1058+
-- When an unmodeled call produces a Hole, also havoc maybe_except since
1059+
-- the call is a black box that could throw any exception.
1060+
let rhsIsCall := match rhs with | .Call _ _ _ _ => true | _ => false
10581061
if let .Hole := rhs_trans.val then
10591062
{
1063+
let exceptHavoc :=
1064+
if rhsIsCall then
1065+
[mkStmtExprMdWithLoc (StmtExpr.Assign [maybeExceptVar] (mkStmtExprMd (.Hole false none))) md]
1066+
else []
10601067
match lhs with
10611068
| .Name _ n _ =>
10621069
if n.val ∈ ctx.variableTypes.unzip.1 then
10631070
let targetExpr := mkStmtExprMd (StmtExpr.Identifier n.val)
1064-
return (ctx, [mkStmtExprMd (StmtExpr.Assign [targetExpr] rhs_trans)])
1071+
return (ctx, [mkStmtExprMd (StmtExpr.Assign [targetExpr] rhs_trans)] ++ exceptHavoc)
10651072
else
10661073
-- Use type annotation if it matches a known composite type
10671074
let annType := annotation.map (fun a => pyExprToString a) |>.getD "Any"
@@ -1073,8 +1080,8 @@ partial def translateAssign (ctx : TranslationContext)
10731080
| _ => pure (AnyTy, "Any")
10741081
let initStmt := mkStmtExprMd (StmtExpr.LocalVariable n.val varTy (mkStmtExprMd .Hole))
10751082
let newctx := {ctx with variableTypes:=(n.val, trackType)::ctx.variableTypes}
1076-
return (newctx, [initStmt])
1077-
| _ => return (ctx, [mkStmtExprMd .Hole])
1083+
return (newctx, [initStmt] ++ exceptHavoc)
1084+
| _ => return (ctx, [mkStmtExprMd .Hole] ++ exceptHavoc)
10781085
}
10791086
let mut newctx := ctx
10801087
match lhs with
@@ -1374,6 +1381,12 @@ partial def translateStmt (ctx : TranslationContext) (s : Python.stmt SourceRang
13741381
let expr := { expr with md := md }
13751382
let exceptionCheck := getExceptionAssertions ctx expr
13761383

1384+
-- When a call has no model (translates to Hole), also havoc maybe_except
1385+
-- since an unmodeled call is a black box that could throw any exception.
1386+
let holeExceptHavoc :=
1387+
if let .Call _ _ _ _ := value then
1388+
[mkStmtExprMdWithLoc (StmtExpr.Assign [maybeExceptVar] (mkStmtExprMd (.Hole false none))) md]
1389+
else []
13771390
match expr.val with
13781391
| .StaticCall fnname _ =>
13791392
match ctx.functionSignatures.find? (λ funsig => funsig.name == fnname) with
@@ -1385,6 +1398,9 @@ partial def translateStmt (ctx : TranslationContext) (s : Python.stmt SourceRang
13851398
else
13861399
return (ctx, exceptionCheck ++ [expr])
13871400
| _ => return (ctx, exceptionCheck ++ [expr])
1401+
-- Unmodeled call: skip exception checks (no model to check against),
1402+
-- but havoc maybe_except since the call could throw.
1403+
| .Hole => return (ctx, [expr] ++ holeExceptHavoc)
13881404
| _ => return (ctx, exceptionCheck ++ [expr])
13891405

13901406
| .Import _ _ | .ImportFrom _ _ _ _ |.Pass _ => return (ctx, [])

StrataTest/Languages/Python/PreludeVerifyTest.lean

Lines changed: 3 additions & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -23,193 +23,16 @@ private def preludeProgram : Core.Program :=
2323
| some prog => prog
2424
| none => { decls := [] }
2525

26-
private def verifyPrelude : IO Core.VCResults := do
26+
private def verifyPrelude : IO (Array DiagnosticModel) := do
2727
IO.FS.withTempDir fun tempDir => do
2828
let r ← EIO.toIO (IO.Error.userError ∘ toString)
2929
(Core.verify preludeProgram tempDir
3030
(options := .quiet)
3131
(moreFns := Strata.Python.ReFactory)
3232
(externalPhases := [Strata.frontEndPhase]))
33-
return r
33+
return r.flatMap (fun vcr => (toDiagnosticModel vcr []).toArray)
3434

35-
/--
36-
info:
37-
Obligation: postcondition
38-
Property: assert
39-
Result: ✅ pass
40-
41-
Obligation: List_take_body_calls_List_take_0
42-
Property: assert
43-
Result: ✅ pass
44-
45-
Obligation: List_take_len_post_postcondition_calls_List_take_0
46-
Property: assert
47-
Result: ✅ pass
48-
49-
Obligation: assume_postcondition_calls_List_take_0
50-
Property: assert
51-
Result: ✅ pass
52-
53-
Obligation: postcondition
54-
Property: assert
55-
Result: ✅ pass
56-
57-
Obligation: List_drop_body_calls_List_drop_0
58-
Property: assert
59-
Result: ✅ pass
60-
61-
Obligation: List_drop_len_post_postcondition_calls_List_drop_0
62-
Property: assert
63-
Result: ✅ pass
64-
65-
Obligation: assume_postcondition_calls_List_drop_0
66-
Property: assert
67-
Result: ✅ pass
68-
69-
Obligation: postcondition
70-
Property: assert
71-
Result: ✅ pass
72-
73-
Obligation: postcondition
74-
Property: assert
75-
Result: ✅ pass
76-
77-
Obligation: List_get_non_neg_body_calls_List_get_0
78-
Property: assert
79-
Result: ✅ pass
80-
81-
Obligation: List_get_body_calls_List_get_non_neg_0
82-
Property: assert
83-
Result: ✅ pass
84-
85-
Obligation: List_get_body_calls_List_get_non_neg_1
86-
Property: assert
87-
Result: ✅ pass
88-
89-
Obligation: List_slice_non_neg_body_calls_List_drop_0
90-
Property: assert
91-
Result: ✅ pass
92-
93-
Obligation: List_slice_non_neg_body_calls_List_take_1
94-
Property: assert
95-
Result: ✅ pass
96-
97-
Obligation: List_slice_body_calls_List_slice_non_neg_0
98-
Property: assert
99-
Result: ✅ pass
100-
101-
Obligation: List_set_non_neg_body_calls_List_set_0
102-
Property: assert
103-
Result: ✅ pass
104-
105-
Obligation: List_set_body_calls_List_set_non_neg_0
106-
Property: assert
107-
Result: ✅ pass
108-
109-
Obligation: List_set_body_calls_List_set_non_neg_1
110-
Property: assert
111-
Result: ✅ pass
112-
113-
Obligation: DictStrAny_get_body_calls_DictStrAny_get_0
114-
Property: assert
115-
Result: ✅ pass
116-
117-
Obligation: DictStrAny_get_or_none_body_calls_DictStrAny_get_0
118-
Property: assert
119-
Result: ✅ pass
120-
121-
Obligation: Any_get_body_calls_DictStrAny_get_0
122-
Property: assert
123-
Result: ✅ pass
124-
125-
Obligation: Any_get_body_calls_List_get_1
126-
Property: assert
127-
Result: ✅ pass
128-
129-
Obligation: Any_get!_body_calls_DictStrAny_get_0
130-
Property: assert
131-
Result: ✅ pass
132-
133-
Obligation: Any_get!_body_calls_List_get_1
134-
Property: assert
135-
Result: ✅ pass
136-
137-
Obligation: Any_set_body_calls_List_set_0
138-
Property: assert
139-
Result: ✅ pass
140-
141-
Obligation: Any_set!_body_calls_List_set_0
142-
Property: assert
143-
Result: ✅ pass
144-
145-
Obligation: PFloorDiv_body_calls_Int.SafeDiv_0
146-
Property: division by zero check
147-
Result: ✅ pass
148-
149-
Obligation: PFloorDiv_body_calls_Int.SafeDiv_1
150-
Property: division by zero check
151-
Result: ✅ pass
152-
153-
Obligation: PFloorDiv_body_calls_Int.SafeDiv_2
154-
Property: division by zero check
155-
Result: ✅ pass
156-
157-
Obligation: PFloorDiv_body_calls_Int.SafeDiv_3
158-
Property: division by zero check
159-
Result: ✅ pass
160-
161-
Obligation: PAnd_body_calls_Any_to_bool_0
162-
Property: assert
163-
Result: ✅ pass
164-
165-
Obligation: POr_body_calls_Any_to_bool_0
166-
Property: assert
167-
Result: ✅ pass
168-
169-
Obligation: PMod_body_calls_Int.SafeMod_0
170-
Property: division by zero check
171-
Result: ✅ pass
172-
173-
Obligation: PMod_body_calls_Int.SafeMod_1
174-
Property: division by zero check
175-
Result: ✅ pass
176-
177-
Obligation: PMod_body_calls_Int.SafeMod_2
178-
Property: division by zero check
179-
Result: ✅ pass
180-
181-
Obligation: PMod_body_calls_Int.SafeMod_3
182-
Property: division by zero check
183-
Result: ✅ pass
184-
185-
Obligation: postcondition
186-
Property: assert
187-
Result: ✅ pass
188-
189-
Obligation: postcondition
190-
Property: assert
191-
Result: ✅ pass
192-
193-
Obligation: postcondition
194-
Property: assert
195-
Result: ✅ pass
196-
197-
Obligation: assert(43079)
198-
Property: assert
199-
Result: ✅ pass
200-
201-
Obligation: assert(43146)
202-
Property: assert
203-
Result: ✅ pass
204-
205-
Obligation: assert(43254)
206-
Property: assert
207-
Result: ✅ pass
208-
209-
Obligation: postcondition
210-
Property: assert
211-
Result: ✅ pass
212-
-/
35+
/-- info: #[] -/
21336
#guard_msgs in
21437
#eval verifyPrelude
21538

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,31 @@
1+
test_missing_models.py(12, 4): ❓ unknown - (Origin_test_helper_procedure_Requires)req_name_is_foo
2+
test_missing_models.py(12, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str
3+
test_missing_models.py(12, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar
4+
test_missing_models.py(15, 4): ❓ unknown - (Origin_test_helper_procedure_Requires)req_name_is_foo
5+
test_missing_models.py(15, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str
6+
test_missing_models.py(15, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar
7+
test_missing_models.py(12, 4): ❓ unknown - (Origin_test_helper_procedure_Requires)req_name_is_foo
8+
test_missing_models.py(12, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str
9+
test_missing_models.py(12, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar
10+
test_missing_models.py(15, 4): ❓ unknown - (Origin_test_helper_procedure_Requires)req_name_is_foo
11+
test_missing_models.py(15, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str
12+
test_missing_models.py(15, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar
113
test_missing_models.py(8, 4): ❓ unknown - init_calls_Any_get_0
214
test_missing_models.py(8, 4): ❓ unknown - init_calls_Any_get_1
15+
test_missing_models.py(12, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_name_is_foo
16+
test_missing_models.py(12, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str
17+
test_missing_models.py(12, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar
18+
test_missing_models.py(15, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_name_is_foo
19+
test_missing_models.py(15, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str
20+
test_missing_models.py(15, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar
321
test_missing_models.py(9, 4): ❓ unknown - (Origin_test_helper_procedure_Requires)req_name_is_foo
422
test_missing_models.py(9, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str
523
test_missing_models.py(9, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar
6-
DETAIL: 2 passed, 0 failed, 3 inconclusive
24+
test_missing_models.py(12, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_name_is_foo
25+
test_missing_models.py(12, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str
26+
test_missing_models.py(12, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar
27+
test_missing_models.py(15, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_name_is_foo
28+
test_missing_models.py(15, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_str
29+
test_missing_models.py(15, 4): ✅ pass - (Origin_test_helper_procedure_Requires)req_opt_name_none_or_bar
30+
DETAIL: 22 passed, 0 failed, 7 inconclusive
731
RESULT: Inconclusive

0 commit comments

Comments
 (0)