www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

invariants-phantom.hl.rkt (30279B)


      1 #lang aful/unhygienic hyper-literate type-expander/lang
      2 
      3 @require[scribble-math
      4          scribble-enhanced/doc
      5          "notations.rkt"]
      6 
      7 @title[#:style (with-html5 manual-doc-style)
      8        #:tag "inv-phantom"
      9        #:tag-prefix "phc-graph/inv-phantom"]{Tracking checked contracts
     10  via refinement types}
     11 
     12 @(chunks-toc-prefix
     13   '("(lib phc-graph/scribblings/phc-graph-implementation.scrbl)"
     14     "phc-graph/inv-phantom"))
     15 
     16 @section{Introduction}
     17 
     18 The cautious compiler writer will no doubt want to check that the Abstract
     19 Syntax Tree or Graph used to represent the program verifies some structural
     20 properties. For example, the compiled language might not allow cycles between
     21 types. Another desirable property is that the @racket[in-method] field of the
     22 node representing an instruction points back to the method containing it. We
     23 will use this second property as a running example in this section.
     24 
     25 @section{Implementation overview : subtyping, variance and phantom types}
     26 
     27 It is possible to express with Typed/Racket that a @racket[Method] should
     28 contain a list of @racket[Instruction]s, and that @racket[Instruction]s should
     29 point to a @racket[Method]@note{We are not concerned here about the ability to
     30  create such values, which necessarily contain some form of cycle. The goal of
     31  the graph library is indeed to handle the creation and traversal of such
     32  cyclic data structures in a safe way}:
     33 
     34 @chunk[<invariant-1>
     35        (struct Instruction ([opcode : Byte]
     36                             [in-method : Method]))
     37        (struct Method ([body : (Listof Instruction)]))]
     38 
     39 This type does not, however, encode the fact that an instruction should point
     40 to the method containing it. Typed/Racket does not really have a notion of
     41 singleton types, aside from symbols and other primitive data. It also lacks a
     42 way to type "the value itself" (e.g. to describe a single-field structure
     43 pointing to itself, possibly via a @racket[Promise]). This means that the
     44 property could only be expressed in a rather contrived way, if it is at all
     45 possible.
     46 
     47 @; (define-type Self-Loop (∀ (A) (→ (Pairof Integer (Self-Loop A)))))
     48 
     49 We decide to rely instead on a run-time check, i.e. a sort of contract which
     50 checks the structural invariant on the whole graph. In order to let the
     51 type-checker know whether a value was checked against that contract or not, we
     52 include within the node a phantom type which is used as a flag, indicating
     53 that the graph was checked against that contract. This phantom type in a sense
     54 refines the node type, indicating an additional property (which, in our case,
     55 is not checked at compile-time but instead enforced at run-time).
     56 
     57 @chunk[<invariant-2>
     58        (struct (Flag) Instruction ([opcode : Byte]
     59                                    [in-method : (Method Flag)]))
     60        (struct (Flag) Method ([body : (Listof (Instruction Flag))]))]
     61 
     62 We would then write a function accepting a @racket[Method] for which the
     63 contract @racket[method→instruction→same-method] was checked like this:
     64 
     65 @chunk[<invariant-2-use>
     66        (λ ([m : (Method 'method→instruction→same-method)])
     67          …)]
     68 
     69 Unfortunately, this attempt fails to catch errors as one would expect, because
     70 Typed/Racket discards unused polymorphic arguments, as can be seen in the
     71 following example, which type-checks without any complaint:
     72 
     73 @chunk[<phantom-types-ignored>
     74        (struct (Phantom) S ([x : Integer]))
     75        (define inst-sa : (S 'a) (S 1))
     76        (ann inst-sa (S 'b))]
     77 
     78 We must therefore make a field with the @racket[Flag] type actually appear
     79 within the instance:
     80 
     81 @chunk[<invariant-3>
     82        (struct (Flag) Instruction ([opcode : Byte]
     83                                    [in-method : (Method Flag)]
     84                                    [flag : Flag]))
     85        (struct (Flag) Method ([body : (Listof (Instruction Flag))]
     86                               [flag : Flag]))]
     87 
     88 Another issue is that the flag can easily be forged. We would therefore like
     89 to wrap it in a struct type which is only accessible by the graph library:
     90 
     91 @chunk[<invariant-4>
     92        (struct (Flag) Flag-Wrapper-Struct ([flag : Flag]))
     93        (define-type Flag-Wrapper Flag-Wrapper-Struct)
     94        (code:comment "provide only the type, not the constructor or accessor")
     95        (provide Flag-Wrapper)]
     96 
     97 We would like to be able to indicate that a graph node has validated several
     98 invariants. For that, we need a way to represent the type of a "set" of
     99 invariant witnesses. We also want some subtyping relationship between the
    100 sets: a set @${s₁} with more invariant witnesses should be a subtype of a
    101 subset @${s₂ ⊆ s₁}. We can order the invariant witnesses and use @racket[Rec]
    102 to build the type of a list of invariant witnesses, where some may be missing:
    103 
    104 @chunk[<invariant-set-as-List+Rec>
    105        (define-type At-Least-InvB+InvD
    106          (Rec R₁ (U (Pairof Any R₁)
    107                     (Pairof 'InvB (Rec R₂ (U (Pairof Any R₂)
    108                                              (Pairof 'InvD (Listof Any))))))))]
    109 
    110 @chunk[<invariant-set-as-List+Rec-use>
    111        (ann '(InvA InvB InvC InvD InvE) At-Least-InvB+InvD)
    112        (ann '(InvB InvD) At-Least-InvB+InvD)
    113        (code:comment "Rejected, because it lacks 'InvD")
    114        (code:comment "(ann '(InvB InvC InvE) At-Least-InvB+InvD)")
    115        (code:comment "The elements must be in the right order,")
    116        (code:comment "this would be rejected by the typechecker:")
    117        (code:comment "(ann '(InvD InvB) At-Least-InvB+InvD)")]
    118 
    119 Another solution is to group the witnesses in an untagged union with
    120 @racket[U], and place it in a contravariant position:
    121 
    122 @chunk[<invariant-set-as-contravariant-U>
    123        (define-type At-Least-InvB+InvD
    124          (→ (U 'InvB 'InvD) Void))]
    125 
    126 In the case where no invariant is present in the untagged union, the type
    127 @racket[(U)] a.k.a @racket[Nothing], the bottom type with no value, would
    128 appear. This type is somewhat pathological and allows absurd reasoning (a
    129 function accepting @racket[Nothing] can never be called, which may incite the
    130 type checker to perform excessive elision). To avoid any pitfalls, we will
    131 systematically include a dummy element @racket[Or] in the union, to make sure
    132 the union never becomes empty.
    133 
    134 This solution also has the advantage that the size of the run-time witness is
    135 constant, and does not depend on the number of checked contracts (unlike the
    136 representation using a list). In practice the function should never be called.
    137 It can however simply be implemented, in a way which will match all witness
    138 types, as a function accepting anything and returning void.
    139 
    140 In addition to testifying that a graph node was checked against multiple,
    141 separate contracts, there might be some contracts which check stronger
    142 properties than others. A way to encode this relationship in the type system
    143 is to have subtyping relationships between the contract witnesses, so that
    144 @; TODO: get rid of the textit
    145 @${\textit{P}₁(x) ⇒ \textit{P}₂(x) ⇒ \textit{Inv}₁ @texsubtype \textit{Inv}₂}:
    146 
    147 @chunk[<invariant-contract-subtyping>
    148        (struct InvWeak ())
    149        (struct InvStrong InvWeak ())]
    150 
    151 If the witnesses must appear in a contravariant position (when using
    152 @racket[U] to group them), the relationship must be reversed:
    153 
    154 @chunk[<invariant-contract-subtyping>
    155        (struct InvStrongContra ())
    156        (struct InvWeakContra InvStrongContra ())]
    157 
    158 Alternatively, it is possible to use a second contravariant position to
    159 reverse the subtyping relationship again:
    160 
    161 @chunk[<invariant-contract-subtyping>
    162        (struct InvWeak ())
    163        (struct InvStrong InvWeak ())
    164 
    165        (define InvWeakContra (→ InvWeak Void))
    166        (define InvStrongContra (→ InvStrong Void))]
    167 
    168 Finally, we note that the invariants should always be represented using a
    169 particular struct type, instead of using a symbol, so that name clashes are
    170 not a problem.
    171 
    172 @section{Encoding property implication as subtyping}
    173 
    174 The witness for a strong property should be a subtype of the witness for a
    175 weaker property. This allows a node with a strong property to be passed where
    176 a node with a weaker property is passed.
    177 
    178 @chunk[<structural-draft>
    179        (code:comment "Draft ideas")
    180        
    181        (struct inv∈ ())
    182        (struct inv≡ ())
    183        (struct inv∉ ())
    184 
    185        ;(List Rel From Path1 Path2)
    186        (List ≡ ANodeName (List f1 f2) (List))
    187        (List ∈ ANodeName (List f1 f2) (List))
    188        (List ∉ ANodeName (List f1 f2) (List))
    189        (List ∉ ANodeName (List (* f1 f2 f3 f4) (* f5 f6)) (List))
    190 
    191        ;(List From Path+Rel)
    192        (List ANodeName (List f1 f2 ≡))
    193        (List ANodeName (List f1 f2 ∈))
    194        (List ANodeName (List f1 f2 ∉))
    195        (List ANodeName (List (List f1 ∉)
    196                              (List f2 ∉)
    197                              (List f3 ∉)
    198                              (List f4
    199                                    (List f5 ∉)
    200                                    (List f6 ∉))))
    201        
    202        ;; How to make it have the right kind of subtyping?
    203 
    204        
    205        ]
    206 
    207 @subsection{Properties applying to all reachable nodes from @racket[x]}
    208 
    209 The property @racket[x ≢ x.**] can be expanded to a series of properties. For
    210 example, if @racket[x] has two fields @racket[a] and @racket[d], the former
    211 itself having two fields @racket[b] and @racket[c], and the latter having a
    212 field @racket[e], itself with a field @racket[f]:
    213 @chunk[<expanded-path-set>
    214        (x ≢ x.a)
    215        (x ≢ x.a.b)
    216        (x ≢ x.a.c)
    217        (x ≢ x.d)
    218        (x ≢ x.d.e)
    219        (x ≢ x.d.e.f)]
    220 
    221 @subsection{Prefix trees to the rescue}
    222 
    223 This expanded representation is however costly, and can be expressed more
    224 concisely by factoring out the prefixes.
    225 
    226 @chunk[<prefixes>
    227        (x ≢ (x (a (b) (c))
    228                (d (e (f)))))]
    229 
    230 One thing which notably cannot be represented concisely in this way is
    231 @racket[x.a.** ≢ x.b.**], meaning that the subgraphs rooted at @racket[x.a]
    232 and @racket[x.b] are disjoint. It would be possible to have a representation
    233 combining a prefix-tree on the left, and a prefix-tree on the right, implying
    234 the cartesian product of both sets of paths. This has a negligible cost in the
    235 size of the type for the case where one of the members of the cartesian
    236 product, as we end up with the following (the left-hand-side @racket[x] gains
    237 an extra pair of parentheses, because it is now an empty tree):
    238 
    239 @chunk[<prefixes>
    240        ((x) ≢ (x (a (b) (c))
    241                  (d (e (f)))))]
    242 
    243 This does not allow concise expression of all properties, i.e. this is a form
    244 of compression, which encodes concisely likely sets of pairs of paths, but is
    245 of little help for more random properties. For example, if a random subset of
    246 the cartesian product of reachable paths is selected, there is no obvious way
    247 to encode it in a significantly more concise way than simply listing the pairs
    248 of paths one by one.
    249 
    250 @subsection{Cycles in properties}
    251 
    252 If a @racket[**] path element (i.e. a set of paths representing any path of
    253 any length) corresponds to a part of the graph which contains a cycle in the
    254 type, it is necessary to make that cycle appear in the expanded form. For
    255 that, we use @racket[Rec]. Supposing that the node @racket[x] has two fields,
    256 @racket[a] and @racket[c], the first itself having a field @racket[b] of type
    257 @racket[x]. We would expand @racket[x.**] to the following shape:
    258 
    259 @racket[(Rec X (≢ (Node0 'x)
    260                   (Node2 'x
    261                          (Field1 'a (Field1 'b (Field1 X)))
    262                          (Field1 'c))))]
    263 
    264 If one of the fields refers not to the root, but to 
    265 
    266 TODO: distinction between root nodes and fields in the path. Add an @racket[ε]
    267 component at the root of each path?
    268 
    269 @subsection{Partial paths}
    270 
    271 Partial paths: if a property holds between @racket[x.a] and @racket[x.b], then
    272 it is stronger than a property which holds between @racket[y.fx.a] and
    273 @racket[y.fx.b] (i.e. the common prefix path narrows down the set of pairs of
    274 values which are related by the property).
    275 
    276 A possible solution idea: mask the "beginning" of the path with a @racket[∀]
    277 or @racket[Any]. Either use
    278 @racket[(Rec Ign (U (Field1 Any Ign) Actual-tail-of-type))], or reverse the
    279 ``list'', so that one writes @racket[(Field1 'b (Field1 'a Any))], i.e. we
    280 have @racket[(Field1 field-name up)] instead of
    281 @racket[(Field1 field-name children)]. The problem with the reversed version
    282 is that two child fields @racket[b] and @racket[c] need to refer to the same
    283 parent @racket[a], which leads to duplication or naming (in the case of
    284 naming, Typed/Racket tends to perform some inlining anyway, except if tricks
    285 are used to force the type to be recursive (in which case the subtyping / type
    286 matching is less good and fails to recognise weaker or equivalent formulations
    287 of the type)). The problem with the @racket[Rec] solution for an ignored head
    288 of any length is that the number of fields is not known in advance (but
    289 hopefully our representation choices to allow weaker properties with missing
    290 fields could make this a non-problem?).
    291 
    292 @subsection{Array and list indices}
    293 
    294 When a path reaches an array, list, set or another similar collection, the
    295 special path element @racket[*] can be used to indicate ``any element in the
    296 array or list''. Specific indices can be indicated by an integer, or for lists
    297 with @racket[car], @racket[first], @racket[second], @racket[third] and so on.
    298 The special path elements @racket[cdr] and @racket[rest] access the rest of
    299 the list, i.e. everything but the first element.
    300 
    301 @subsection{Other richer properties}
    302 
    303 Other richer properties can be expressed, like
    304 @racket[x.len = (length x.somelist)]. This property calls some racket
    305 primitives (@racket[length]), and compares numeric values. However, we do not
    306 attempt to make the type checker automatically recognise weaker or equivalent
    307 properties. Instead, we simply add to the phantom type a literal description
    308 of the checked property, which will only match the same exact property.
    309 
    310 @section{Implementation}
    311 
    312 @subsection{The witness value}
    313 
    314 Since all witnesses will have a type of the form
    315 @racket[(→ (U (→ invᵢ Void) …) Void)], they can all be represented at run-time
    316 by a single value: a function accepting any argument and returning
    317 @racket[Void]. Note that the type of the witness is normally a phantom type,
    318 and an actual value is supplied only because Typed/Racket drops phantom types
    319 before typechecking, as mentioned earlier.
    320 
    321 @chunk[<witness-value>
    322        (: witness-value (→ Any Void))
    323        (define witness-value (λ (x) (void)))]
    324 
    325 @subsection{Grouping multiple invariants}
    326 
    327 As mentioned earlier, we group invariants together using an untagged union
    328 @racket[U], which must appear in a contravariant position. We wish to express
    329 witnesses for stronger invariants as subtypes of witnesses for weaker
    330 invariants, and therefore use a second nested function type to flip again the
    331 variance direction. We always include the @racket[Or] element in the union, to
    332 avoid ever having an empty union.
    333 
    334 @chunk[<Or>
    335        (struct Or ())]
    336 
    337 @chunk[<grouping-invariants>
    338        (define-type-expander (Invariants stx)
    339          (syntax-case stx ()
    340            [(_ invᵢ …)
    341             #'(→ (U Or (→ invᵢ Void) …) Void)
    342             #;#'(→ (→ (∩ invᵢ …) Void) Void)]))]
    343 
    344 @subsection{Structural (in)equality and (non-)membership invariants}
    345 
    346 @subsubsection{Invariants and their relationships}
    347 
    348 We design our typing hierarchy to allow encoding the equality, inequality,
    349 membership and non-membership between paths. A simple example would be
    350 the property @racket[:A.b.c ≡ :A.d.e], which would hold for all nodes of type
    351 @racket[A].
    352 
    353 These paths patterns form a suffix of actual paths. Let @${S₁} and @${S₂} be
    354 two sets of pairs of suffixes. If the set of pairs of actual paths covered by
    355 @${S₁} is a superset the set of pairs of actual paths covered by @${S₂}, then
    356 @${S₁} can be used to express a property over more pairs of actual paths,
    357 and the resulting property on the graph as a whole is therefore more precise.
    358 
    359 Our implementation allows the concise expression of a set of paths using a
    360 template within which sections of the path may be repeated any number of
    361 times. For example, the template @racket[:A.b(.c)*.d] corresponds to the set of
    362 paths containing @racket[:A.b.d], @racket[:A.b.c.d], @racket[:A.b.c.c.d] and so
    363 on.
    364 
    365 When path elements may produce a value whose type is a variant (i.e. a union
    366 of several constructors), it can be necessary to distinguish which
    367 constructor(s) the path applies to. We use a syntax inspired from that of
    368 @racketmodname[syntax/parse] for that purpose. Any point in a path can be
    369 followed by @racket[:node-name], which effectively refines the set of actual
    370 paths so that it contains only paths where the value at that point is of the
    371 given type. The syntax @racket[:A.b.c] therefore indicates that the path must
    372 start on an element of type @racket[A], and follow its fields @racket[b] then
    373 @racket[c]. The syntax @racket[.x:T.y.z] indicates paths @racket[.x.y.z], where
    374 the element accessed by @racket[.x] has the type @racket[T].
    375 
    376 The @racket[?] path element indicates that any field can be used at the given
    377 point. The syntax @racket[.x.?.y] therefore indicates the paths
    378 @racket[.x.f.y], @racket[.x.g.y], @racket[.x.h.y] and so on, where @racket[f],
    379 @racket[g] and @racket[h] are the fields of the value obtained after
    380 @racket[.x]. The @racket[?] path element can be used to describe all fields,
    381 including those hidden away by row polymorphism.
    382 
    383 It would be possible to represent sets of path concisely by reversing all
    384 these paths so that they start with their target element, and building a
    385 prefix tree. Unfortunately, Typed Racket does not currently does not
    386 automatically detect the equivalence between the types
    387 @racket[(U (Pairof A B) (Pairof A C))] and @racket[(Pairof A (U B C))]. @;{
    388  TODO: insert reference to the relevant bug.} In other words, at the type
    389 level, unions do not implicitly distribute onto pairs. When a set of
    390 properties @racket[S] is extended with a new property @racket[p], the
    391 resulting set of properties will be encoded as @racket[(U S p)]. If @racket[S]
    392 is encoded as a prefix tree, @racket[p] will not implicitly be merged into the
    393 prefix tree. This means that if prefix trees were to be used, extending a set
    394 of properties with a new property would give one representation, and directly
    395 encoding that set would give another representation, and the two
    396 representations would be incomparable.
    397 
    398 We therefore represent sets of paths using a more costly representation, by
    399 making a union of all paths, without factoring out common parts.
    400 
    401 @subsection{Parsing paths}
    402 
    403 @; TODO: make sure the literal "dot" is provided.
    404 
    405 @;   .a.b(.c.d)*.e
    406 @; ⇒ (λdot a b) ((λdot c d)) * (λdot e)
    407 
    408 @chunk[<parse>
    409        (begin-for-syntax
    410          (define (match-id rx id)
    411            (let ([m (regexp-match rx (identifier→string id))])
    412              (and m (map (λ (%) (datum->syntax id (string->symbol %) id id))
    413                          (cdr m)))))
    414          (define-syntax ~rx-id
    415            (pattern-expander
    416             (λ (stx)
    417               (syntax-case stx ()
    418                 [(_ rx g …)
    419                  #'(~and x:id
    420                          {~parse (g …) (match-id rx #'x)})]))))
    421 
    422          (define-syntax-class f+τ
    423            #:attributes (f τ)
    424            (pattern {~rx-id #px"^([^:]+):([^:]+)$" f τ})
    425            (pattern {~rx-id #px"^([^:]+)$" f} #:with ({~optional τ}) #'())
    426            (pattern {~rx-id #px"^:([^:]+)$" τ} #:with ({~optional f}) #'())
    427            (pattern {~rx-id #px"^:$"} #:with ({~optional (f τ)}) #'()))
    428          (define-syntax-class just-τ
    429            #:attributes (τ)
    430            (pattern {~rx-id #px"^:([^:]+)$" τ}))
    431          (define-syntax-class π-elements
    432            #:literals (#%dotted-id #%dot-separator)
    433            #:attributes ([f 1] [τ 1])
    434            (pattern (#%dotted-id {~seq #%dot-separator :f+τ} …)))
    435          (define-syntax-class extract-star
    436            #:literals (* #%dotted-id)
    437            (pattern (#%dotted-id {~and st *} . rest)
    438                     #:with {extracted-star …} #'{st (#%dotted-id . rest)})
    439            (pattern other
    440                     #:with {extracted-star …} #'{other}))
    441          (define-syntax-class sub-elements
    442            #:literals (* #%dot-separator)
    443            #:attributes ([f 2] [τ 2] [sub 1])
    444            (pattern
    445             (:extract-star …)
    446             #:with ({~either :π-elements {~seq sub:sub-elements *}} …)
    447             #|  |# #'(extracted-star … …))))]
    448 
    449 @chunk[<parse>
    450        (define-type-expander <~τ
    451          (syntax-parser
    452            [(_ τ) #'τ]
    453            [(_ f₀ . more)
    454             #`(f₀ (<~τ . more))]))
    455        (begin-for-syntax
    456          (define-template-metafunction generate-sub-π
    457            (syntax-parser
    458              [(_ :sub-elements after)
    459               #:with R (gensym 'R)
    460               (template
    461                (Rec R
    462                     (U (<~τ (?? (∀ (more) (generate-sub-π sub more))
    463                                 (∀ (more) (List* (Pairof (?? 'f AnyField)
    464                                                          (?? τ AnyType))
    465    466                                                  more)))
    467    468                             R)
    469                        after)))])))
    470        (define-type-expander Π
    471          (syntax-parser
    472            #:literals (#%dotted-id #%dot-separator)
    473            [(_ {~optional (~or (#%dotted-id fst-τ:just-τ
    474                                             {~seq #%dot-separator fst:f+τ} …)
    475                                {~datum :}
    476                                (~and fst-τ:just-τ
    477                                      {~parse (fst:f+τ …) #'()}))}
    478                . :sub-elements)
    479             #:with R (gensym 'R)
    480             (template/top-loc
    481              this-syntax
    482              (Rec R (U (Pairof Any R)
    483                        (List* (?? (?@ (Pairof AnyField fst-τ.τ)
    484                                       (Pairof (?? 'fst.f AnyField)
    485                                               (?? fst.τ AnyType))
    486                                       …))
    487                               <π>))))]))]
    488 
    489 @chunk[<π>
    490        (<~τ (?? (∀ (more) (generate-sub-π sub more))
    491                 (∀ (more) (List* (Pairof (?? 'f AnyField) (?? τ AnyType))
    492    493                                  more)))
    494    495             Null)]
    496 
    497 @chunk[<Invariant>
    498        (define-type-expander Invariant
    499          (syntax-parser
    500            #:literals (≡ ≢ ∈ ∉ ∋ ∌ = ≤ ≥ length +)
    501            [(_ π₁ … ≡ π₂ …) #`(U (inv≡ (Π π₁ …) (Π π₂ …))
    502                                  (inv≡ (Π π₂ …) (Π π₁ …)))]
    503            [(_ π₁ … ≢ π₂ …) #`(U (inv≢ (Π π₁ …) (Π π₂ …))
    504                                  (inv≢ (Π π₂ …) (Π π₁ …)))]
    505            [(_ π₁ … ∈ π₂ …) #`(inv∈ (Π π₁ …) (Π π₂ …))]
    506            [(_ π₁ … ∋ π₂ …) #`(inv∈ (Π π₂ …) (Π π₁ …))]
    507            [(_ π₁ … ∉ π₂ …) 
    508             #`(U (inv≢ (Π π₁ …) (Π π₂ … (#%dotted-id #%dot-separator :)))
    509                  (inv≢ (Π π₂ … (#%dotted-id #%dot-separator :)) (Π π₁ …)))]
    510            [(_ π₁ … ∌ π₂ …)
    511             #`(U (inv≢ (Π π₂ …) (Π π₁ … (#%dotted-id #%dot-separator :)))
    512                  (inv≢ (Π π₁ … (#%dotted-id #%dot-separator :)) (Π π₂ …)))]
    513            ;; TODO: = should use a combination of ≤ and ≥
    514            [(_ π₁ …
    515                {~and op {~or = ≤ ≥}}
    516                (~or (+ (length π₂ …) n:nat)
    517                     {~and (length π₂ …) {~parse n 0}}))
    518             #:with opN (syntax-case #'op (= ≤ ≥)
    519                          [= #'=N]
    520                          [≤ #'≤N]
    521                          [≥ #'≥N])
    522             #`(→ (invLength (opN n) (Π π₁ …) (Π π₂ …)) Void)]))]
    523 
    524 @CHUNK[<=>
    525        (define-type (P A B) (Promise (Pairof A B)))
    526        (define-type a 'a)
    527        (define-type x 'x)
    528        (define-type ax (U a x))
    529        (define-type x* (Rec R (P x R)))
    530        (define-type ax* (Rec R (P ax R)))
    531        (define-type-expander =N
    532          (syntax-parser
    533            [(_ 0) #'x*]
    534            [(_ n:nat) #`(P a (=N #,(sub1 (syntax-e #'n))))]))
    535        (define-type-expander ≥N
    536          (syntax-parser
    537            [(_ 0) #'ax*]
    538            [(_ n:nat) #`(P a (≥N #,(sub1 (syntax-e #'n))))]))
    539        (define-type-expander ≤N
    540          (syntax-parser
    541            [(_ 0) #'x*]
    542            [(_ n:nat) #`(P ax (≤N #,(sub1 (syntax-e #'n))))]))]
    543 
    544 @chunk[<Invariants>
    545        (define-type-expander Invariants
    546          (syntax-parser
    547            [(_ inv …)
    548             #`(→ (U Or (Invariant . inv) …) Void)]))]
    549 
    550 @chunk[<Any*>
    551        (define-type AnyField Symbol);(struct AnyField () #:transparent)
    552        (struct AnyType () #:transparent)
    553        (define-type ε (Π))]
    554 
    555 @subsubsection{Comparison operator tokens}
    556 
    557 We define some tokens which will be used to identify the operator which
    558 relates two nodes in the graph.
    559 
    560 @chunk[<comparison-operators>
    561        (struct (A B) inv≡ ([a : A] [b : B]))
    562        (struct (A B) inv≢ ([a : A] [b : B]))
    563        (struct (A B) inv∈ ([a : A] [b : B]))
    564        (struct (A B C) invLength ([a : A] [b : B] [c : C]))
    565        ;(struct inv∉ ()) ;; Can be expressed in terms of ≢
    566 
    567        (module literals typed/racket
    568          (define-syntax-rule (define-literals name ...)
    569            (begin
    570              (provide name ...)
    571              (define-syntax name
    572                (λ (stx)
    573                  (raise-syntax-error 'name
    574                                      "Can only be used in special contexts"
    575                                      stx)))
    576              ...))
    577          
    578          (define-literals ≡ ≢ ∈ ∉ ∋ ∌ ≥ ≤))
    579        (require 'literals)]
    580 
    581 @CHUNK[<≡>
    582        (define-for-syntax (relation inv)
    583          (syntax-parser
    584            [(_ (pre-a … {~literal _} post-a …)
    585                (pre-b … {~literal _} post-b …))
    586             #:with (r-pre-a …) (reverse (syntax->list #'(pre-a …)))
    587             #:with (r-pre-b …) (reverse (syntax->list #'(pre-b …)))
    588             ;; Use U to make it order-independent
    589             #`(#,inv (U (Pairof (Cycle r-pre-a …)
    590                                 (Cycle post-a …))
    591                         (Pairof (Cycle r-pre-b …)
    592                                 (Cycle post-b …))))]))
    593 
    594        (define-type-expander ≡x (relation #'inv≡))
    595        (define-type-expander ≢x (relation #'inv≢))]
    596 
    597 @chunk[<cycles>
    598        (struct ε () #:transparent)
    599        (struct (T) Target ([x : T]) #:transparent)
    600        (struct (T) NonTarget Target () #:transparent)
    601        
    602        (define-type-expander Cycle
    603          (syntax-parser
    604            [(_ field:id … {~literal ↙} loop1:id … (target:id) loop2:id …)
    605             #'(→ (List* (NonTarget ε)
    606                         (NonTarget 'field) …
    607                         (Rec R (List* (NonTarget 'loop1) … ;(NonTarget 'loop1) …
    608                                       (Target 'target) ;(NonTarget 'target)
    609                                       (U (List* (NonTarget 'loop2) … ;(NonTarget 'loop2) …
    610                                                 R)
    611                                          Null)))) Void)]
    612            [(_ field … target)
    613             #'(→ (List (NonTarget ε)
    614                        (NonTarget 'field)
    615    616                        (Target 'target)) Void)]
    617            [(_)
    618             #'(→ (List (Target ε)) Void)]))]
    619 
    620 @;{@[
    621    
    622  ;.a.b = .x.y
    623  ;(l1=a ∧ l2=b ∧ r1=x ∧ r2=y) ⇒ eq
    624  ;¬(l1=a ∧ l2=b ∧ r1=x ∧ r2=y) ∨ eq
    625  ;¬l1=a ∨ ¬l2=b ∨ ¬r1=x ∨ ¬r2=y ∨ eq
    626 
    627  ;.a.c = .x.y
    628  ;(l1=a ∧ l2=c ∧ r1=x ∧ r2=y) ⇒ eq
    629 
    630  ;.a.c = .x.z
    631  ;(l1=a ∧ l2=b ∧ r1=x ∧ r2=z) ⇒ eq
    632  ;¬l1=a ∨ ¬l2=b ∨ ¬r1=x ∨ ¬r2=z ∨ eq
    633 
    634 
    635  ;.a.b = .x.y ∧ .a.c = .x.z
    636  ;(¬l1=a ∨ ¬l2=b ∨ ¬r1=x ∨ ¬r2=y ∨ eq) ∧ (¬l1=a ∨ ¬l2=b ∨ ¬r1=x ∨ ¬r2=z ∨ eq)
    637  ;¬¬(¬l1=a ∨ ¬l2=b ∨ ¬r1=x ∨ ¬r2=y ∨ eq) ∧ (¬l1=a ∨ ¬l2=b ∨ ¬r1=x ∨ ¬r2=z ∨ eq)
    638  ;¬(l1=a ∧ l2=b ∧ r1=x ∧ r2=y ∧ eq) ∨ (l1=a ∧ l2=b ∧ r1=x ∧ r2=z ∧ ¬eq)
    639  ]}
    640 
    641 @; Problem with ∩: it factors out too much, (∩ '(a . b) '(a . c) '(x . b))
    642 @; becomes (Pairof (∩ 'a 'x) (∩ 'b 'c)), which is equivalent to have all four
    643 @; elements of {a,x} × {b,c}, but we only want three out of these four.
    644 
    645 @;{
    646  Two sorts of paths inside (in)equality constraints:
    647 
    648  @itemlist[
    649  @item{those anchored on a node, stating that
    650    @$${
    651     ∀\ \textit{node} : \textit{NodeType},\quad
    652     \textit{node}.\textit{path}₁ ≡ \textit{node}.\textit{path}₂}}
    653  @item{those not anchored on a node, stating that
    654    @$${
    655     \begin{array}{c}
    656     ∀\ \textit{node}₁ : \textit{NodeType}₁,\quad
    657     ∀\ \textit{node}₂ : \textit{NodeType}₂,\\
    658     \textit{node}₁.\textit{path}₁ ≡ \textit{node}₂.\textit{path}₂
    659     \end{array}}}]
    660 }
    661 
    662 @subsection{Putting it all together}
    663 
    664 @CHUNK[<*>
    665        (require (only-in typed/dotlambda #%dotted-id #%dot-separator)
    666                 "dot-lang.rkt"
    667                 (for-syntax racket/base
    668                             racket/list
    669                             phc-toolkit/untyped
    670                             syntax/parse
    671                             syntax/parse/experimental/template)
    672                 (for-meta 2 racket/base)
    673                 (for-meta 2 phc-toolkit/untyped/aliases)
    674                 (for-meta 3 racket/base))
    675 
    676        (begin-for-syntax
    677          (define-syntax-rule (quasisyntax e)
    678            (quasisyntax/top-loc this-syntax e))
    679          (define-syntax-rule (template/top-loc loc e)
    680            (quasisyntax/top-loc loc #,(template e))))
    681 
    682        (provide ≡ ≢ ∈ ∉ ∋ ∌ ≥ ≤)
    683        
    684        ;; For testing:
    685        (provide Invariants
    686                 inv≡
    687                 inv≢
    688                 Or
    689                 ;Target
    690                 ;NonTarget
    691                 ε
    692                 witness-value
    693                 Π
    694                 AnyType
    695                 AnyField
    696                 Invariant)
    697 
    698        <parse>
    699 
    700        <witness-value>
    701        <Any*>
    702        <comparison-operators>
    703        <Invariant>
    704        <Invariants>
    705        <Or>
    706        <=>]