Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Declarative Type System Specification with Statix

291 views

Published on

In this talk I present the design of Statix, a new constraint-based language for the executable specification of type systems. Statix specifications consist of predicates that define the well-formedness of language constructs in terms of built-in and user-defined constraints. Statix has a declarative semantics that defines whether a model satisfies a constraint. The operational semantics of Statix is defined as a sound constraint solving algorithm that searches for a solution for a constraint. The aim of the design is that Statix users can ignore the execution order of constraint solving and think in terms of the declarative semantics.

A distinctive feature of Statix is its use of scope graphs, a language parametric framework for the representation and querying of the name binding facts in programs. Since types depend on name resolution and name resolution may depend on types, it is typically not possible to construct the entire scope graph of a program before type constraint resolution. In (algorithmic) type system specifications this leads to explicit staging of the construction and querying of the type environment (class table, symbol table). Statix automatically stages the construction of the scope graph of a program such that queries are never executed when their answers may be affected by future scope graph extension. In the talk, I will explain the design of Statix by means of examples.

https://eelcovisser.org/post/309/declarative-type-system-specification-with-statix

Published in: Software
  • Be the first to comment

  • Be the first to like this

Declarative Type System Specification with Statix

  1. 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. 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. 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. 4. Statix By Example 4
  5. 5. 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
  6. 6. 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
  7. 7. 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
  8. 8. 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
  9. 9. 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
  10. 10. 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.
  11. 11. 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
  12. 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. 13. 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).
  14. 14. 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
  15. 15. 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)
  16. 16. Modules !16 module A { import B def a = 4 def c = b + 4 } module B { import A def b = a + 3 }
  17. 17. 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]
  18. 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. 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. 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. 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. 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. 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. 24. What Else 24
  25. 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. 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

×