Skip to content

Commit 2e055ab

Browse files
authored
Fix self.field.method() calls bypassing dispatch resolution (#1085)
Fixes #1082 `self.field.method()` calls inside class methods were bypassing dispatch resolution. The type inference for `self` fell through to `Package`, so chained calls like `self.store.put_item(...)` were flattened into underscore-separated package calls instead of resolving through the field's type and dispatching to the correct spec. The fix registers `self` with the current class name in `variableTypes` during method body translation, so `inferExprType` can resolve `self` → class → field type → correct method dispatch. Tested with a new end-to-end test that verifies `self.store.put_item()` correctly dispatches through the Storage spec and detects a precondition violation. Existing tests pass.
1 parent 641d967 commit 2e055ab

3 files changed

Lines changed: 41 additions & 0 deletions

File tree

Strata/Languages/Python/PythonToLaurel.lean

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2213,6 +2213,9 @@ def translateFunction (ctx : TranslationContext) (sourceRange: SourceRange) (fun
22132213
let inputTypes := funcDecl.args.map fun arg =>
22142214
(arg.name, highTypeToPyLauType arg.laurelType.val)
22152215
let ctx := {ctx with variableTypes:= ("nullcall_ret", PyLauType.Any)::inputTypes}
2216+
let ctx := match ctx.currentClassName with
2217+
| some cn => {ctx with variableTypes := ("self", cn) :: ctx.variableTypes}
2218+
| none => ctx
22162219
let (bodyTrans, newCtx) ← match body with
22172220
| some body =>
22182221
let (bodyBlock, newCtx) ← translateFunctionBody ctx funcDecl.kwargsName inputs body

StrataTestExtra/Languages/Python/AnalyzeLaurelTest.lean

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,4 +383,29 @@ recursively translates subclasses, so the type
383383
let msgs := result.errors.toList.map (·.message)
384384
throw <| IO.userError s!"Resolution errors after FilterPrelude:\n{"\n".intercalate msgs}"
385385

386+
/-! ## Self-field dispatch test
387+
388+
Verifies that `self.field.method()` inside a class method resolves through
389+
dispatch rather than being flattened to an underscore-separated package call.
390+
Without the fix, `self.store.put_item(...)` would produce a Hole instead of
391+
invoking the Storage spec, so precondition violations would go undetected. -/
392+
393+
#eval withPython fun pythonCmd => do
394+
IO.FS.withTempDir fun tmpDir => do
395+
setupFixture pythonCmd tmpDir
396+
let result ← runAnalyzeAndVerify pythonCmd tmpDir
397+
"test_self_field_dispatch_precondition.py" (useRoots := true)
398+
match result with
399+
| .error msg => throw <| IO.userError s!"Pipeline failed: {msg}"
400+
| .ok vcResults =>
401+
let mut foundAlwaysFalse := false
402+
for r in vcResults do
403+
if r.obligation.label.startsWith "servicelib_Storage_" then
404+
let line := r.formatOutcome
405+
if (line.splitOn "✖️").length != 1 then
406+
foundAlwaysFalse := true
407+
if !foundAlwaysFalse then
408+
throw <| IO.userError
409+
"Expected ✖️ always false for empty bucket violation via self.field dispatch"
410+
386411
end Strata.Python.AnalyzeLaurelTest
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import servicelib
2+
3+
class MyService:
4+
def __init__(self):
5+
self.store: Storage = servicelib.connect("storage")
6+
7+
def save_empty_bucket(self) -> bool:
8+
self.store.put_item(Bucket="", Key="k", Data="v")
9+
return True
10+
11+
def trigger() -> bool:
12+
svc: MyService = MyService()
13+
return svc.save_empty_bucket()

0 commit comments

Comments
 (0)