Eelco Visser
WG2.16 | Portland | February 2019
Statix: Declarative Type System Specification
Based on:

Van Antwerpen, Bach Poulsen, Rouvoet, Visser

Scopes as Types
PACMPL 2 (OOPSLA), 2018
Context
- Language workbench

- High-level language specification

- Abstract from implementation concerns (generate automatically)

Type systems
- Type constraints

- Name resolution => scope graphs

- Staging
!2
Type System Specification
Scope Graphs
!3
0A : MOD(1) B : MOD(2)
1 2
P P
I
q : BOOLp : BOOL
::
: :
module A {
import B
def p : bool = ~q
}
module B {
def q : bool = true
}
B
P*

$ < P
Statix By Example
4
Arithmetic Expressions: Concrete and Abstract Syntax
!5
context-free syntax // arithmetic
Exp.Int = <<INT>>
Exp.Add = <<Exp> + <Exp>> {left}
Exp.Sub = <<Exp> - <Exp>> {left}
Exp.Mul = <<Exp> * <Exp>> {left}
Exp.Eq = <<Exp> == <Exp>> {non-assoc}
constructors // arithmetic
Int : INT -> Exp
Add : Exp * Exp -> Exp
Sub : Exp * Exp -> Exp
Mul : Exp * Exp -> Exp
Eq : Exp * Exp -> Exp
SDF3 Statix
Program(
[Exp(
Add(Int("1"), Mul(Int("2"), Int("3")))
)]
)
> 1 + 2 * 3
Arithmetic Expressions: Abstract Syntax
!6
> 1 + 2 * 3
constructors // arithmetic
Int : INT -> Exp
Add : Exp * Exp -> Exp
Sub : Exp * Exp -> Exp
Mul : Exp * Exp -> Exp
Eq : Exp * Exp -> Exp
Arithmetic Expressions: Type Predicate
!7
> 1 + 2 * 3
sorts TYPE constructors
INT : TYPE
BOOL : TYPE
FUN : TYPE * TYPE -> TYPE
rules // expressions
typeOfExp : scope * Exp -> TYPE
typeOfExp(s, Int(i)) = INT().
typeOfExp(s, Add(e1, e2)) = INT() :-
typeOfExp(s, e1) == INT(),
typeOfExp(s, e2) == INT().
. . .
typeOfExp(s, Eq(e1, e2)) = BOOL() :- {T}
typeOfExp(s, e1) == T,
typeOfExp(s, e2) == T.
constructors // arithmetic
Int : INT -> Exp
Add : Exp * Exp -> Exp
Sub : Exp * Exp -> Exp
Mul : Exp * Exp -> Exp
Eq : Exp * Exp -> Exp
Variable Definitions
!8
sorts Program constructors
Program : list(Decl) -> Program
sorts Decl constructors
Def : Bind -> Decl
Exp : Exp -> Decl
sorts Bind constructors
Bind : ID * Exp -> Bind
TBind : ID * Type * Exp -> Bind
relations
typeOfDecl : occurrence -> TYPE
def a = 0
def b = a + c
def b = 1 + d
def c : Int = 0
def e : Bool = 1
> a + b + c
Variable Definitions
!9
sorts Program constructors
Program : list(Decl) -> Program
sorts Decl constructors
Def : Bind -> Decl
Exp : Exp -> Decl
sorts Bind constructors
Bind : ID * Exp -> Bind
TBind : ID * Type * Exp -> Bind
relations
typeOfDecl : occurrence -> TYPE
rules
programOK : Program
programOK(Program(decls)) :- {s}
new s,
declsOk(s, decls).
rules // declarations
declOk : scope * Decl
declsOk maps declOk(*, list(*))
declOk(s, Def(bind)) :-
bindOk(s, s, bind).
declOk(s, Exp(e)) :- {T}
typeOfExp(s, e) == T.
def a = 0
def b = a + c
def b = 1 + d
def c : Int = 0
def e : Bool = 1
> a + b + c
Variable Definitions: Declarations
!10
sorts Program constructors
Program : list(Decl) -> Program
sorts Decl constructors
Def : Bind -> Decl
Exp : Exp -> Decl
sorts Bind constructors
Bind : ID * Exp -> Bind
TBind : ID * Type * Exp -> Bind
relations
typeOfDecl : occurrence -> TYPE
def a = 0
def b = a + c
def b = 1 + d
def c : Int = 0
def e : Bool = 1
> a + b + c
rules // bindings
bindOk : scope * scope * Bind
bindsOk maps bindOk(*, *, list(*))
bindOk(s_bnd, s_ctx, Bind(x, e)) :- {T}
typeOfExp(s_ctx, e) == T,
s_bnd -> Var{x@x} with typeOfDecl T.
bindOk(s_bnd, s_ctx, TBind(x, t, e)) :- {T}
typeOfType(s_ctx, t) == T,
typeOfExp(s_ctx, e) == T,
s_bnd -> Var{x@x} with typeOfDecl T.
Variable Definitions: Name Resolution
!11
rules // bindings
bindOk : scope * scope * Bind
bindsOk maps bindOk(*, *, list(*))
bindOk(s_bnd, s_ctx, Bind(x, e)) :- {T}
typeOfExp(s_ctx, e) == T,
s_bnd -> Var{x@x} with typeOfDecl T.
bindOk(s_bnd, s_ctx, TBind(x, t, e)) :- {T}
typeOfType(s_ctx, t) == T,
typeOfExp(s_ctx, e) == T,
s_bnd -> Var{x@x} with typeOfDecl T.
rules // variables
typeOfExp(s, Var(x)) = T :- {p d }
typeOfDecl of Var{x@x} in s |-> [(p, (d, T))].
sorts Program constructors
Program : list(Decl) -> Program
sorts Decl constructors
Def : Bind -> Decl
Exp : Exp -> Decl
sorts Bind constructors
Bind : ID * Exp -> Bind
TBind : ID * Type * Exp -> Bind
relations
typeOfDecl : occurrence -> TYPE
def a = 0
def b = a + c
def b = 1 + d
def c : Int = 0
def e : Bool = 1
> a + b + c
Lexical Scope: Functions
!12
rules // functions
typeOfExp(s, Fun(x, t, e)) = FUN(T, S) :- {s_fun}
typeOfType(s, t) == T,
new s_fun,
s_fun -P-> s,
s_fun -> Var{x@x} with typeOfDecl T,
typeOfExp(s_fun, e) == S.
typeOfExp(s, App(e1, e2)) = T :- {S}
typeOfExp(s, e1) == FUN(S, T),
typeOfExp(s, e2) == S.
sorts TYPE constructors
INT : TYPE
BOOL : TYPE
FUN : TYPE * TYPE -> TYPE
def i = 3
def inc = fun(x : Int) { x + i }
> inc 2
constructors // functions
Fun : ID * Type * Exp -> Exp
App : Exp * Exp -> Exp
Lexical Scope: Sequential Let
!13
rules // let bindings
typeOfExp(s, Let(binds, e)) = T :- {s_let}
new s_let,
sbindsOk(s, s_let, binds),
typeOfExp(s_let, e) == T.
def a = 0
def b = 1
def c = 2
> let
a = c;
b = a;
c = b
in
a + b + c
rules // bindings
sbindsOk : scope * scope * list(Bind)
sbindsOk(s, s_fin, []) :-
s_fin -P-> s.
sbindsOk(s, s_fin, [bind | binds]) :- {s_mid}
new s_mid, s_mid -P-> s,
bindOk(s_mid, s, bind),
sbindsOk(s_mid, s_fin, binds).
Lexical Scope: Parallel Let
!14
rules // let bindings
typeOfExp(s, LetPar(binds, e)) = T :- {s_let}
new s_let, s_let -P-> s,
bindsOk(s_let, s, binds),
typeOfExp(s_let, e) == T.
bindOk : scope * scope * Bind
bindsOk maps bindOk(*, *, list(*))
bindOk(s_bnd, s_ctx, Bind(x, e)) :- {T}
typeOfExp(s_ctx, e) == T,
s_bnd -> Var{x@x} with typeOfDecl T.
def a = 0
def b = 1
def c = 2
> letpar
a = c;
b = a;
c = b
in
a + b + c
Lexical Scope: Recursive Let
!15
rules // let bindings
typeOfExp(s, LetRec(binds, e)) = T :- {s_let}
new s_let, s_let -P-> s,
bindsOk(s_let, s_let, binds),
typeOfExp(s_let, e) == T.
bindOk : scope * scope * Bind
bindsOk maps bindOk(*, *, list(*))
bindOk(s_bnd, s_ctx, Bind(x, e)) :- {T}
typeOfExp(s_ctx, e) == T,
s_bnd -> Var{x@x} with typeOfDecl T.
> letrec
odd = fun(x : Int) {
if x == 0 then false
else even(x - 1)
};
even = fun(x : Int) {
if x == 0 then true
else odd(x - 1)
}
in
even(3)
Modules
!16
module A {
import B
def a = 4
def c = b + 4
}
module B {
import A
def b = a + 3
}
Modules: Scopes as Types
!17
rules // modules
declOk(s, Module(m, decls)) :- {s_mod}
new s_mod, s_mod -P-> s,
s -> Mod{m@m} with typeOfDecl MOD(s_mod),
declsOk(s_mod, decls).
declOk(s, Import(m)) :- {p d s_mod}
typeOfDecl of Mod{m@m} in s |-> [(p, (d, MOD(s_mod)))],
s -I-> s_mod.
module A {
import B
def a = 4
def c = b + 4
}
module B {
import A
def b = a + 3
}
sorts TYPE constructors
INT : TYPE
BOOL : TYPE
FUN : TYPE * TYPE -> TYPE
MOD : scope -> TYPE
name-resolution
labels P I R
resolve Var filter pathMatch[P* (R* | I*)]
min pathLt[$ < I, $ < P, I < P, R < P]
resolve Mod filter pathMatch[P P* I*]
min pathLt[$ < I, $ < P, I < P, R < P]
Records
!18
record Point {
x : Int
y : Int
}
def p : Point
= new Point { x = 1, y = 2}
> p.x + p.y
def z = 3
> with p do x + y + z
Record Type Declaration: Scopes as Types
!19
rules // record type
declOk(s, Record(x, fdecls)) :- {s_rec}
new s_rec,
fdeclsOk(s_rec, s, fdecls),
s -> Var{x@x} with typeOfDecl REC(s_rec).
fdeclOk : scope * scope * FDecl
fdeclsOk maps fdeclOk(*, *, list(*))
fdeclOk(s_bnd, s_ctx, FDecl(x, t)) :- {T}
typeOfType(s_ctx, t) == T,
s_bnd -> Var{x@x} with typeOfDecl T.
sorts TYPE constructors
INT : TYPE
BOOL : TYPE
FUN : TYPE * TYPE -> TYPE
REC : scope -> TYPE
record Point {
x : Int
y : Int
}
def p : Point
= new Point { x = 1, y = 2}
> p.x + p.y
def z = 3
> with p do x + y + z
Record Literals
!20
rules // records construction
typeOfExp(s, New(x, fbinds)) = REC(s_rec) :- {p d}
typeOfDecl of Var{x@x} in s |-> [(p, (d, REC(s_rec)))],
fbindsOk(s, s_rec, fbinds).
fbindOk : scope * scope * FBind
fbindsOk maps fbindOk(*, *, list(*))
fbindOk(s, s_rec, FBind(x, e)) :- {p d T}
typeOfExp(s, e) == T,
typeOfDecl of Var{x@x} in s_rec |-> [(p, (d, T))].
record Point {
x : Int
y : Int
}
def p : Point
= new Point { x = 1, y = 2}
> p.x + p.y
def z = 3
> with p do x + y + z
sorts TYPE constructors
INT : TYPE
BOOL : TYPE
FUN : TYPE * TYPE -> TYPE
REC : scope -> TYPE
Record Projection
!21
rules // record projection
typeOfExp(s, Proj(e, x)) = T :- {p d s_rec S}
typeOfExp(s, e) == S,
proj(S, x) == T.
proj : TYPE * ID -> TYPE
proj(REC(s_rec), x) = T :- {p d}
typeOfDecl of Var{x@x} in s_rec |-> [(p, (d, T))].
record Point {
x : Int
y : Int
}
def p : Point
= new Point { x = 1, y = 2}
> p.x + p.y
def z = 3
> with p do x + y + z
sorts TYPE constructors
INT : TYPE
BOOL : TYPE
FUN : TYPE * TYPE -> TYPE
REC : scope -> TYPE
With Record
!22
rules // with record value
typeOfExp(s, With(e1, e2)) = T :- {s_with s_rec}
typeOfExp(s, e1) == REC(s_rec),
new s_with,
s_with -P-> s, s_with -R-> s_rec,
typeOfExp(s_with, e2) == T.
record Point {
x : Int
y : Int
}
def p : Point
= new Point { x = 1, y = 2}
> p.x + p.y
def z = 3
> with p do x + y + z
sorts TYPE constructors
INT : TYPE
BOOL : TYPE
FUN : TYPE * TYPE -> TYPE
REC : scope -> TYPE
name-resolution
labels P I R
resolve Var filter pathMatch[P* (R* | I*)]
min pathLt[$ < I, $ < P, I < P, R < P]
Type References
!23
record Point {
x : Int
y : Int
}
def translate : Point -> Point -> Point
= fun(p: Point){ fun(d: Point) {
new Point{
x = p.x + d.x,
y = p.y + d.y }
} }
def p : Point = new Point { x = 1, y = 2}
> translate(p)(p)
rules // types
typeOfType : scope * Type -> TYPE
typeOfType(s, IntT()) = INT().
typeOfType(s, BoolT()) = BOOL().
typeOfType(s, FunT(t1, t2)) =
FUN(typeOfType(s, t1), typeOfType(s, t2)).
typeOfType(s, RecT(x)) = REC(s_rec) :- {p d}
typeOfDecl of Var{x@x}
in s |-> [(p, (d, REC(s_rec)))].
What Else
24
Implementation
!25
type point = {x : num, y : num} in
let mkpoint = fun(x : num) { {x = x, y = x} } in
type color = num in
type colorpoint =
{k : color} extends point in
let addColor =
fun(c : num) {
fun(p : colorpoint) {
({c = c} extends p) : colorpoint
}
} in
(addColor 6 ({c = 5} extends mkpoint 4)) : colorpoint
typeOfExp : scope * Exp -> Type
typeOfExp(s, Num(_)) = NUM().
typeOfExp(s, Plus(e1, e2)) = NUM() :-
typeOfExp(s, e1) == NUM(),
typeOfExp(s, e2) == NUM().
typeOfExp(s, Fun(x, te, e)) = FUN(S, T) :- {s_fun}
typeOfTypeExp(s, te) == S,
new s_fun, s_fun -P-> s,
s_fun -> Var{x@x} with typeOfDecl S,
typeOfExp(s_fun, e) == T.
typeOfExp(s, Var(x)) = T :-
query typeOfDecl filter pathMatch[P*(R|E)*] and { d :- varOrFld(x, d) }
min pathLt[$ < P, $ < R, $ < E, R < P, R < E] and true
in s |-> [(_, (_, T))].
typeOfExp(s, App(e1, e2)) = T :- {S U}
typeOfExp(s, e1) == FUN(S, T),
typeOfExp(s, e2) == U,
subType(U, S).
type point = {x : num, y : num} in
let mkpoint = fun(x : num) { {x = x, y = x} } in
type color = num in
type colorpoint =
{k : color} extends point in
let addColor =
fun(c : num) {
fun(p : colorpoint) {
({c = c} extends p) : colorpoint
}
} in
(addColor 6 ({c = 5} extends mkpoint 4)) : colorpoint
Program
Statix Specification
Typed Program
Solver
package mb.statix.solver;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.immutables.value.Value;
import org.metaborg.util.functions.Predicate1;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import mb.nabl2.terms.ITerm;
import mb.nabl2.terms.ITermVar;
import mb.nabl2.terms.unification.IUnifier;
import mb.nabl2.util.TermFormatter;
import mb.statix.solver.log.IDebugContext;
import mb.statix.solver.log.LazyDebugContext;
import mb.statix.solver.log.Log;
public class Solver {
private Solver() {
}
public static SolverResult solve(final State state, final Iterable<IConstraint> constraints,
final Completeness completeness, final IDebugContext debug) throws InterruptedException {
return solve(state, constraints, completeness, v -> false, s -> false, debug);
}
public static SolverResult solve(final State _state, final Iterable<IConstraint> _constraints,
final Completeness _completeness, Predicate1<ITermVar> isRigid, Predicate1<ITerm> isClosed,
final IDebugContext debug) throws InterruptedException {
debug.info("Solving constraints");
final LazyDebugContext proxyDebug = new LazyDebugContext(debug);
// set-up
final Set<IConstraint> constraints = Sets.newConcurrentHashSet(_constraints);
State state = _state;
Completeness completeness = _completeness;
completeness = completeness.addAll(constraints);
// fixed point
final Set<IConstraint> failed = Sets.newHashSet();
final Log delayedLog = new Log();
final Map<IConstraint, Delay> delays = Maps.newHashMap();
boolean progress = true;
int reduced = 0;
int delayed = 0;
outer: while(progress) {
progress = false;
delayedLog.clear();
delays.clear();
final Iterator<IConstraint> it = constraints.iterator();
while(it.hasNext()) {
if(Thread.interrupted()) {
throw new InterruptedException();
}
final IConstraint constraint = it.next();
proxyDebug.info("Solving {}", constraint.toString(Solver.shallowTermFormatter(state.unifier())));
IDebugContext subDebug = proxyDebug.subContext();
try {
Optional<ConstraintResult> maybeResult =
constraint.solve(state, new ConstraintContext(completeness, isRigid, isClosed, subDebug));
progress = true;
it.remove();
completeness = completeness.remove(constraint);
reduced += 1;
if(maybeResult.isPresent()) {
final ConstraintResult result = maybeResult.get();
state = result.state();
if(!result.constraints().isEmpty()) {
final List<IConstraint> newConstaints = result.constraints().stream()
.map(c -> c.withCause(constraint)).collect(Collectors.toList());
subDebug.info("Simplified to {}", toString(newConstaints, state.unifier()));
constraints.addAll(newConstaints);
completeness = completeness.addAll(newConstaints);
}
} else {
subDebug.error("Failed");
failed.add(constraint);
if(proxyDebug.isRoot()) {
printTrace(constraint, state.unifier(), subDebug);
} else {
proxyDebug.info("Break early because of errors.");
break outer;
}
}
proxyDebug.commit();
} catch(Delay d) {
subDebug.info("Delayed");
delayedLog.absorb(proxyDebug.clear());
delays.put(constraint, d);
delayed += 1;
}
}
}
delayedLog.flush(debug);
debug.info("Solved {} constraints ({} delays) with {} failed and {} remaining constraint(s).", reduced, delayed,
failed.size(), constraints.size());
return SolverResult.of(state, completeness, failed, delays);
}
public static Optional<SolverResult> entails(final State state, final Iterable<IConstraint> constraints,
final Completeness completeness, final IDebugContext debug) throws InterruptedException, Delay {
return entails(state, constraints, completeness, ImmutableSet.of(), debug);
}
public static Optional<SolverResult> entails(final State state, final Iterable<IConstraint> constraints,
final Completeness completeness, final Iterable<ITermVar> _localVars, final IDebugContext debug)
throws InterruptedException, Delay {
debug.info("Checking entailment of {}", toString(constraints, state.unifier()));
final Set<ITermVar> localVars = ImmutableSet.copyOf(_localVars);
final Set<ITermVar> rigidVars = Sets.difference(state.vars(), localVars);
final SolverResult result = Solver.solve(state, constraints, completeness, rigidVars::contains,
state.scopes()::contains, debug.subContext());
if(result.hasErrors()) {
debug.info("Constraints not entailed");
return Optional.empty();
} else if(result.delays().isEmpty()) {
debug.info("Constraints entailed");
return Optional.of(result);
} else {
debug.info("Cannot decide constraint entailment (unsolved constraints)");
throw result.delay(); // FIXME Remove local vars and scopes
}
}
private static void printTrace(IConstraint failed, IUnifier unifier, IDebugContext debug) {
@Nullable IConstraint constraint = failed;
while(constraint != null) {
debug.error(" * {}", constraint.toString(Solver.shallowTermFormatter(unifier)));
constraint = constraint.cause().orElse(null);
}
}
private static String toString(Iterable<IConstraint> constraints, IUnifier unifier) {
final StringBuilder sb = new StringBuilder();
boolean first = true;
for(IConstraint constraint : constraints) {
if(first) {
first = false;
} else {
sb.append(", ");
}
sb.append(constraint.toString(Solver.shallowTermFormatter(unifier)));
}
return sb.toString();
}
@Value.Immutable
public static abstract class ASolverResult {
@Value.Parameter public abstract State state();
@Value.Parameter public abstract Completeness completeness();
@Value.Parameter public abstract Set<IConstraint> errors();
public boolean hasErrors() {
return !errors().isEmpty();
}
@Value.Parameter public abstract Map<IConstraint, Delay> delays();
public Delay delay() {
ImmutableSet.Builder<ITermVar> vars = ImmutableSet.builder();
ImmutableMultimap.Builder<ITerm, ITerm> scopes = ImmutableMultimap.builder();
delays().values().stream().forEach(d -> {
vars.addAll(d.vars());
scopes.putAll(d.scopes());
});
return new Delay(vars.build(), scopes.build());
}
}
public static TermFormatter shallowTermFormatter(final IUnifier unifier) {
return t -> unifier.toString(t, 3);
}
}
Scope Graph
In the paper
- Typing rules for STLC+records, System F, Featherweight Java

- Resolution calculus of scope graphs

- Declarative semantics of Statix

- Description of solver algorithm of Statix

In the artifact
- Implementation of scope graphs and Statix

- Executable specs of STLC+records, System F, Featherweight
Generic Java
!26
Other Contributions

Declarative Type System Specification with Statix

  • 1.
    Eelco Visser WG2.16 |Portland | February 2019 Statix: Declarative Type System Specification Based on: Van Antwerpen, Bach Poulsen, Rouvoet, Visser Scopes as Types PACMPL 2 (OOPSLA), 2018
  • 2.
    Context - Language workbench -High-level language specification - Abstract from implementation concerns (generate automatically) Type systems - Type constraints - Name resolution => scope graphs - Staging !2 Type System Specification
  • 3.
    Scope Graphs !3 0A :MOD(1) B : MOD(2) 1 2 P P I q : BOOLp : BOOL :: : : module A { import B def p : bool = ~q } module B { def q : bool = true } B P* $ < P
  • 4.
  • 5.
    Arithmetic Expressions: Concreteand Abstract Syntax !5 context-free syntax // arithmetic Exp.Int = <<INT>> Exp.Add = <<Exp> + <Exp>> {left} Exp.Sub = <<Exp> - <Exp>> {left} Exp.Mul = <<Exp> * <Exp>> {left} Exp.Eq = <<Exp> == <Exp>> {non-assoc} constructors // arithmetic Int : INT -> Exp Add : Exp * Exp -> Exp Sub : Exp * Exp -> Exp Mul : Exp * Exp -> Exp Eq : Exp * Exp -> Exp SDF3 Statix Program( [Exp( Add(Int("1"), Mul(Int("2"), Int("3"))) )] ) > 1 + 2 * 3
  • 6.
    Arithmetic Expressions: AbstractSyntax !6 > 1 + 2 * 3 constructors // arithmetic Int : INT -> Exp Add : Exp * Exp -> Exp Sub : Exp * Exp -> Exp Mul : Exp * Exp -> Exp Eq : Exp * Exp -> Exp
  • 7.
    Arithmetic Expressions: TypePredicate !7 > 1 + 2 * 3 sorts TYPE constructors INT : TYPE BOOL : TYPE FUN : TYPE * TYPE -> TYPE rules // expressions typeOfExp : scope * Exp -> TYPE typeOfExp(s, Int(i)) = INT(). typeOfExp(s, Add(e1, e2)) = INT() :- typeOfExp(s, e1) == INT(), typeOfExp(s, e2) == INT(). . . . typeOfExp(s, Eq(e1, e2)) = BOOL() :- {T} typeOfExp(s, e1) == T, typeOfExp(s, e2) == T. constructors // arithmetic Int : INT -> Exp Add : Exp * Exp -> Exp Sub : Exp * Exp -> Exp Mul : Exp * Exp -> Exp Eq : Exp * Exp -> Exp
  • 8.
    Variable Definitions !8 sorts Programconstructors Program : list(Decl) -> Program sorts Decl constructors Def : Bind -> Decl Exp : Exp -> Decl sorts Bind constructors Bind : ID * Exp -> Bind TBind : ID * Type * Exp -> Bind relations typeOfDecl : occurrence -> TYPE def a = 0 def b = a + c def b = 1 + d def c : Int = 0 def e : Bool = 1 > a + b + c
  • 9.
    Variable Definitions !9 sorts Programconstructors Program : list(Decl) -> Program sorts Decl constructors Def : Bind -> Decl Exp : Exp -> Decl sorts Bind constructors Bind : ID * Exp -> Bind TBind : ID * Type * Exp -> Bind relations typeOfDecl : occurrence -> TYPE rules programOK : Program programOK(Program(decls)) :- {s} new s, declsOk(s, decls). rules // declarations declOk : scope * Decl declsOk maps declOk(*, list(*)) declOk(s, Def(bind)) :- bindOk(s, s, bind). declOk(s, Exp(e)) :- {T} typeOfExp(s, e) == T. def a = 0 def b = a + c def b = 1 + d def c : Int = 0 def e : Bool = 1 > a + b + c
  • 10.
    Variable Definitions: Declarations !10 sortsProgram constructors Program : list(Decl) -> Program sorts Decl constructors Def : Bind -> Decl Exp : Exp -> Decl sorts Bind constructors Bind : ID * Exp -> Bind TBind : ID * Type * Exp -> Bind relations typeOfDecl : occurrence -> TYPE def a = 0 def b = a + c def b = 1 + d def c : Int = 0 def e : Bool = 1 > a + b + c rules // bindings bindOk : scope * scope * Bind bindsOk maps bindOk(*, *, list(*)) bindOk(s_bnd, s_ctx, Bind(x, e)) :- {T} typeOfExp(s_ctx, e) == T, s_bnd -> Var{x@x} with typeOfDecl T. bindOk(s_bnd, s_ctx, TBind(x, t, e)) :- {T} typeOfType(s_ctx, t) == T, typeOfExp(s_ctx, e) == T, s_bnd -> Var{x@x} with typeOfDecl T.
  • 11.
    Variable Definitions: NameResolution !11 rules // bindings bindOk : scope * scope * Bind bindsOk maps bindOk(*, *, list(*)) bindOk(s_bnd, s_ctx, Bind(x, e)) :- {T} typeOfExp(s_ctx, e) == T, s_bnd -> Var{x@x} with typeOfDecl T. bindOk(s_bnd, s_ctx, TBind(x, t, e)) :- {T} typeOfType(s_ctx, t) == T, typeOfExp(s_ctx, e) == T, s_bnd -> Var{x@x} with typeOfDecl T. rules // variables typeOfExp(s, Var(x)) = T :- {p d } typeOfDecl of Var{x@x} in s |-> [(p, (d, T))]. sorts Program constructors Program : list(Decl) -> Program sorts Decl constructors Def : Bind -> Decl Exp : Exp -> Decl sorts Bind constructors Bind : ID * Exp -> Bind TBind : ID * Type * Exp -> Bind relations typeOfDecl : occurrence -> TYPE def a = 0 def b = a + c def b = 1 + d def c : Int = 0 def e : Bool = 1 > a + b + c
  • 12.
    Lexical Scope: Functions !12 rules// functions typeOfExp(s, Fun(x, t, e)) = FUN(T, S) :- {s_fun} typeOfType(s, t) == T, new s_fun, s_fun -P-> s, s_fun -> Var{x@x} with typeOfDecl T, typeOfExp(s_fun, e) == S. typeOfExp(s, App(e1, e2)) = T :- {S} typeOfExp(s, e1) == FUN(S, T), typeOfExp(s, e2) == S. sorts TYPE constructors INT : TYPE BOOL : TYPE FUN : TYPE * TYPE -> TYPE def i = 3 def inc = fun(x : Int) { x + i } > inc 2 constructors // functions Fun : ID * Type * Exp -> Exp App : Exp * Exp -> Exp
  • 13.
    Lexical Scope: SequentialLet !13 rules // let bindings typeOfExp(s, Let(binds, e)) = T :- {s_let} new s_let, sbindsOk(s, s_let, binds), typeOfExp(s_let, e) == T. def a = 0 def b = 1 def c = 2 > let a = c; b = a; c = b in a + b + c rules // bindings sbindsOk : scope * scope * list(Bind) sbindsOk(s, s_fin, []) :- s_fin -P-> s. sbindsOk(s, s_fin, [bind | binds]) :- {s_mid} new s_mid, s_mid -P-> s, bindOk(s_mid, s, bind), sbindsOk(s_mid, s_fin, binds).
  • 14.
    Lexical Scope: ParallelLet !14 rules // let bindings typeOfExp(s, LetPar(binds, e)) = T :- {s_let} new s_let, s_let -P-> s, bindsOk(s_let, s, binds), typeOfExp(s_let, e) == T. bindOk : scope * scope * Bind bindsOk maps bindOk(*, *, list(*)) bindOk(s_bnd, s_ctx, Bind(x, e)) :- {T} typeOfExp(s_ctx, e) == T, s_bnd -> Var{x@x} with typeOfDecl T. def a = 0 def b = 1 def c = 2 > letpar a = c; b = a; c = b in a + b + c
  • 15.
    Lexical Scope: RecursiveLet !15 rules // let bindings typeOfExp(s, LetRec(binds, e)) = T :- {s_let} new s_let, s_let -P-> s, bindsOk(s_let, s_let, binds), typeOfExp(s_let, e) == T. bindOk : scope * scope * Bind bindsOk maps bindOk(*, *, list(*)) bindOk(s_bnd, s_ctx, Bind(x, e)) :- {T} typeOfExp(s_ctx, e) == T, s_bnd -> Var{x@x} with typeOfDecl T. > letrec odd = fun(x : Int) { if x == 0 then false else even(x - 1) }; even = fun(x : Int) { if x == 0 then true else odd(x - 1) } in even(3)
  • 16.
    Modules !16 module A { importB def a = 4 def c = b + 4 } module B { import A def b = a + 3 }
  • 17.
    Modules: Scopes asTypes !17 rules // modules declOk(s, Module(m, decls)) :- {s_mod} new s_mod, s_mod -P-> s, s -> Mod{m@m} with typeOfDecl MOD(s_mod), declsOk(s_mod, decls). declOk(s, Import(m)) :- {p d s_mod} typeOfDecl of Mod{m@m} in s |-> [(p, (d, MOD(s_mod)))], s -I-> s_mod. module A { import B def a = 4 def c = b + 4 } module B { import A def b = a + 3 } sorts TYPE constructors INT : TYPE BOOL : TYPE FUN : TYPE * TYPE -> TYPE MOD : scope -> TYPE name-resolution labels P I R resolve Var filter pathMatch[P* (R* | I*)] min pathLt[$ < I, $ < P, I < P, R < P] resolve Mod filter pathMatch[P P* I*] min pathLt[$ < I, $ < P, I < P, R < P]
  • 18.
    Records !18 record Point { x: Int y : Int } def p : Point = new Point { x = 1, y = 2} > p.x + p.y def z = 3 > with p do x + y + z
  • 19.
    Record Type Declaration:Scopes as Types !19 rules // record type declOk(s, Record(x, fdecls)) :- {s_rec} new s_rec, fdeclsOk(s_rec, s, fdecls), s -> Var{x@x} with typeOfDecl REC(s_rec). fdeclOk : scope * scope * FDecl fdeclsOk maps fdeclOk(*, *, list(*)) fdeclOk(s_bnd, s_ctx, FDecl(x, t)) :- {T} typeOfType(s_ctx, t) == T, s_bnd -> Var{x@x} with typeOfDecl T. sorts TYPE constructors INT : TYPE BOOL : TYPE FUN : TYPE * TYPE -> TYPE REC : scope -> TYPE record Point { x : Int y : Int } def p : Point = new Point { x = 1, y = 2} > p.x + p.y def z = 3 > with p do x + y + z
  • 20.
    Record Literals !20 rules //records construction typeOfExp(s, New(x, fbinds)) = REC(s_rec) :- {p d} typeOfDecl of Var{x@x} in s |-> [(p, (d, REC(s_rec)))], fbindsOk(s, s_rec, fbinds). fbindOk : scope * scope * FBind fbindsOk maps fbindOk(*, *, list(*)) fbindOk(s, s_rec, FBind(x, e)) :- {p d T} typeOfExp(s, e) == T, typeOfDecl of Var{x@x} in s_rec |-> [(p, (d, T))]. record Point { x : Int y : Int } def p : Point = new Point { x = 1, y = 2} > p.x + p.y def z = 3 > with p do x + y + z sorts TYPE constructors INT : TYPE BOOL : TYPE FUN : TYPE * TYPE -> TYPE REC : scope -> TYPE
  • 21.
    Record Projection !21 rules //record projection typeOfExp(s, Proj(e, x)) = T :- {p d s_rec S} typeOfExp(s, e) == S, proj(S, x) == T. proj : TYPE * ID -> TYPE proj(REC(s_rec), x) = T :- {p d} typeOfDecl of Var{x@x} in s_rec |-> [(p, (d, T))]. record Point { x : Int y : Int } def p : Point = new Point { x = 1, y = 2} > p.x + p.y def z = 3 > with p do x + y + z sorts TYPE constructors INT : TYPE BOOL : TYPE FUN : TYPE * TYPE -> TYPE REC : scope -> TYPE
  • 22.
    With Record !22 rules //with record value typeOfExp(s, With(e1, e2)) = T :- {s_with s_rec} typeOfExp(s, e1) == REC(s_rec), new s_with, s_with -P-> s, s_with -R-> s_rec, typeOfExp(s_with, e2) == T. record Point { x : Int y : Int } def p : Point = new Point { x = 1, y = 2} > p.x + p.y def z = 3 > with p do x + y + z sorts TYPE constructors INT : TYPE BOOL : TYPE FUN : TYPE * TYPE -> TYPE REC : scope -> TYPE name-resolution labels P I R resolve Var filter pathMatch[P* (R* | I*)] min pathLt[$ < I, $ < P, I < P, R < P]
  • 23.
    Type References !23 record Point{ x : Int y : Int } def translate : Point -> Point -> Point = fun(p: Point){ fun(d: Point) { new Point{ x = p.x + d.x, y = p.y + d.y } } } def p : Point = new Point { x = 1, y = 2} > translate(p)(p) rules // types typeOfType : scope * Type -> TYPE typeOfType(s, IntT()) = INT(). typeOfType(s, BoolT()) = BOOL(). typeOfType(s, FunT(t1, t2)) = FUN(typeOfType(s, t1), typeOfType(s, t2)). typeOfType(s, RecT(x)) = REC(s_rec) :- {p d} typeOfDecl of Var{x@x} in s |-> [(p, (d, REC(s_rec)))].
  • 24.
  • 25.
    Implementation !25 type point ={x : num, y : num} in let mkpoint = fun(x : num) { {x = x, y = x} } in type color = num in type colorpoint = {k : color} extends point in let addColor = fun(c : num) { fun(p : colorpoint) { ({c = c} extends p) : colorpoint } } in (addColor 6 ({c = 5} extends mkpoint 4)) : colorpoint typeOfExp : scope * Exp -> Type typeOfExp(s, Num(_)) = NUM(). typeOfExp(s, Plus(e1, e2)) = NUM() :- typeOfExp(s, e1) == NUM(), typeOfExp(s, e2) == NUM(). typeOfExp(s, Fun(x, te, e)) = FUN(S, T) :- {s_fun} typeOfTypeExp(s, te) == S, new s_fun, s_fun -P-> s, s_fun -> Var{x@x} with typeOfDecl S, typeOfExp(s_fun, e) == T. typeOfExp(s, Var(x)) = T :- query typeOfDecl filter pathMatch[P*(R|E)*] and { d :- varOrFld(x, d) } min pathLt[$ < P, $ < R, $ < E, R < P, R < E] and true in s |-> [(_, (_, T))]. typeOfExp(s, App(e1, e2)) = T :- {S U} typeOfExp(s, e1) == FUN(S, T), typeOfExp(s, e2) == U, subType(U, S). type point = {x : num, y : num} in let mkpoint = fun(x : num) { {x = x, y = x} } in type color = num in type colorpoint = {k : color} extends point in let addColor = fun(c : num) { fun(p : colorpoint) { ({c = c} extends p) : colorpoint } } in (addColor 6 ({c = 5} extends mkpoint 4)) : colorpoint Program Statix Specification Typed Program Solver package mb.statix.solver; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import javax.annotation.Nullable; import org.immutables.value.Value; import org.metaborg.util.functions.Predicate1; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import mb.nabl2.terms.ITerm; import mb.nabl2.terms.ITermVar; import mb.nabl2.terms.unification.IUnifier; import mb.nabl2.util.TermFormatter; import mb.statix.solver.log.IDebugContext; import mb.statix.solver.log.LazyDebugContext; import mb.statix.solver.log.Log; public class Solver { private Solver() { } public static SolverResult solve(final State state, final Iterable<IConstraint> constraints, final Completeness completeness, final IDebugContext debug) throws InterruptedException { return solve(state, constraints, completeness, v -> false, s -> false, debug); } public static SolverResult solve(final State _state, final Iterable<IConstraint> _constraints, final Completeness _completeness, Predicate1<ITermVar> isRigid, Predicate1<ITerm> isClosed, final IDebugContext debug) throws InterruptedException { debug.info("Solving constraints"); final LazyDebugContext proxyDebug = new LazyDebugContext(debug); // set-up final Set<IConstraint> constraints = Sets.newConcurrentHashSet(_constraints); State state = _state; Completeness completeness = _completeness; completeness = completeness.addAll(constraints); // fixed point final Set<IConstraint> failed = Sets.newHashSet(); final Log delayedLog = new Log(); final Map<IConstraint, Delay> delays = Maps.newHashMap(); boolean progress = true; int reduced = 0; int delayed = 0; outer: while(progress) { progress = false; delayedLog.clear(); delays.clear(); final Iterator<IConstraint> it = constraints.iterator(); while(it.hasNext()) { if(Thread.interrupted()) { throw new InterruptedException(); } final IConstraint constraint = it.next(); proxyDebug.info("Solving {}", constraint.toString(Solver.shallowTermFormatter(state.unifier()))); IDebugContext subDebug = proxyDebug.subContext(); try { Optional<ConstraintResult> maybeResult = constraint.solve(state, new ConstraintContext(completeness, isRigid, isClosed, subDebug)); progress = true; it.remove(); completeness = completeness.remove(constraint); reduced += 1; if(maybeResult.isPresent()) { final ConstraintResult result = maybeResult.get(); state = result.state(); if(!result.constraints().isEmpty()) { final List<IConstraint> newConstaints = result.constraints().stream() .map(c -> c.withCause(constraint)).collect(Collectors.toList()); subDebug.info("Simplified to {}", toString(newConstaints, state.unifier())); constraints.addAll(newConstaints); completeness = completeness.addAll(newConstaints); } } else { subDebug.error("Failed"); failed.add(constraint); if(proxyDebug.isRoot()) { printTrace(constraint, state.unifier(), subDebug); } else { proxyDebug.info("Break early because of errors."); break outer; } } proxyDebug.commit(); } catch(Delay d) { subDebug.info("Delayed"); delayedLog.absorb(proxyDebug.clear()); delays.put(constraint, d); delayed += 1; } } } delayedLog.flush(debug); debug.info("Solved {} constraints ({} delays) with {} failed and {} remaining constraint(s).", reduced, delayed, failed.size(), constraints.size()); return SolverResult.of(state, completeness, failed, delays); } public static Optional<SolverResult> entails(final State state, final Iterable<IConstraint> constraints, final Completeness completeness, final IDebugContext debug) throws InterruptedException, Delay { return entails(state, constraints, completeness, ImmutableSet.of(), debug); } public static Optional<SolverResult> entails(final State state, final Iterable<IConstraint> constraints, final Completeness completeness, final Iterable<ITermVar> _localVars, final IDebugContext debug) throws InterruptedException, Delay { debug.info("Checking entailment of {}", toString(constraints, state.unifier())); final Set<ITermVar> localVars = ImmutableSet.copyOf(_localVars); final Set<ITermVar> rigidVars = Sets.difference(state.vars(), localVars); final SolverResult result = Solver.solve(state, constraints, completeness, rigidVars::contains, state.scopes()::contains, debug.subContext()); if(result.hasErrors()) { debug.info("Constraints not entailed"); return Optional.empty(); } else if(result.delays().isEmpty()) { debug.info("Constraints entailed"); return Optional.of(result); } else { debug.info("Cannot decide constraint entailment (unsolved constraints)"); throw result.delay(); // FIXME Remove local vars and scopes } } private static void printTrace(IConstraint failed, IUnifier unifier, IDebugContext debug) { @Nullable IConstraint constraint = failed; while(constraint != null) { debug.error(" * {}", constraint.toString(Solver.shallowTermFormatter(unifier))); constraint = constraint.cause().orElse(null); } } private static String toString(Iterable<IConstraint> constraints, IUnifier unifier) { final StringBuilder sb = new StringBuilder(); boolean first = true; for(IConstraint constraint : constraints) { if(first) { first = false; } else { sb.append(", "); } sb.append(constraint.toString(Solver.shallowTermFormatter(unifier))); } return sb.toString(); } @Value.Immutable public static abstract class ASolverResult { @Value.Parameter public abstract State state(); @Value.Parameter public abstract Completeness completeness(); @Value.Parameter public abstract Set<IConstraint> errors(); public boolean hasErrors() { return !errors().isEmpty(); } @Value.Parameter public abstract Map<IConstraint, Delay> delays(); public Delay delay() { ImmutableSet.Builder<ITermVar> vars = ImmutableSet.builder(); ImmutableMultimap.Builder<ITerm, ITerm> scopes = ImmutableMultimap.builder(); delays().values().stream().forEach(d -> { vars.addAll(d.vars()); scopes.putAll(d.scopes()); }); return new Delay(vars.build(), scopes.build()); } } public static TermFormatter shallowTermFormatter(final IUnifier unifier) { return t -> unifier.toString(t, 3); } } Scope Graph
  • 26.
    In the paper -Typing rules for STLC+records, System F, Featherweight Java - Resolution calculus of scope graphs - Declarative semantics of Statix - Description of solver algorithm of Statix In the artifact - Implementation of scope graphs and Statix - Executable specs of STLC+records, System F, Featherweight Generic Java !26 Other Contributions