Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions Strata/Languages/Laurel/Resolution.lean
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,10 @@ structure ResolveState where
/-- When resolving inside an instance procedure, the owning composite type name.
Used by `resolveFieldRef` to resolve `self.field` when `self` has type `Any`. -/
instanceTypeName : Option String := none
/-- True when resolving inside an expression where the value is used (e.g., as an
argument to another call or operator). Multi-output calls are only diagnosed
in value context, not in statement position or direct assignment RHS. -/
inValueContext : Bool := false

@[expose] abbrev ResolveM := StateM ResolveState

Expand Down Expand Up @@ -358,7 +362,10 @@ def resolveStmtExpr (exprMd : StmtExprMd) : ResolveM StmtExprMd := do
| AstNode.mk expr source =>
let val' ← match _: expr with
| .IfThenElse cond thenBr elseBr =>
let saved := (← get).inValueContext
modify fun s => { s with inValueContext := true }
let cond' ← resolveStmtExpr cond
modify fun s => { s with inValueContext := saved }
let thenBr' ← resolveStmtExpr thenBr
let elseBr' ← elseBr.attach.mapM (fun a => have := a.property; resolveStmtExpr a.val)
pure (.IfThenElse cond' thenBr' elseBr')
Expand All @@ -367,7 +374,10 @@ def resolveStmtExpr (exprMd : StmtExprMd) : ResolveM StmtExprMd := do
let stmts' ← stmts.mapM resolveStmtExpr
pure (.Block stmts' label)
| .While cond invs dec body =>
let saved := (← get).inValueContext
modify fun s => { s with inValueContext := true }
let cond' ← resolveStmtExpr cond
modify fun s => { s with inValueContext := saved }
let invs' ← invs.attach.mapM (fun a => have := a.property; resolveStmtExpr a.val)
let dec' ← dec.attach.mapM (fun a => have := a.property; resolveStmtExpr a.val)
let body' ← resolveStmtExpr body
Expand Down Expand Up @@ -437,10 +447,30 @@ def resolveStmtExpr (exprMd : StmtExprMd) : ResolveM StmtExprMd := do
| .StaticCall callee args =>
let callee' ← resolveRef callee source
(expected := #[.parameter, .staticProcedure, .datatypeConstructor, .constant])
-- Resolve arguments in value context (their results are used as values)
let saved := (← get).inValueContext
modify fun s => { s with inValueContext := true }
let args' ← args.mapM resolveStmtExpr
modify fun s => { s with inValueContext := saved }
-- Multi-output procedures must not appear in value context: the extra
-- outputs (e.g. error channels) would be silently discarded.
let s ← get
if s.inValueContext then
let outputCount := match s.scope.get? callee'.text with
| some (_, .staticProcedure proc) => proc.outputs.length
| some (_, .instanceProcedure _ proc) => proc.outputs.length
| _ => 0
if outputCount > 1 then
let diag := diagnosticFromSource source
s!"Multi-output procedure '{callee'.text}' used in expression position; it returns {outputCount} values but only one can be used here. Use a multi-target assignment instead."
modify fun s => { s with errors := s.errors.push diag }
pure (.StaticCall callee' args')
| .PrimitiveOp op args =>
Comment thread
tautschnig marked this conversation as resolved.
-- Resolve arguments in value context
let saved := (← get).inValueContext
modify fun s => { s with inValueContext := true }
let args' ← args.mapM resolveStmtExpr
modify fun s => { s with inValueContext := saved }
pure (.PrimitiveOp op args')
| .New ref =>
let ref' ← resolveRef ref source
Expand Down Expand Up @@ -482,10 +512,16 @@ def resolveStmtExpr (exprMd : StmtExprMd) : ResolveM StmtExprMd := do
let val' ← resolveStmtExpr val
pure (.Fresh val')
| .Assert ⟨condExpr, summary⟩ =>
let saved := (← get).inValueContext
modify fun s => { s with inValueContext := true }
let cond' ← resolveStmtExpr condExpr
modify fun s => { s with inValueContext := saved }
pure (.Assert { condition := cond', summary })
| .Assume cond =>
let saved := (← get).inValueContext
modify fun s => { s with inValueContext := true }
let cond' ← resolveStmtExpr cond
modify fun s => { s with inValueContext := saved }
pure (.Assume cond')
| .ProveBy val proof =>
let val' ← resolveStmtExpr val
Expand Down
13 changes: 13 additions & 0 deletions StrataTest/Languages/Laurel/ResolutionKindTests.lean
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,17 @@ composite Foo extends nat { }
#guard_msgs (error, drop all) in
#eval testInputWithOffset "ExtendConstrained" extendConstrained 90 processResolution

/-! ## Multi-output procedure used in expression position -/

def multiOutputInExpr := r"
procedure multi(x: int) returns (a: int, b: int) opaque;
procedure test() opaque {
assert multi(1) == 1
// ^^^^^^^^ error: Multi-output procedure 'multi' used in expression position
};
"

#guard_msgs (error, drop all) in
#eval testInputWithOffset "MultiOutputInExpr" multiOutputInExpr 100 processResolution

end Laurel
Loading