Appearance
| 1 | namespace Syntax.Process is | |
| 2 | use Source | |
| 3 | use Trees | |
| 4 | ||
| 5 | use Logging | |
| 6 | ||
| 7 | use Collections.LIST | |
| 8 | ||
| 9 | use Ghul.Pipes | |
| 10 | ||
| 11 | ||
| 12 | // Synthesis helpers for the accessors and union members the | |
| 13 | // visitor adds: typed and object equals, get_hash_code, variant | |
| 14 | // init, and the type-expression helpers they read. | |
| 15 | // Turn a type-parameter declaration list into a list of | |
| 16 | // type-argument references. A declaration carries the parameter's | |
| 17 | // bound / kind / variance (e.g. E: Comparable[E]); those belong only | |
| 18 | // at the declaration site. In the self-referential UL[E] spelled by | |
| 19 | // the synthesised operators each parameter must appear as a bare | |
| 20 | // name, or the bound resolves to an error type and IL generation | |
| 21 | // fails on the malformed argument. | |
| 22 | get_type_argument_references(arguments: TypeExpressions.LIST) -> TypeExpressions.LIST is | |
| 23 | let references = Collections.LIST[TypeExpressions.TypeExpression]() | |
| 24 | ||
| 25 | for a in arguments do | |
| 26 | let name = a.name | |
| 27 | ||
| 28 | if name? then | |
| 29 | references.add(TypeExpressions.NAMED(Source.LOCATION.internal, name.copy())) | |
| 30 | else | |
| 31 | references.add(a.copy()) | |
| 32 | fi | |
| 33 | od | |
| 34 | ||
| 35 | return TypeExpressions.LIST(Source.LOCATION.internal, references) | |
| 36 | si | |
| 37 | ||
| 38 | // Build a TypeExpression for the parent union — used as the | |
| 39 | // declared parameter type of the synthesised `=~` so the method | |
| 40 | // overrides the union's base implementation. The reference stands | |
| 41 | // in for a type the user never wrote, so it carries an internal | |
| 42 | // location: a real declaration-span location would otherwise be | |
| 43 | // recorded as a phantom self-use of the union, surfacing in | |
| 44 | // find-references and rename of the union type. | |
| 45 | get_union_type_expression(`union: Definitions.UNION) -> TypeExpressions.TypeExpression => | |
| 46 | if `union.arguments? /\ `union.arguments.count > 0 then | |
| 47 | TypeExpressions.GENERIC( | |
| 48 | Source.LOCATION.internal, | |
| 49 | Identifiers.Identifier(Source.LOCATION.internal, `union.name.name), | |
| 50 | get_type_argument_references(`union.arguments!) | |
| 51 | ) | |
| 52 | else | |
| 53 | TypeExpressions.NAMED( | |
| 54 | Source.LOCATION.internal, | |
| 55 | Identifiers.Identifier(Source.LOCATION.internal, `union.name.name) | |
| 56 | ) | |
| 57 | fi | |
| 58 | ||
| 59 | // Build a TypeExpression for the variant — variants share the | |
| 60 | // union's type-arg names, so we mirror them. Internal location for | |
| 61 | // the same reason as get_union_type_expression. | |
| 62 | get_variant_type_expression(variant: Definitions.VARIANT, `union: Definitions.UNION) -> TypeExpressions.TypeExpression => | |
| 63 | if `union.arguments? /\ `union.arguments.count > 0 then | |
| 64 | TypeExpressions.GENERIC( | |
| 65 | Source.LOCATION.internal, | |
| 66 | Trees.Identifiers.QUALIFIED( | |
| 67 | Source.LOCATION.internal, | |
| 68 | Identifiers.Identifier(Source.LOCATION.internal, `union.name.name), | |
| 69 | variant.name.name, | |
| 70 | Source.LOCATION.internal, | |
| 71 | Source.LOCATION.internal | |
| 72 | ), | |
| 73 | get_type_argument_references(`union.arguments!) | |
| 74 | ) | |
| 75 | else | |
| 76 | TypeExpressions.NAMED( | |
| 77 | Source.LOCATION.internal, | |
| 78 | Trees.Identifiers.QUALIFIED( | |
| 79 | Source.LOCATION.internal, | |
| 80 | Identifiers.Identifier(Source.LOCATION.internal, `union.name.name), | |
| 81 | variant.name.name, | |
| 82 | Source.LOCATION.internal, | |
| 83 | Source.LOCATION.internal | |
| 84 | ) | |
| 85 | ) | |
| 86 | fi | |
| 87 | ||
| 88 | get_typed_equals_method_for_union(`union: Definitions.UNION) -> Definitions.FUNCTION is | |
| 89 | let location = LOCATION.internal | |
| 90 | ||
| 91 | let other_arg = Variables.VARIABLE( | |
| 92 | location, | |
| 93 | Identifiers.Identifier(location, "other"), | |
| 94 | get_union_type_expression(`union), | |
| 95 | false, | |
| 96 | false, | |
| 97 | null | |
| 98 | ) | |
| 99 | ||
| 100 | let arguments = Variables.LIST( | |
| 101 | location, | |
| 102 | Collections.LIST[Variables.VARIABLE]([other_arg]) | |
| 103 | ) | |
| 104 | ||
| 105 | let body = Trees.Bodies.EXPRESSION( | |
| 106 | location, | |
| 107 | Trees.Expressions.Literals.BOOLEAN(location, "false") | |
| 108 | ) | |
| 109 | ||
| 110 | let modifiers = Modifiers.LIST(location, null, null) | |
| 111 | modifiers.is_pure = true | |
| 112 | ||
| 113 | return Definitions.FUNCTION( | |
| 114 | location, | |
| 115 | Identifiers.Identifier(`union.name.location, "=~"), | |
| 116 | TypeExpressions.LIST(LOCATION.internal, Collections.LIST[TypeExpressions.TypeExpression](0)), | |
| 117 | arguments, | |
| 118 | TypeExpressions.NAMED(location, Identifiers.Identifier(location, "bool")), | |
| 119 | modifiers, | |
| 120 | body | |
| 121 | ) | |
| 122 | si | |
| 123 | ||
| 124 | // The name a variant reads one of its fields by. A field taken | |
| 125 | // from the union's header is the union's member, which a private | |
| 126 | // header parameter captures under another name. | |
| 127 | get_variant_field_member_name(variable: Variables.VARIABLE) -> string static => | |
| 128 | variable.inherited_member_name ?? variable.name!.name | |
| 129 | ||
| 130 | get_typed_equals_method_for_variant( | |
| 131 | variant: Definitions.VARIANT, | |
| 132 | `union: Definitions.UNION | |
| 133 | ) -> Definitions.FUNCTION is | |
| 134 | let names = Collections.LIST[string]() | |
| 135 | let locations = Collections.LIST[LOCATION]() | |
| 136 | ||
| 137 | for f in variant.fields do | |
| 138 | names.add(get_variant_field_member_name(f)) | |
| 139 | locations.add(f.location) | |
| 140 | od | |
| 141 | ||
| 142 | return get_memberwise_equals( | |
| 143 | get_union_type_expression(`union), | |
| 144 | names, | |
| 145 | locations, | |
| 146 | true | |
| 147 | ) | |
| 148 | si | |
| 149 | ||
| 150 | // Synthesise `equals(other: object?) -> bool` on the union — this | |
| 151 | // overrides .NET's `Object.Equals(object)` so unions work as | |
| 152 | // dictionary keys / set members. Body forwards to typed `=~` | |
| 153 | // after a runtime type check; the variant override of `=~` does | |
| 154 | // the per-variant work. | |
| 155 | get_object_equals_method_for_union(`union: Definitions.UNION) -> Definitions.FUNCTION is | |
| 156 | let location = LOCATION.internal | |
| 157 | ||
| 158 | let other_arg = Variables.VARIABLE( | |
| 159 | location, | |
| 160 | Identifiers.Identifier(location, "other"), | |
| 161 | TypeExpressions.OPTIONAL( | |
| 162 | location, | |
| 163 | TypeExpressions.NAMED(location, Identifiers.Identifier(location, "object")) | |
| 164 | ), | |
| 165 | false, | |
| 166 | false, | |
| 167 | null | |
| 168 | ) | |
| 169 | ||
| 170 | let arguments = Variables.LIST( | |
| 171 | location, | |
| 172 | Collections.LIST[Variables.VARIABLE]([other_arg]) | |
| 173 | ) | |
| 174 | ||
| 175 | let union_type = get_union_type_expression(`union) | |
| 176 | ||
| 177 | let isa_check = Trees.Expressions.ISA( | |
| 178 | location, | |
| 179 | union_type, | |
| 180 | Trees.Expressions.IDENTIFIER( | |
| 181 | location, | |
| 182 | Identifiers.Identifier(location, "other") | |
| 183 | ) | |
| 184 | ) | |
| 185 | ||
| 186 | let typed_call = Trees.Expressions.BINARY( | |
| 187 | location, | |
| 188 | Identifiers.Identifier(location, "=~"), | |
| 189 | "=~", | |
| 190 | Trees.Expressions.SELF(location), | |
| 191 | Trees.Expressions.CAST( | |
| 192 | location, | |
| 193 | get_union_type_expression(`union), | |
| 194 | Trees.Expressions.IDENTIFIER( | |
| 195 | location, | |
| 196 | Identifiers.Identifier(location, "other") | |
| 197 | ), | |
| 198 | false | |
| 199 | ) | |
| 200 | ) | |
| 201 | ||
| 202 | let condition = Trees.Expressions.BINARY( | |
| 203 | location, | |
| 204 | Identifiers.Identifier(location, "/\\"), | |
| 205 | "/\\", | |
| 206 | isa_check, | |
| 207 | typed_call | |
| 208 | ) | |
| 209 | ||
| 210 | let body = Trees.Bodies.EXPRESSION(location, condition) | |
| 211 | ||
| 212 | return Definitions.FUNCTION( | |
| 213 | location, | |
| 214 | Identifiers.Identifier(`union.name.location, "equals"), | |
| 215 | TypeExpressions.LIST(LOCATION.internal, Collections.LIST[TypeExpressions.TypeExpression](0)), | |
| 216 | arguments, | |
| 217 | TypeExpressions.NAMED(location, Identifiers.Identifier(location, "bool")), | |
| 218 | Modifiers.LIST(location, null, null), | |
| 219 | body | |
| 220 | ) | |
| 221 | si | |
| 222 | ||
| 223 | // Union's `get_hash_code` is overridden by every variant. The | |
| 224 | // base body returns 0 — only ever called via super, which never | |
| 225 | // happens on synthesised methods. | |
| 226 | get_hash_code_method_for_union() -> Definitions.FUNCTION is | |
| 227 | let location = LOCATION.internal | |
| 228 | ||
| 229 | let body = Trees.Bodies.EXPRESSION( | |
| 230 | location, | |
| 231 | Trees.Expressions.Literals.INTEGER(location, "0") | |
| 232 | ) | |
| 233 | ||
| 234 | return Definitions.FUNCTION( | |
| 235 | location, | |
| 236 | Identifiers.Identifier(location, "get_hash_code"), | |
| 237 | TypeExpressions.LIST(LOCATION.internal, Collections.LIST[TypeExpressions.TypeExpression](0)), | |
| 238 | Variables.LIST(location, Collections.LIST[Variables.VARIABLE]()), | |
| 239 | TypeExpressions.NAMED(location, Identifiers.Identifier(location, "int")), | |
| 240 | Modifiers.LIST(location, null, null), | |
| 241 | body | |
| 242 | ) | |
| 243 | si | |
| 244 | ||
| 245 | // Variant's `get_hash_code` combines a per-variant seed (hash of | |
| 246 | // the variant's name string, distinct per variant in an union) | |
| 247 | // with each field's hash code via XOR. Not the strongest mixer | |
| 248 | // but cheap and respects the equality contract: any two values | |
| 249 | // that compare `=~` will have hashed the same field values. | |
| 250 | get_hash_code_method_for_variant(variant: Definitions.VARIANT) -> Definitions.FUNCTION is | |
| 251 | let location = LOCATION.internal | |
| 252 | ||
| 253 | let seed = Trees.Expressions.CALL( | |
| 254 | location, | |
| 255 | Trees.Expressions.MEMBER( | |
| 256 | location, | |
| 257 | Trees.Expressions.Literals.STRING(location, variant.name.name), | |
| 258 | Identifiers.Identifier(location, "get_hash_code"), | |
| 259 | location | |
| 260 | ), | |
| 261 | Trees.Expressions.LIST( | |
| 262 | location, | |
| 263 | Collections.LIST[Trees.Expressions.Expression]() | |
| 264 | ) | |
| 265 | ) | |
| 266 | ||
| 267 | let combined: Trees.Expressions.Expression mut = seed | |
| 268 | ||
| 269 | for f in variant.fields do | |
| 270 | let field_hash = | |
| 271 | get_member_hash_code( | |
| 272 | location, | |
| 273 | Trees.Expressions.MEMBER( | |
| 274 | location, | |
| 275 | Trees.Expressions.SELF(location), | |
| 276 | Identifiers.Identifier(location, get_variant_field_member_name(f)), | |
| 277 | location | |
| 278 | ) | |
| 279 | ) | |
| 280 | ||
| 281 | // h = h * 31 + field_hash — standard multiply-and-add | |
| 282 | // hash combiner (ghūl has no XOR operator on int). | |
| 283 | let mul = Trees.Expressions.BINARY( | |
| 284 | location, | |
| 285 | Identifiers.Identifier(location, "*"), | |
| 286 | "*", | |
| 287 | combined, | |
| 288 | Trees.Expressions.Literals.INTEGER(location, "31") | |
| 289 | ) | |
| 290 | ||
| 291 | combined = Trees.Expressions.BINARY( | |
| 292 | location, | |
| 293 | Identifiers.Identifier(location, "+"), | |
| 294 | "+", | |
| 295 | mul, | |
| 296 | field_hash | |
| 297 | ) | |
| 298 | od | |
| 299 | ||
| 300 | let body = Trees.Bodies.EXPRESSION(location, combined) | |
| 301 | ||
| 302 | return Definitions.FUNCTION( | |
| 303 | location, | |
| 304 | Identifiers.Identifier(location, "get_hash_code"), | |
| 305 | TypeExpressions.LIST(LOCATION.internal, Collections.LIST[TypeExpressions.TypeExpression](0)), | |
| 306 | Variables.LIST(location, Collections.LIST[Variables.VARIABLE]()), | |
| 307 | TypeExpressions.NAMED(location, Identifiers.Identifier(location, "int")), | |
| 308 | Modifiers.LIST(location, null, null), | |
| 309 | body | |
| 310 | ) | |
| 311 | si | |
| 312 | ||
| 313 | get_init_method_for_variant(variant: Trees.Definitions.VARIANT) -> Definitions.FUNCTION is | |
| 314 | // variant.fields holds the variant's full positional shape, | |
| 315 | // including any `..` splice expanded to copies of the union's | |
| 316 | // primary params (marked is_inherited_primary). Inherited | |
| 317 | // entries become init args that the body forwards to | |
| 318 | // super.init(...); own entries get the usual | |
| 319 | // self.<f> = <f>; assignment. | |
| 320 | let arguments = | |
| 321 | variant.fields |> | |
| 322 | map((v) -> Variables.VARIABLE => | |
| 323 | Variables.VARIABLE( | |
| 324 | v.location, | |
| 325 | v.name!.copy(), | |
| 326 | v.type_expression.copy(), | |
| 327 | false, | |
| 328 | false, | |
| 329 | null | |
| 330 | ) | |
| 331 | ) |> | |
| 332 | collect_list() | |
| 333 | ||
| 334 | let body_statements = Collections.LIST[Trees.Statements.Statement]() | |
| 335 | ||
| 336 | let super_args = Collections.LIST[Trees.Expressions.Expression]() | |
| 337 | for f in variant.fields do | |
| 338 | if f.is_inherited_primary then | |
| 339 | super_args.add( | |
| 340 | Trees.Expressions.IDENTIFIER( | |
| 341 | LOCATION.internal, | |
| 342 | Trees.Identifiers.Identifier(LOCATION.internal, f.name!.name) | |
| 343 | ) | |
| 344 | ) | |
| 345 | fi | |
| 346 | od | |
| 347 | ||
| 348 | if super_args.count > 0 then | |
| 349 | let super_member = | |
| 350 | Trees.Expressions.MEMBER( | |
| 351 | LOCATION.internal, | |
| 352 | Trees.Expressions.SUPER(LOCATION.internal), | |
| 353 | Trees.Identifiers.Identifier(LOCATION.internal, "init"), | |
| 354 | LOCATION.internal | |
| 355 | ) | |
| 356 | let super_call = | |
| 357 | Trees.Expressions.CALL( | |
| 358 | LOCATION.internal, | |
| 359 | super_member, | |
| 360 | Trees.Expressions.LIST(LOCATION.internal, super_args) | |
| 361 | ) | |
| 362 | body_statements.add(Trees.Statements.EXPRESSION(LOCATION.internal, super_call)) | |
| 363 | fi | |
| 364 | ||
| 365 | for f in variant.fields do | |
| 366 | if f.is_inherited_primary then | |
| 367 | continue | |
| 368 | fi | |
| 369 | ||
| 370 | let member_access = | |
| 371 | Trees.Expressions.MEMBER( | |
| 372 | LOCATION.internal, | |
| 373 | Trees.Expressions.SELF(LOCATION.internal), | |
| 374 | Trees.Identifiers.Identifier(LOCATION.internal, f.name!.name), | |
| 375 | LOCATION.internal | |
| 376 | ) | |
| 377 | ||
| 378 | let left = | |
| 379 | Trees.Expressions.SIMPLE_LEFT_EXPRESSION(LOCATION.internal, member_access) | |
| 380 | ||
| 381 | body_statements.add( | |
| 382 | Trees.Statements.ASSIGNMENT( | |
| 383 | LOCATION.internal, | |
| 384 | left, | |
| 385 | Trees.Expressions.IDENTIFIER( | |
| 386 | LOCATION.internal, | |
| 387 | Trees.Identifiers.Identifier(LOCATION.internal, f.name!.name) | |
| 388 | ) | |
| 389 | ) | |
| 390 | ) | |
| 391 | od | |
| 392 | ||
| 393 | let body_statement_list = Trees.Statements.LIST( | |
| 394 | LOCATION.internal, | |
| 395 | body_statements | |
| 396 | ) | |
| 397 | ||
| 398 | let init_body = Trees.Bodies.BLOCK( | |
| 399 | LOCATION.internal, | |
| 400 | body_statement_list | |
| 401 | ) | |
| 402 | ||
| 403 | let init_function = Definitions.FUNCTION( | |
| 404 | LOCATION.internal, | |
| 405 | // The constructor is the one synthesised member with a home in | |
| 406 | // the source: it is what a variant construction resolves to, so | |
| 407 | // its name carries the variant's own location. | |
| 408 | Identifiers.Identifier( | |
| 409 | variant.location, | |
| 410 | "init" | |
| 411 | ), | |
| 412 | TypeExpressions.LIST(LOCATION.internal, Collections.LIST[TypeExpressions.TypeExpression](0)), | |
| 413 | Variables.LIST( | |
| 414 | LOCATION.internal, | |
| 415 | arguments | |
| 416 | ), | |
| 417 | TypeExpressions.NAMED( | |
| 418 | LOCATION.internal, | |
| 419 | Identifiers.Identifier( | |
| 420 | LOCATION.internal, | |
| 421 | "void" | |
| 422 | ) | |
| 423 | ), | |
| 424 | Modifiers.LIST( | |
| 425 | LOCATION.internal, | |
| 426 | null, | |
| 427 | null | |
| 428 | ), | |
| 429 | init_body | |
| 430 | ) | |
| 431 | ||
| 432 | return init_function | |
| 433 | si | |
| 434 | ||
| 435 | // The declarations that hold a value's state, in source order: a | |
| 436 | // member with neither accessor written, which is an auto-property | |
| 437 | // or a `field`. A property that supplies a body is derived from | |
| 438 | // that state rather than part of it, and a static member belongs | |
| 439 | // to the type rather than to the value, so neither takes part in | |
| 440 | // equality. Read before the property rewrite splits an | |
| 441 | // auto-property into a backing variable and accessors, so members | |
| 442 | // are still named as they were written. | |
| 443 | get_state_members(body: Definitions.LIST) -> Collections.LIST[Definitions.PROPERTY] static is | |
| 444 | let result = Collections.LIST[Definitions.PROPERTY]() | |
| 445 | ||
| 446 | for definition in body do | |
| 447 | if | |
| 448 | isa Definitions.PROPERTY(definition) /\ | |
| 449 | definition.name? /\ | |
| 450 | !definition.is_poisoned /\ | |
| 451 | !definition.modifiers.is_static /\ | |
| 452 | !definition.read_body? /\ | |
| 453 | !definition.assign_body? | |
| 454 | then | |
| 455 | result.add(definition) | |
| 456 | fi | |
| 457 | od | |
| 458 | ||
| 459 | return result | |
| 460 | si | |
| 461 | ||
| 462 | // How a state member is read from another value of the same type. | |
| 463 | // An auto-property's backing field is read directly rather than | |
| 464 | // through the getter it will be given: the getter of a struct | |
| 465 | // property hands back a copy, and the comparison wants the value | |
| 466 | // itself. A `field` member is already the storage and keeps its | |
| 467 | // own name, and so is one the accessor rewrite leaves alone - a | |
| 468 | // non-public member never becomes an auto-property, so there is | |
| 469 | // no backing field behind it to reach. | |
| 470 | get_state_member_read_name(member: Definitions.PROPERTY) -> string static => | |
| 471 | if | |
| 472 | member.modifiers.is_field \/ | |
| 473 | member.modifiers.is_private \/ | |
| 474 | member.name!.name.starts_with('_') | |
| 475 | then | |
| 476 | member.name!.name | |
| 477 | else | |
| 478 | "${member.name!.name}" | |
| 479 | fi | |
| 480 | ||
| 481 | // Whether every declaration holding the type's state is part of | |
| 482 | // the type's public contract. Private state is an implementation | |
| 483 | // detail that only its author knows the equality significance of, | |
| 484 | // and comparing the public members alone would answer equal for | |
| 485 | // two values a private member distinguishes, so a type with any | |
| 486 | // is left without a synthesized operator. `protected` counts as | |
| 487 | // non-public: it is readable inside the assembly but is no part of | |
| 488 | // what consumers see. | |
| 489 | has_only_public_state(body: Definitions.LIST) -> bool static is | |
| 490 | for member in get_state_members(body) do | |
| 491 | if | |
| 492 | member.name!.name.starts_with('_') \/ | |
| 493 | member.modifiers.is_private \/ | |
| 494 | member.modifiers.is_protected | |
| 495 | then | |
| 496 | return false | |
| 497 | fi | |
| 498 | od | |
| 499 | ||
| 500 | return true | |
| 501 | si | |
| 502 | ||
| 503 | // Whether the type gets a synthesized `=~` and `get_hash_code`. | |
| 504 | // Declaring either operator opts out of both: a hand-written `=~` | |
| 505 | // is the author's own answer, and a hand-written `get_hash_code` | |
| 506 | // beside a synthesized operator could disagree with it, which is | |
| 507 | // the pair invariant `equality-without-hash` exists to protect. | |
| 508 | // `<>` opts out too, since equality is already derived from it. | |
| 509 | wants_synthesized_equality(body: Definitions.LIST) -> bool static => | |
| 510 | declares_no_equality(body) /\ | |
| 511 | has_only_public_state(body) | |
| 512 | ||
| 513 | // A class is synthesized for only where an `@equality` pragma | |
| 514 | // asked, so the state it holds is state the author meant to | |
| 515 | // compare and its visibility says nothing about that. What still | |
| 516 | // opts out is an equality already written. | |
| 517 | declares_no_equality(body: Definitions.LIST) -> bool static => | |
| 518 | !declares_function(body, "=~") /\ | |
| 519 | !declares_function(body, "<>") /\ | |
| 520 | !declares_function(body, "get_hash_code") /\ | |
| 521 | !declares_object_equals(body) | |
| 522 | ||
| 523 | // A struct compares memberwise. The parameter is the struct's own | |
| 524 | // type and a struct value has no absent form, so the body needs no | |
| 525 | // guard: every operand it is handed is a value of exactly this | |
| 526 | // type. Each member is compared by a FIELD_EQUALS node rather than | |
| 527 | // a `=~` operator, because which comparison to run is not knowable | |
| 528 | // here - only once the member's static type has resolved. | |
| 529 | get_memberwise_equals_for_struct(`struct: Definitions.STRUCT) -> Definitions.FUNCTION is | |
| 530 | return get_memberwise_equals( | |
| 531 | get_classy_type_expression(`struct), | |
| 532 | get_member_read_names(`struct.body), | |
| 533 | get_member_locations(`struct.body), | |
| 534 | false | |
| 535 | ) | |
| 536 | si | |
| 537 | ||
| 538 | // A class compares by runtime type and then memberwise. The | |
| 539 | // runtime-type test comes first because a base's comparison run | |
| 540 | // against a subclass value would otherwise compare the base's | |
| 541 | // fields and answer true, while the subclass's own override | |
| 542 | // answered false, leaving the relation asymmetric. | |
| 543 | // | |
| 544 | // The parameter is the class's own type here. Where the class | |
| 545 | // turns out to extend one that is itself synthesized, the pass | |
| 546 | // that resolves that retypes it to the base's parameter type, so | |
| 547 | // that the two are one virtual slot rather than two overloads. | |
| 548 | // The members are read straight off `other`, which is this class | |
| 549 | // here. Where the parameter is retyped, the pass that does it | |
| 550 | // prefixes an `isa` to narrow it back. | |
| 551 | get_memberwise_equals_for_class(`class: Definitions.CLASS) -> Definitions.FUNCTION is | |
| 552 | let equals = | |
| 553 | get_memberwise_equals( | |
| 554 | get_classy_type_expression(`class), | |
| 555 | get_member_read_names(`class.body), | |
| 556 | get_member_locations(`class.body), | |
| 557 | false | |
| 558 | ) | |
| 559 | ||
| 560 | return equals | |
| 561 | si | |
| 562 | ||
| 563 | // `expression.get_type()`, the runtime type of a value. | |
| 564 | get_runtime_type( | |
| 565 | location: LOCATION, | |
| 566 | expression: Trees.Expressions.Expression | |
| 567 | ) -> Trees.Expressions.Expression static => | |
| 568 | Trees.Expressions.CALL( | |
| 569 | location, | |
| 570 | Trees.Expressions.MEMBER( | |
| 571 | location, | |
| 572 | expression, | |
| 573 | Identifiers.Identifier(location, "get_type"), | |
| 574 | location | |
| 575 | ), | |
| 576 | Trees.Expressions.LIST( | |
| 577 | location, | |
| 578 | Collections.LIST[Trees.Expressions.Expression]() | |
| 579 | ) | |
| 580 | ) | |
| 581 | ||
| 582 | // One member's contribution to a synthesized `get_hash_code`, | |
| 583 | // read through `object?` so that a member of any kind answers: | |
| 584 | // boxing a value type, reaching a bare type parameter's own hash, | |
| 585 | // and giving an absent optional a hash rather than a cast that | |
| 586 | // would throw on it. What is hashed is a HASH_OPERAND rather than | |
| 587 | // the member itself, since a member compared element-wise, or one | |
| 588 | // compared more finely than its own hash answers for, cannot be | |
| 589 | // hashed as it stands without two equal values hashing | |
| 590 | // differently. | |
| 591 | get_member_hash_code( | |
| 592 | location: LOCATION, | |
| 593 | member_read: Trees.Expressions.Expression | |
| 594 | ) -> Trees.Expressions.Expression static is | |
| 595 | let boxed = Trees.Expressions.CAST( | |
| 596 | location, | |
| 597 | TypeExpressions.OPTIONAL( | |
| 598 | location, | |
| 599 | TypeExpressions.NAMED(location, Identifiers.Identifier(location, "object")) | |
| 600 | ), | |
| 601 | Trees.Expressions.HASH_OPERAND(location, member_read), | |
| 602 | false | |
| 603 | ) | |
| 604 | ||
| 605 | let hash_member = Trees.Expressions.MEMBER( | |
| 606 | location, | |
| 607 | boxed, | |
| 608 | Identifiers.Identifier(location, "get_hash_code"), | |
| 609 | location | |
| 610 | ) | |
| 611 | ||
| 612 | hash_member.is_coalesce = true | |
| 613 | ||
| 614 | return Trees.Expressions.BINARY( | |
| 615 | location, | |
| 616 | Identifiers.Identifier(location, "??"), | |
| 617 | "??", | |
| 618 | Trees.Expressions.CALL( | |
| 619 | location, | |
| 620 | hash_member, | |
| 621 | Trees.Expressions.LIST( | |
| 622 | location, | |
| 623 | Collections.LIST[Trees.Expressions.Expression]() | |
| 624 | ) | |
| 625 | ), | |
| 626 | Trees.Expressions.Literals.INTEGER(location, "0") | |
| 627 | ) | |
| 628 | si | |
| 629 | ||
| 630 | // The hash the memberwise comparison implies: every member the | |
| 631 | // comparison reads, mixed by multiply-and-add. Each is read | |
| 632 | // through `object?` so that a member of any kind answers - boxing | |
| 633 | // a value type, and giving an absent optional a hash of its own | |
| 634 | // rather than a cast that would throw on it. | |
| 635 | get_memberwise_hash_code(body: Definitions.LIST) -> Definitions.FUNCTION is | |
| 636 | let location = LOCATION.internal | |
| 637 | ||
| 638 | return Definitions.FUNCTION( | |
| 639 | location, | |
| 640 | Identifiers.Identifier(location, "get_hash_code"), | |
| 641 | TypeExpressions.LIST(location, Collections.LIST[TypeExpressions.TypeExpression](0)), | |
| 642 | Variables.LIST(location, Collections.LIST[Variables.VARIABLE]()), | |
| 643 | TypeExpressions.NAMED(location, Identifiers.Identifier(location, "int")), | |
| 644 | Modifiers.LIST(location, null, null), | |
| 645 | Trees.Bodies.EXPRESSION( | |
| 646 | location, | |
| 647 | Trees.Expressions.MEMBERWISE_HASH( | |
| 648 | location, | |
| 649 | get_member_read_names(body), | |
| 650 | get_member_locations(body) | |
| 651 | ) | |
| 652 | ) | |
| 653 | ) | |
| 654 | si | |
| 655 | ||
| 656 | // The `=~` a type is given: one parameter at the type the whole | |
| 657 | // chain compares at, and a body that stands for the comparison | |
| 658 | // rather than spelling it. What it does - which guard, how each | |
| 659 | // member compares, what a base contributes - is settled where | |
| 660 | // those are known, which is not here. | |
| 661 | get_memberwise_equals( | |
| 662 | parameter_type: TypeExpressions.TypeExpression, | |
| 663 | members: Collections.LIST[string], | |
| 664 | member_locations: Collections.LIST[LOCATION], | |
| 665 | reports_fallback: bool | |
| 666 | ) -> Definitions.FUNCTION is | |
| 667 | let location = LOCATION.internal | |
| 668 | ||
| 669 | let other_argument = Variables.VARIABLE( | |
| 670 | location, | |
| 671 | Identifiers.Identifier(location, "other"), | |
| 672 | parameter_type, | |
| 673 | false, | |
| 674 | false, | |
| 675 | null | |
| 676 | ) | |
| 677 | ||
| 678 | let body = Trees.Expressions.MEMBERWISE_EQUALS(location, members, member_locations) | |
| 679 | ||
| 680 | body.reports_fallback = reports_fallback | |
| 681 | ||
| 682 | // Declared rather than left to be proven, and not because the | |
| 683 | // body needs covering: it reads members and compares them, and | |
| 684 | // the effects walk is told which comparisons it reaches. What | |
| 685 | // the declaration buys is the contract over dispatch. An `open` | |
| 686 | // class can be subclassed in another assembly, so no analysis | |
| 687 | // here can prove what an override does, and the mandate that | |
| 688 | // makes a member `=~` trustworthy is what binds every override | |
| 689 | // to the same rule. A user writing the operator on an open | |
| 690 | // class declares it for the same reason. | |
| 691 | let modifiers = Modifiers.LIST(location, null, null) | |
| 692 | modifiers.is_pure = true | |
| 693 | ||
| 694 | return Definitions.FUNCTION( | |
| 695 | location, | |
| 696 | Identifiers.Identifier(location, "=~"), | |
| 697 | TypeExpressions.LIST(location, Collections.LIST[TypeExpressions.TypeExpression](0)), | |
| 698 | Variables.LIST(location, Collections.LIST[Variables.VARIABLE]([other_argument])), | |
| 699 | TypeExpressions.NAMED(location, Identifiers.Identifier(location, "bool")), | |
| 700 | modifiers, | |
| 701 | Trees.Bodies.EXPRESSION(location, body) | |
| 702 | ) | |
| 703 | si | |
| 704 | ||
| 705 | // The members a value of the type is compared and hashed by, under | |
| 706 | // the names it is read by: an auto-property through its backing | |
| 707 | // field, a `field` member by its own name. | |
| 708 | get_member_locations(body: Definitions.LIST) -> Collections.LIST[LOCATION] static is | |
| 709 | let locations = Collections.LIST[LOCATION]() | |
| 710 | ||
| 711 | for member in get_state_members(body) do | |
| 712 | locations.add(member.location) | |
| 713 | od | |
| 714 | ||
| 715 | return locations | |
| 716 | si | |
| 717 | ||
| 718 | get_member_read_names(body: Definitions.LIST) -> Collections.LIST[string] static is | |
| 719 | let names = Collections.LIST[string]() | |
| 720 | ||
| 721 | for member in get_state_members(body) do | |
| 722 | names.add(get_state_member_read_name(member)) | |
| 723 | od | |
| 724 | ||
| 725 | return names | |
| 726 | si | |
| 727 | ||
| 728 | // A TypeExpression naming the type itself, mirroring its own type | |
| 729 | // parameters where it has them. Internal location, so the | |
| 730 | // synthesised reference is not recorded as a use of the type. | |
| 731 | get_classy_type_expression(classy: Definitions.Classy) -> TypeExpressions.TypeExpression static => | |
| 732 | if classy.arguments? /\ classy.arguments.count > 0 then | |
| 733 | TypeExpressions.GENERIC( | |
| 734 | Source.LOCATION.internal, | |
| 735 | Identifiers.Identifier(Source.LOCATION.internal, classy.name.name), | |
| 736 | get_type_argument_references(classy.arguments!) | |
| 737 | ) | |
| 738 | else | |
| 739 | TypeExpressions.NAMED( | |
| 740 | Source.LOCATION.internal, | |
| 741 | Identifiers.Identifier(Source.LOCATION.internal, classy.name.name) | |
| 742 | ) | |
| 743 | fi | |
| 744 | ||
| 745 | // Whether the member of that name in the type's body is one the | |
| 746 | // compiler put there. | |
| 747 | declares_synthesized_function(body: Definitions.LIST, name: string) -> bool static is | |
| 748 | for definition in body do | |
| 749 | if | |
| 750 | isa Definitions.FUNCTION(definition) /\ | |
| 751 | definition.is_synthesized /\ | |
| 752 | definition.name? /\ | |
| 753 | definition.name.name =~ name | |
| 754 | then | |
| 755 | return true | |
| 756 | fi | |
| 757 | od | |
| 758 | ||
| 759 | return false | |
| 760 | si | |
| 761 | ||
| 762 | // Whether a type's own body declares a member of the given name. | |
| 763 | // Only the body is consulted, so a member arriving from a | |
| 764 | // `partial` or `impl` block is not seen here: those are declared | |
| 765 | // later, in declare-members, and a type reopened that way does | |
| 766 | // not get the synthesised bridge below. | |
| 767 | declares_function(body: Definitions.LIST, name: string) -> bool static is | |
| 768 | for definition in body do | |
| 769 | if | |
| 770 | isa Definitions.FUNCTION(definition) /\ | |
| 771 | definition.name? /\ | |
| 772 | definition.name.name =~ name | |
| 773 | then | |
| 774 | return true | |
| 775 | fi | |
| 776 | od | |
| 777 | ||
| 778 | return false | |
| 779 | si | |
| 780 | ||
| 781 | // A type expression naming `object`, with or without a `?`. | |
| 782 | is_object_type_expression(type_expression: TypeExpressions.TypeExpression) -> bool static is | |
| 783 | let inner = | |
| 784 | if isa TypeExpressions.OPTIONAL(type_expression) then | |
| 785 | type_expression.element | |
| 786 | else | |
| 787 | type_expression | |
| 788 | fi | |
| 789 | ||
| 790 | return | |
| 791 | isa TypeExpressions.NAMED(inner) /\ | |
| 792 | inner.name.name =~ "object" | |
| 793 | si | |
| 794 | ||
| 795 | // Whether the body already supplies the `Object.Equals` the bridge | |
| 796 | // would add. Both spellings reach it: a member named `equals`, and | |
| 797 | // `=~` itself when its parameter is `object` - an equality | |
| 798 | // operator over `object` is emitted under the name `Equals` and so | |
| 799 | // already overrides it. Adding a bridge alongside either produces | |
| 800 | // a duplicate method. | |
| 801 | declares_object_equals(body: Definitions.LIST) -> bool static is | |
| 802 | for definition in body do | |
| 803 | if !isa Definitions.FUNCTION(definition) \/ !definition.name? then | |
| 804 | continue | |
| 805 | fi | |
| 806 | ||
| 807 | let name = definition.name.name | |
| 808 | ||
| 809 | if !(name =~ "equals") /\ !(name =~ "=~") then | |
| 810 | continue | |
| 811 | fi | |
| 812 | ||
| 813 | if definition.arguments.count == 1 then | |
| 814 | for argument in definition.arguments do | |
| 815 | if is_object_type_expression(argument.type_expression) then | |
| 816 | return true | |
| 817 | fi | |
| 818 | od | |
| 819 | fi | |
| 820 | ||
| 821 | if name =~ "equals" then | |
| 822 | return true | |
| 823 | fi | |
| 824 | od | |
| 825 | ||
| 826 | return false | |
| 827 | si | |
| 828 | ||
| 829 | // `=~` is emitted as a typed `Equals` method, which .NET does not | |
| 830 | // recognise as an equality implementation on its own: an | |
| 831 | // `EqualityComparer[T]` reaches for `IEquatable[T]` and then for | |
| 832 | // `Object.Equals(object)`, and a type carrying neither falls back | |
| 833 | // to the default for its kind. A class then compares by reference | |
| 834 | // and a struct by reflected memberwise comparison, so a user's own | |
| 835 | // `=~` is bypassed either way when the value is used as a | |
| 836 | // dictionary key or searched for in a collection. | |
| 837 | // | |
| 838 | // Bridge it: a type whose body declares `=~` gets an | |
| 839 | // `equals(other: object?)` that casts and delegates, so the | |
| 840 | // operator the user wrote is what .NET calls. Written as a cast to | |
| 841 | // the optional form and a presence test rather than an `isa` | |
| 842 | // narrow, which is the one shape that holds for a struct as well | |
| 843 | // as a class, and which accepts either spelling of the operator's | |
| 844 | // own parameter. | |
| 845 | // Marked synthesized as well as carrying an internal location: | |
| 846 | // the class path takes the bridge back out with the operator it | |
| 847 | // bridges to, and finds it by that mark. | |
| 848 | get_object_equals_bridge( | |
| 849 | location: LOCATION, | |
| 850 | type_expression: TypeExpressions.TypeExpression | |
| 851 | ) -> Definitions.FUNCTION static is | |
| 852 | let other_argument = Variables.VARIABLE( | |
| 853 | location, | |
| 854 | Identifiers.Identifier(location, "other"), | |
| 855 | TypeExpressions.OPTIONAL( | |
| 856 | location, | |
| 857 | TypeExpressions.NAMED(location, Identifiers.Identifier(location, "object")) | |
| 858 | ), | |
| 859 | false, | |
| 860 | false, | |
| 861 | null | |
| 862 | ) | |
| 863 | ||
| 864 | let narrowed = Variables.VARIABLE( | |
| 865 | location, | |
| 866 | Identifiers.Identifier(location, "narrowed"), | |
| 867 | TypeExpressions.INFER(location), | |
| 868 | false, | |
| 869 | false, | |
| 870 | Trees.Expressions.CAST( | |
| 871 | location, | |
| 872 | TypeExpressions.OPTIONAL(location, type_expression), | |
| 873 | Trees.Expressions.IDENTIFIER( | |
| 874 | location, | |
| 875 | Identifiers.Identifier(location, "other") | |
| 876 | ), | |
| 877 | false | |
| 878 | ) | |
| 879 | ) | |
| 880 | ||
| 881 | let narrowed_reference = | |
| 882 | Trees.Expressions.IDENTIFIER( | |
| 883 | location, | |
| 884 | Identifiers.Identifier(location, "narrowed") | |
| 885 | ) | |
| 886 | ||
| 887 | let then_branch = Trees.Statements.IF_BRANCH( | |
| 888 | location, | |
| 889 | Trees.Expressions.HAS_VALUE(location, narrowed_reference), | |
| 890 | Trees.Statements.LIST( | |
| 891 | location, | |
| 892 | Collections.LIST[Statements.Statement]([ | |
| 893 | Trees.Statements.RETURN( | |
| 894 | location, | |
| 895 | Trees.Expressions.BINARY( | |
| 896 | location, | |
| 897 | Identifiers.Identifier(location, "=~"), | |
| 898 | "=~", | |
| 899 | Trees.Expressions.SELF(location), | |
| 900 | Trees.Expressions.IDENTIFIER( | |
| 901 | location, | |
| 902 | Identifiers.Identifier(location, "narrowed") | |
| 903 | ) | |
| 904 | ) | |
| 905 | ) | |
| 906 | ]: Statements.Statement) | |
| 907 | ) | |
| 908 | ) | |
| 909 | ||
| 910 | let body = Trees.Bodies.BLOCK( | |
| 911 | location, | |
| 912 | Trees.Statements.LIST( | |
| 913 | location, | |
| 914 | Collections.LIST[Statements.Statement]([ | |
| 915 | Trees.Statements.LET( | |
| 916 | location, | |
| 917 | false, | |
| 918 | Variables.LIST( | |
| 919 | location, | |
| 920 | Collections.LIST[Variables.VARIABLE]([narrowed]) | |
| 921 | ) | |
| 922 | ), | |
| 923 | Trees.Statements.IF( | |
| 924 | location, | |
| 925 | Collections.LIST[Trees.Statements.IF_BRANCH]([then_branch]) | |
| 926 | ), | |
| 927 | Trees.Statements.RETURN( | |
| 928 | location, | |
| 929 | Trees.Expressions.Literals.BOOLEAN(location, "false") | |
| 930 | ) | |
| 931 | ]: Statements.Statement) | |
| 932 | ) | |
| 933 | ) | |
| 934 | ||
| 935 | let bridge = Definitions.FUNCTION( | |
| 936 | location, | |
| 937 | Identifiers.Identifier(location, "equals"), | |
| 938 | TypeExpressions.LIST(LOCATION.internal, Collections.LIST[TypeExpressions.TypeExpression](0)), | |
| 939 | Variables.LIST( | |
| 940 | location, | |
| 941 | Collections.LIST[Variables.VARIABLE]([other_argument]) | |
| 942 | ), | |
| 943 | TypeExpressions.NAMED(location, Identifiers.Identifier(location, "bool")), | |
| 944 | Modifiers.LIST(location, null, null), | |
| 945 | body | |
| 946 | ) | |
| 947 | ||
| 948 | bridge.mark_synthesized() | |
| 949 | ||
| 950 | return bridge | |
| 951 | si | |
| 952 | ||
| 953 | si |