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 <=>]