Appearance
| 1 | namespace Syntax.Process is | |
| 2 | use Source; | |
| 3 | use Logging; | |
| 4 | use Trees; | |
| 5 | ||
| 6 | // Walks a `super(...)` argument expression tree and collects the | |
| 7 | // names of primary-ctor parameters referenced as bare variable | |
| 8 | // expressions (e.g. `super(x, f(x))` — `x` referenced). Member | |
| 9 | // names in `a.x` and type-arg names in `LIST[T]` are deliberately | |
| 10 | // NOT collected: only `visit(Expressions.IDENTIFIER)` fires, so the | |
| 11 | // bare `Identifier` nodes for member names and type arguments never | |
| 12 | // reach this visitor. | |
| 13 | // | |
| 14 | // Used to gate auto-field synthesis: a primary param consumed by | |
| 15 | // `super(...)` does not auto-generate a same-named field. | |
| 16 | class SUPER_PARAM_REFERENCE_COLLECTOR(_primary_param_names: Collections.SET[string]): Syntax.Visitor is | |
| 17 | referenced_names: Collections.SET[string] public; | |
| 18 | ||
| 19 | super(); | |
| 20 | ||
| 21 | init(..) is | |
| 22 | referenced_names = Collections.SET[string](); | |
| 23 | si | |
| 24 | ||
| 25 | visit(identifier: Trees.Expressions.IDENTIFIER) is | |
| 26 | let name = identifier.identifier.name; | |
| 27 | ||
| 28 | if _primary_param_names.contains(name) then | |
| 29 | referenced_names.add(name); | |
| 30 | fi | |
| 31 | si | |
| 32 | si | |
| 33 | ||
| 34 | // Rewrites `class FOO(p1: T1, p2: T2) is ... si` into the | |
| 35 | // classic-form equivalent. Runs at the head of | |
| 36 | // `rewrite-syntax-trees` so the synthesised init (and any | |
| 37 | // captured-public fields) flow through `add_accessors_for_properties` | |
| 38 | // exactly like a hand-written init / hand-written public field would. | |
| 39 | // | |
| 40 | // Surface this visitor consumes: | |
| 41 | // - Classy.primary_params: parameters parsed from the `(...)` header. | |
| 42 | // Modifier suffixes on each param (`public` / `field` / `init`) | |
| 43 | // are read here and ride through to the auto-generated body decl | |
| 44 | // (or, for `init`, suppress it). | |
| 45 | // - Body-level `super(expr, expr);` declarations (Definitions.SUPER_CALL). | |
| 46 | // Each arg can be any expression whose only free names are primary- | |
| 47 | // ctor params; literals and module/type-level references are also | |
| 48 | // in scope. Walking the expression trees identifies which primary | |
| 49 | // params are consumed by `super(...)` so they aren't also auto- | |
| 50 | // field-generated. | |
| 51 | // - Body-level capture shorthand: a field declaration with INFER | |
| 52 | // (no explicit) type expression and no read/assign body. Matches a | |
| 53 | // primary parameter named `<field-name>` or `_<field-name>`. Typed | |
| 54 | // body decls match the same way — body decl wins, no auto-gen. | |
| 55 | // - Primary parameters that match nothing in the body and are not in | |
| 56 | // `super(...)` and lack the `init` modifier: an auto-field is | |
| 57 | // synthesised before the body iteration so downstream phases see | |
| 58 | // it as if the user had written it by hand. | |
| 59 | // - Secondary-init formal arg lists containing a VARIABLE flagged | |
| 60 | // is_splice (the `..` marker). Expanded to the primary parameters | |
| 61 | // in place. | |
| 62 | // | |
| 63 | // After this visitor runs on a Classy, `primary_params` is null and | |
| 64 | // every SUPER_CALL / capture-field / splice in the body has been | |
| 65 | // consumed; downstream phases see a node indistinguishable from a | |
| 66 | // class written in classic form. | |
| 67 | class REWRITE_PRIMARY_CONSTRUCTORS: Visitor is | |
| 68 | _logger: Logger; | |
| 69 | ||
| 70 | init(logger: Logger) is | |
| 71 | super.init(); | |
| 72 | ||
| 73 | _logger = logger; | |
| 74 | si | |
| 75 | ||
| 76 | apply(node: Trees.Node) is | |
| 77 | node.walk(self); | |
| 78 | si | |
| 79 | ||
| 80 | pre(`class: Trees.Definitions.CLASS) -> bool is | |
| 81 | _lower(`class); | |
| 82 | return false; | |
| 83 | si | |
| 84 | ||
| 85 | pre(`struct: Trees.Definitions.STRUCT) -> bool is | |
| 86 | _lower(`struct); | |
| 87 | return false; | |
| 88 | si | |
| 89 | ||
| 90 | // A union with a primary-constructor header lowers the same | |
| 91 | // way as a class: the primary params become auto-fields on the | |
| 92 | // union base class and feed the synthesised init. A variant | |
| 93 | // with additional fields must include `..` exactly once to | |
| 94 | // splice the primary params in; a variant with no field list | |
| 95 | // at all (just `NAME;`) has the splice implied. The splice | |
| 96 | // expansion replaces the marker with deep-copies of the | |
| 97 | // primary params (marked is_inherited_primary so the variant's | |
| 98 | // init synthesis can forward them to super.init(...) rather | |
| 99 | // than reassigning them). | |
| 100 | // | |
| 101 | // A union without a primary header rejects stray `..` in any | |
| 102 | // variant — the splice has no source to expand against. | |
| 103 | pre(`union: Trees.Definitions.UNION) -> bool is | |
| 104 | let primary_params = `union.primary_params; | |
| 105 | ||
| 106 | _lower(`union); | |
| 107 | ||
| 108 | if primary_params? then | |
| 109 | for member in `union.body do | |
| 110 | if isa Trees.Definitions.VARIANT(member) then | |
| 111 | _expand_variant_splice(cast Trees.Definitions.VARIANT(member), primary_params); | |
| 112 | fi | |
| 113 | od | |
| 114 | else | |
| 115 | for member in `union.body do | |
| 116 | if isa Trees.Definitions.VARIANT(member) then | |
| 117 | _reject_stray_splice(cast Trees.Definitions.VARIANT(member)); | |
| 118 | fi | |
| 119 | od | |
| 120 | fi | |
| 121 | ||
| 122 | return false; | |
| 123 | si | |
| 124 | ||
| 125 | // The parser allows `..` in any `init(...)` formal-arg list, | |
| 126 | // because that's what's syntactically valid; the semantic | |
| 127 | // requirement (init must belong to a primary-constructor | |
| 128 | // class) can't be enforced at parse time. By the time the | |
| 129 | // walk reaches a VARIABLE in a primary class, `_expand_secondary_init` | |
| 130 | // has replaced every splice with the primary parameters, so | |
| 131 | // any surviving splice came from a non-primary class. | |
| 132 | pre(variable: Trees.Variables.VARIABLE) -> bool is | |
| 133 | if variable.is_splice /\ !variable.is_poisoned then | |
| 134 | _logger.error( | |
| 135 | variable.location, | |
| 136 | ".. requires a primary constructor header on the surrounding class" | |
| 137 | ); | |
| 138 | fi | |
| 139 | return false; | |
| 140 | si | |
| 141 | ||
| 142 | _lower(classy: Trees.Definitions.Classy) is | |
| 143 | let primary_params: Trees.Variables.LIST? mut = classy.primary_params; | |
| 144 | ||
| 145 | if !primary_params? then | |
| 146 | // No primary header — a `super(...)` written in the | |
| 147 | // class body has nowhere to forward arguments to. Flag | |
| 148 | // each one and drop it from the body so downstream | |
| 149 | // visitors don't trip over the unconsumed node. | |
| 150 | _strip_orphan_super_calls(classy); | |
| 151 | return; | |
| 152 | fi | |
| 153 | ||
| 154 | // The variable parser already rejected `..` in the class | |
| 155 | // header (`in_init_arguments` is false there). What can | |
| 156 | // still reach here is a destructuring left | |
| 157 | // (e.g. `(a, b): PAIR`), which has no simple name to use | |
| 158 | // for capture matching or as the synthesised init's | |
| 159 | // formal-argument name. Flag and filter so the synthesised | |
| 160 | // init doesn't inherit a broken argument. | |
| 161 | for v in primary_params do | |
| 162 | if !v.name? then | |
| 163 | _logger.error( | |
| 164 | v.location, | |
| 165 | "primary constructor parameter must have a simple name" | |
| 166 | ); | |
| 167 | fi | |
| 168 | od | |
| 169 | ||
| 170 | primary_params = _filter_unusable_primary_params(primary_params); | |
| 171 | ||
| 172 | let class_location = classy.location; | |
| 173 | ||
| 174 | // Validate primary-param modifier combinations once up-front; | |
| 175 | // diagnostics target the param's location so the user sees the | |
| 176 | // offending param, not the body downstream. | |
| 177 | for p in primary_params do | |
| 178 | _validate_primary_param_modifiers(p); | |
| 179 | od | |
| 180 | ||
| 181 | // Index field-shaped properties by name; per primary | |
| 182 | // parameter prefer the `_foo` match then fall back to bare | |
| 183 | // `foo`; first match wins. | |
| 184 | let field_props_by_name = Collections.MAP[string, Trees.Definitions.PROPERTY](); | |
| 185 | ||
| 186 | for d in classy.body do | |
| 187 | if isa Trees.Definitions.PROPERTY(d) then | |
| 188 | let p = cast Trees.Definitions.PROPERTY?(d)!; | |
| 189 | ||
| 190 | if | |
| 191 | !p.read_body? /\ | |
| 192 | !p.assign_body? /\ | |
| 193 | p.name? /\ | |
| 194 | !field_props_by_name.contains_key(p.name.name) | |
| 195 | then | |
| 196 | field_props_by_name[p.name.name] = p; | |
| 197 | fi | |
| 198 | fi | |
| 199 | od | |
| 200 | ||
| 201 | let chosen_captures = Collections.SET[Trees.Definitions.PROPERTY](); | |
| 202 | let param_for_capture = Collections.MAP[Trees.Definitions.PROPERTY, Trees.Variables.VARIABLE](); | |
| 203 | let primary_params_by_name = Collections.MAP[string, Trees.Variables.VARIABLE](); | |
| 204 | let primary_param_names = Collections.SET[string](); | |
| 205 | let already_captured_param_names = Collections.SET[string](); | |
| 206 | ||
| 207 | for p in primary_params do | |
| 208 | let name = p.name!.name; | |
| 209 | ||
| 210 | primary_params_by_name[name] = p; | |
| 211 | primary_param_names.add(name); | |
| 212 | ||
| 213 | let chosen = _find_chosen_capture(name, field_props_by_name, chosen_captures); | |
| 214 | ||
| 215 | if chosen? then | |
| 216 | chosen_captures.add(chosen); | |
| 217 | param_for_capture[chosen] = p; | |
| 218 | already_captured_param_names.add(name); | |
| 219 | fi | |
| 220 | od | |
| 221 | ||
| 222 | // First sweep over the body — locate `super(...)` so its | |
| 223 | // argument expressions can be walked for primary-param | |
| 224 | // references (used below to suppress auto-field synthesis | |
| 225 | // for params consumed by super). The full body iteration | |
| 226 | // happens later; this pre-scan never mutates classy.body. | |
| 227 | let super_call_for_refs: Trees.Definitions.SUPER_CALL? mut = null; | |
| 228 | for d in classy.body do | |
| 229 | if isa Trees.Definitions.SUPER_CALL(d) /\ !super_call_for_refs? then | |
| 230 | super_call_for_refs = cast Trees.Definitions.SUPER_CALL(d); | |
| 231 | fi | |
| 232 | od | |
| 233 | ||
| 234 | let super_referenced_param_names = Collections.SET[string](); | |
| 235 | if super_call_for_refs? then | |
| 236 | let collector = SUPER_PARAM_REFERENCE_COLLECTOR(primary_param_names); | |
| 237 | for arg_expr in super_call_for_refs.args do | |
| 238 | arg_expr.walk(collector); | |
| 239 | od | |
| 240 | for name in collector.referenced_names do | |
| 241 | super_referenced_param_names.add(name); | |
| 242 | od | |
| 243 | fi | |
| 244 | ||
| 245 | // Auto-field synthesis: a primary param that isn't matched by | |
| 246 | // an existing body decl, isn't referenced by `super(...)`, and | |
| 247 | // doesn't carry the `init` modifier auto-generates a body | |
| 248 | // field/property. Inserted at the head of the body in primary- | |
| 249 | // header order so member ordering reads naturally. Downstream | |
| 250 | // (`add_accessors_for_properties`, `declare_symbols`) treats | |
| 251 | // the synthesised node exactly like a hand-written body decl. | |
| 252 | let auto_gen_props = Collections.LIST[Trees.Definitions.PROPERTY](); | |
| 253 | for p in primary_params do | |
| 254 | let name = p.name!.name; | |
| 255 | ||
| 256 | if already_captured_param_names.contains(name) then | |
| 257 | continue; | |
| 258 | fi | |
| 259 | if super_referenced_param_names.contains(name) then | |
| 260 | continue; | |
| 261 | fi | |
| 262 | if let p.modifiers? /\ modifiers.is_init then | |
| 263 | continue; | |
| 264 | fi | |
| 265 | let synthesised = _synthesise_auto_property(p); | |
| 266 | auto_gen_props.add(synthesised); | |
| 267 | chosen_captures.add(synthesised); | |
| 268 | param_for_capture[synthesised] = p; | |
| 269 | already_captured_param_names.add(name); | |
| 270 | od | |
| 271 | ||
| 272 | let super_call: Trees.Definitions.SUPER_CALL? mut = null; | |
| 273 | let captures = Collections.LIST[Trees.Definitions.PROPERTY](); | |
| 274 | let user_primary_init: Trees.Definitions.FUNCTION? mut = null; | |
| 275 | let secondary_inits = Collections.LIST[Trees.Definitions.FUNCTION](); | |
| 276 | let kept = Collections.LIST[Trees.Definitions.Definition](); | |
| 277 | ||
| 278 | // Auto-gen props ride at the head of the body so they precede | |
| 279 | // user-written members, matching the order of the primary | |
| 280 | // header. | |
| 281 | for synthesised in auto_gen_props do | |
| 282 | captures.add(synthesised); | |
| 283 | kept.add(synthesised); | |
| 284 | od | |
| 285 | ||
| 286 | for d in classy.body do | |
| 287 | if isa Trees.Definitions.SUPER_CALL(d) then | |
| 288 | let sc = cast Trees.Definitions.SUPER_CALL(d); | |
| 289 | ||
| 290 | if super_call? then | |
| 291 | _logger.error(sc.location, "duplicate super(...) declaration"); | |
| 292 | else | |
| 293 | super_call = sc; | |
| 294 | fi | |
| 295 | elif _has_init_modifier(d) then | |
| 296 | _logger.error( | |
| 297 | d.location, | |
| 298 | "init modifier is only valid on a primary constructor parameter" | |
| 299 | ); | |
| 300 | kept.add(d); | |
| 301 | elif isa Trees.Definitions.PROPERTY(d) /\ chosen_captures.contains(cast Trees.Definitions.PROPERTY(d)) then | |
| 302 | let capture = cast Trees.Definitions.PROPERTY(d); | |
| 303 | captures.add(capture); | |
| 304 | kept.add(d); | |
| 305 | elif _is_unmatched_capture_shorthand(d) then | |
| 306 | // Field-shaped property with INFER type but no | |
| 307 | // matching primary parameter (or the matching one | |
| 308 | // was already captured by `_foo`, leaving the bare | |
| 309 | // `foo;` shorthand stranded with no type to fall | |
| 310 | // back to). Either way the field can't reach | |
| 311 | // downstream phases — flag it and drop it. | |
| 312 | let p = cast Trees.Definitions.PROPERTY?(d)!; | |
| 313 | let stripped = _strip_underscore(p.name!.name); | |
| 314 | ||
| 315 | if already_captured_param_names.contains(stripped) then | |
| 316 | _logger.error( | |
| 317 | p.location, | |
| 318 | "primary parameter {stripped} is already captured by _{stripped}" | |
| 319 | ); | |
| 320 | else | |
| 321 | _logger.error( | |
| 322 | p.location, | |
| 323 | "no primary parameter named {stripped} to capture" | |
| 324 | ); | |
| 325 | fi | |
| 326 | elif _is_init_function(d) then | |
| 327 | let f = cast Trees.Definitions.FUNCTION?(d)!; | |
| 328 | let has_splice = _arg_list_has_splice(f.arguments); | |
| 329 | ||
| 330 | if has_splice then | |
| 331 | let splice_count = _count_splices(f.arguments); | |
| 332 | ||
| 333 | if splice_count > 1 then | |
| 334 | _logger.error(f.location, "init parameter list contains more than one .."); | |
| 335 | fi | |
| 336 | ||
| 337 | if _is_primary_init(f) then | |
| 338 | if user_primary_init? then | |
| 339 | _logger.error(f.location, "duplicate primary constructor init body"); | |
| 340 | else | |
| 341 | user_primary_init = f; | |
| 342 | fi | |
| 343 | else | |
| 344 | secondary_inits.add(f); | |
| 345 | fi | |
| 346 | else | |
| 347 | kept.add(d); | |
| 348 | fi | |
| 349 | else | |
| 350 | kept.add(d); | |
| 351 | fi | |
| 352 | od | |
| 353 | ||
| 354 | // Fill in INFER type expressions from the chosen primary | |
| 355 | // parameter. Captures with an explicit type (e.g. | |
| 356 | // `x: int public;` to widen visibility) keep their type. | |
| 357 | for capture in captures do | |
| 358 | let matching = param_for_capture[capture]; | |
| 359 | ||
| 360 | if capture.type_expression.is_inferred then | |
| 361 | capture.set_type_expression(matching.type_expression.copy()); | |
| 362 | fi | |
| 363 | od | |
| 364 | ||
| 365 | // Synthesise the per-capture assignment statements. | |
| 366 | let auto_statements = Collections.LIST[Trees.Statements.Statement](); | |
| 367 | ||
| 368 | if super_call? then | |
| 369 | auto_statements.add(_make_super_init_call(super_call)); | |
| 370 | fi | |
| 371 | ||
| 372 | for capture in captures do | |
| 373 | auto_statements.add(_make_capture_assignment(capture, param_for_capture[capture])); | |
| 374 | od | |
| 375 | ||
| 376 | // If the user wrote `init(..) is body si`, splice their | |
| 377 | // statements in after the auto-generated assignments. | |
| 378 | if user_primary_init? /\ user_primary_init.body? /\ isa Trees.Bodies.BLOCK(user_primary_init.body) then | |
| 379 | let user_block = cast Trees.Bodies.BLOCK(user_primary_init.body); | |
| 380 | ||
| 381 | for s in user_block.statements do | |
| 382 | auto_statements.add(s); | |
| 383 | od | |
| 384 | fi | |
| 385 | ||
| 386 | // The BLOCK's own location is internal: the body has no | |
| 387 | // source text the user wrote, and giving it the class span | |
| 388 | // would make the incremental body re-walk treat every | |
| 389 | // interface symbol on the class header (the class itself, | |
| 390 | // its auto-properties) as `inside` this body and drop them | |
| 391 | // from the symbol-definition map after an EDIT. | |
| 392 | let primary_init_body = | |
| 393 | Trees.Bodies.BLOCK( | |
| 394 | LOCATION.internal, | |
| 395 | Trees.Statements.LIST(LOCATION.internal, auto_statements) | |
| 396 | ); | |
| 397 | ||
| 398 | // Deep-copy the primary parameters into a fresh argument | |
| 399 | // list for the synthesised init — keeps the original list | |
| 400 | // available for the secondary-init splice expansion below | |
| 401 | // without aliasing the same VARIABLE instances across two | |
| 402 | // FUNCTION nodes. | |
| 403 | let primary_init_args = _copy_variable_list(primary_params); | |
| 404 | ||
| 405 | // Anchor the synthesised init's overall span to the class | |
| 406 | // header (class name through primary parameter list). A | |
| 407 | // user-written `init(...)` at the same signature picks up | |
| 408 | // the duplicate-method diagnostic against this range; the | |
| 409 | // synthesised init also surfaces in the VSCE outline at | |
| 410 | // the class-header position. | |
| 411 | // | |
| 412 | // The name identifier is anchored separately: when the | |
| 413 | // user wrote `init(..) is ... si`, point it at the user's | |
| 414 | // own `init` source location so hover, goto-definition, | |
| 415 | // and the semantic-tokens classifier resolve to this | |
| 416 | // symbol at the user-typed keyword. Without that, the | |
| 417 | // user's `init` has no recorded symbol use and falls | |
| 418 | // back to TextMate colouring with no hover. For the | |
| 419 | // auto-generated case (no user `init(..)`) the name stays | |
| 420 | // at the class header. | |
| 421 | let header_location = classy.name.location :: primary_params.location; | |
| 422 | ||
| 423 | let primary_init_name = | |
| 424 | if user_primary_init? /\ user_primary_init.name? then | |
| 425 | user_primary_init.name.copy(); | |
| 426 | else | |
| 427 | Trees.Identifiers.Identifier(header_location, "init"); | |
| 428 | fi; | |
| 429 | ||
| 430 | let primary_init = | |
| 431 | Trees.Definitions.FUNCTION( | |
| 432 | header_location, | |
| 433 | primary_init_name, | |
| 434 | Trees.TypeExpressions.LIST(LOCATION.internal, Collections.LIST[Trees.TypeExpressions.TypeExpression](0)), | |
| 435 | primary_init_args, | |
| 436 | Trees.TypeExpressions.INFER(class_location), | |
| 437 | Trees.Modifiers.LIST(class_location, null, null), | |
| 438 | primary_init_body | |
| 439 | ); | |
| 440 | ||
| 441 | primary_init.is_primary_constructor = true; | |
| 442 | ||
| 443 | kept.add(primary_init); | |
| 444 | ||
| 445 | // Re-attach each secondary init with its splice expanded | |
| 446 | // and an implicit chain to the primary init prepended. | |
| 447 | for f in secondary_inits do | |
| 448 | _expand_secondary_init(f, primary_params); | |
| 449 | kept.add(f); | |
| 450 | od | |
| 451 | ||
| 452 | // Auto-deconstruct synthesis. When the user has not written a | |
| 453 | // `deconstruct(...)` method and has not exposed any | |
| 454 | // conventionally-named positional members (`0`, `1`, ...), | |
| 455 | // synthesise a `deconstruct` exposing every public-readable | |
| 456 | // capture in primary-header order. The resolver's precedence | |
| 457 | // (tuple > deconstruct > positional > by-name) means a | |
| 458 | // user-supplied `0`/`1`/... continues to be the explicit opt-in | |
| 459 | // to positional access — co-existing with the synthesised | |
| 460 | // method would make it unreachable. | |
| 461 | _maybe_synthesise_deconstruct( | |
| 462 | classy, | |
| 463 | class_location, | |
| 464 | primary_params, | |
| 465 | param_for_capture, | |
| 466 | already_captured_param_names, | |
| 467 | kept | |
| 468 | ); | |
| 469 | ||
| 470 | // Replace the body's contents with the curated list. | |
| 471 | classy.body.clear_definitions(); | |
| 472 | ||
| 473 | for d in kept do | |
| 474 | classy.body.add(d); | |
| 475 | od | |
| 476 | ||
| 477 | classy.set_primary_params(null); | |
| 478 | si | |
| 479 | ||
| 480 | _find_chosen_capture( | |
| 481 | param_name: string, | |
| 482 | field_props_by_name: Collections.MAP[string, Trees.Definitions.PROPERTY], | |
| 483 | already_chosen: Collections.SET[Trees.Definitions.PROPERTY] | |
| 484 | ) -> Trees.Definitions.PROPERTY? is | |
| 485 | let underscore_name = "_{param_name}"; | |
| 486 | ||
| 487 | if field_props_by_name.contains_key(underscore_name) then | |
| 488 | let candidate = field_props_by_name[underscore_name]; | |
| 489 | ||
| 490 | if !already_chosen.contains(candidate) then | |
| 491 | return candidate; | |
| 492 | fi | |
| 493 | fi | |
| 494 | ||
| 495 | if field_props_by_name.contains_key(param_name) then | |
| 496 | let candidate = field_props_by_name[param_name]; | |
| 497 | ||
| 498 | if !already_chosen.contains(candidate) then | |
| 499 | return candidate; | |
| 500 | fi | |
| 501 | fi | |
| 502 | ||
| 503 | return null; | |
| 504 | si | |
| 505 | ||
| 506 | _filter_unusable_primary_params(args: Trees.Variables.LIST) -> Trees.Variables.LIST is | |
| 507 | let kept = Collections.LIST[Trees.Variables.VARIABLE](); | |
| 508 | ||
| 509 | for v in args do | |
| 510 | if v.name? then | |
| 511 | kept.add(v); | |
| 512 | fi | |
| 513 | od | |
| 514 | ||
| 515 | if kept.count == args.count then | |
| 516 | return args; | |
| 517 | fi | |
| 518 | ||
| 519 | return Trees.Variables.LIST(args.location, kept); | |
| 520 | si | |
| 521 | ||
| 522 | _strip_orphan_super_calls(classy: Trees.Definitions.Classy) is | |
| 523 | let kept = Collections.LIST[Trees.Definitions.Definition](); | |
| 524 | let any_dropped mut = false; | |
| 525 | ||
| 526 | for d in classy.body do | |
| 527 | if isa Trees.Definitions.SUPER_CALL(d) then | |
| 528 | _logger.error( | |
| 529 | d.location, | |
| 530 | "super(...) declaration requires a primary constructor header" | |
| 531 | ); | |
| 532 | any_dropped = true; | |
| 533 | else | |
| 534 | if _has_init_modifier(d) then | |
| 535 | _logger.error( | |
| 536 | d.location, | |
| 537 | "init modifier is only valid on a primary constructor parameter" | |
| 538 | ); | |
| 539 | fi | |
| 540 | kept.add(d); | |
| 541 | fi | |
| 542 | od | |
| 543 | ||
| 544 | if any_dropped then | |
| 545 | classy.body.clear_definitions(); | |
| 546 | ||
| 547 | for d in kept do | |
| 548 | classy.body.add(d); | |
| 549 | od | |
| 550 | fi | |
| 551 | si | |
| 552 | ||
| 553 | _is_unmatched_capture_shorthand(d: Trees.Definitions.Definition) -> bool is | |
| 554 | if !isa Trees.Definitions.PROPERTY(d) then | |
| 555 | return false; | |
| 556 | fi | |
| 557 | ||
| 558 | let p = cast Trees.Definitions.PROPERTY(d); | |
| 559 | ||
| 560 | if p.read_body? \/ p.assign_body? then | |
| 561 | return false; | |
| 562 | fi | |
| 563 | ||
| 564 | return p.name? /\ p.type_expression.is_inferred; | |
| 565 | si | |
| 566 | ||
| 567 | _is_init_function(d: Trees.Definitions.Definition) -> bool is | |
| 568 | if !isa Trees.Definitions.FUNCTION(d) then | |
| 569 | return false; | |
| 570 | fi | |
| 571 | ||
| 572 | let f = cast Trees.Definitions.FUNCTION(d); | |
| 573 | ||
| 574 | return f.name? /\ f.name.name =~ "init"; | |
| 575 | si | |
| 576 | ||
| 577 | _is_primary_init(f: Trees.Definitions.FUNCTION) -> bool is | |
| 578 | // init(..) — exactly one parameter and it's the splice marker. | |
| 579 | let args = f.arguments.variables; | |
| 580 | ||
| 581 | return args.count == 1 /\ args[0].is_splice; | |
| 582 | si | |
| 583 | ||
| 584 | _arg_list_has_splice(args: Trees.Variables.LIST?) -> bool is | |
| 585 | if !args? then | |
| 586 | return false; | |
| 587 | fi | |
| 588 | ||
| 589 | for v in args do | |
| 590 | if v.is_splice then | |
| 591 | return true; | |
| 592 | fi | |
| 593 | od | |
| 594 | ||
| 595 | return false; | |
| 596 | si | |
| 597 | ||
| 598 | _count_splices(args: Trees.Variables.LIST?) -> int is | |
| 599 | if !args? then | |
| 600 | return 0; | |
| 601 | fi | |
| 602 | ||
| 603 | let n mut = 0; | |
| 604 | ||
| 605 | for v in args do | |
| 606 | if v.is_splice then | |
| 607 | n = n + 1; | |
| 608 | fi | |
| 609 | od | |
| 610 | ||
| 611 | return n; | |
| 612 | si | |
| 613 | ||
| 614 | _strip_underscore(name: string) -> string is | |
| 615 | if name.length > 0 /\ name.get_chars(0) == '_' then | |
| 616 | return name.substring(1); | |
| 617 | fi | |
| 618 | ||
| 619 | return name; | |
| 620 | si | |
| 621 | ||
| 622 | // Diagnoses incoherent combinations on a primary-ctor parameter: | |
| 623 | // - `init` combined with any visibility modifier or with another | |
| 624 | // storage modifier means "no field, but also make the (non- | |
| 625 | // existent) field public / a plain field" — incoherent. | |
| 626 | // - `init` combined with a `_`-prefixed name means "no field, but | |
| 627 | // the name implies a private field" — same incoherence. | |
| 628 | // The rewriter only flags; it doesn't strip, so the user sees | |
| 629 | // every diagnostic in one pass. | |
| 630 | _validate_primary_param_modifiers(p: Trees.Variables.VARIABLE) is | |
| 631 | let modifiers = p.modifiers; | |
| 632 | ||
| 633 | if !modifiers? then | |
| 634 | return; | |
| 635 | fi | |
| 636 | ||
| 637 | if modifiers.is_init then | |
| 638 | if let modifiers.access_modifier? then | |
| 639 | _logger.error( | |
| 640 | access_modifier.location, | |
| 641 | "init modifier cannot combine with visibility modifiers" | |
| 642 | ); | |
| 643 | fi | |
| 644 | if let | |
| 645 | modifiers.storage_class? /\ | |
| 646 | !storage_class.is_init | |
| 647 | then | |
| 648 | _logger.error( | |
| 649 | storage_class.location, | |
| 650 | "init modifier cannot combine with other storage modifiers" | |
| 651 | ); | |
| 652 | fi | |
| 653 | if let | |
| 654 | p.name? /\ | |
| 655 | name.name.length > 0 /\ | |
| 656 | name.name.get_chars(0) == '_' | |
| 657 | then | |
| 658 | _logger.error( | |
| 659 | p.location, | |
| 660 | "init modifier cannot combine with a _-prefixed parameter name" | |
| 661 | ); | |
| 662 | fi | |
| 663 | fi | |
| 664 | si | |
| 665 | ||
| 666 | // Body decls with the `init` modifier are diagnosed at the body- | |
| 667 | // iteration step. This helper recognises the (FUNCTION / PROPERTY) | |
| 668 | // shapes that carry modifier lists. | |
| 669 | _has_init_modifier(d: Trees.Definitions.Definition) -> bool is | |
| 670 | if isa Trees.Definitions.PROPERTY(d) then | |
| 671 | let p = cast Trees.Definitions.PROPERTY(d); | |
| 672 | return p.modifiers.is_init; | |
| 673 | fi | |
| 674 | if isa Trees.Definitions.FUNCTION(d) then | |
| 675 | let f = cast Trees.Definitions.FUNCTION(d); | |
| 676 | return f.modifiers.is_init; | |
| 677 | fi | |
| 678 | return false; | |
| 679 | si | |
| 680 | ||
| 681 | // Synthesise a body-level field/property declaration mirroring the | |
| 682 | // primary-ctor parameter, ready to flow through | |
| 683 | // `add_accessors_for_properties` and `declare_symbols` as if the | |
| 684 | // user had written it by hand. Strips the `init` modifier from | |
| 685 | // the synthesised modifier list as a safety net — even though the | |
| 686 | // caller has already filtered INIT params, leaving INIT on the | |
| 687 | // body decl would re-trigger the "init only on primary param" | |
| 688 | // diagnostic. | |
| 689 | _synthesise_auto_property(p: Trees.Variables.VARIABLE) -> Trees.Definitions.PROPERTY => | |
| 690 | let loc = p.location in | |
| 691 | let modifiers = _make_body_modifiers(p) in | |
| 692 | Trees.Definitions.PROPERTY( | |
| 693 | loc, | |
| 694 | p.type_expression.copy(), | |
| 695 | _body_property_name(p), | |
| 696 | modifiers, | |
| 697 | null, | |
| 698 | null, | |
| 699 | null | |
| 700 | ); | |
| 701 | ||
| 702 | // `private` names the member the way the naming convention does: | |
| 703 | // `v: int private` captures into `_v`, leaving the parameter | |
| 704 | // itself as `v`. The underscore is what the later passes read for | |
| 705 | // visibility, so `_make_body_modifiers` drops the modifier rather | |
| 706 | // than carrying both spellings of the same fact. | |
| 707 | _body_property_name(p: Trees.Variables.VARIABLE) -> Trees.Identifiers.Identifier is | |
| 708 | let name = p.name!; | |
| 709 | ||
| 710 | if _is_private_param(p) /\ !name.name.starts_with('_') then | |
| 711 | return Trees.Identifiers.Identifier(name.location, "_{name.name}"); | |
| 712 | fi | |
| 713 | ||
| 714 | return name.copy(); | |
| 715 | si | |
| 716 | ||
| 717 | _is_private_param(p: Trees.Variables.VARIABLE) -> bool is | |
| 718 | if let p.modifiers?, modifiers.access_modifier? /\ access_modifier.is_private then | |
| 719 | return true; | |
| 720 | fi | |
| 721 | ||
| 722 | return false; | |
| 723 | si | |
| 724 | ||
| 725 | _make_body_modifiers(p: Trees.Variables.VARIABLE) -> Trees.Modifiers.LIST is | |
| 726 | let loc = p.location; | |
| 727 | let modifiers = p.modifiers; | |
| 728 | ||
| 729 | if !modifiers? then | |
| 730 | return Trees.Modifiers.LIST(loc, null, null); | |
| 731 | fi | |
| 732 | ||
| 733 | let access: Trees.Modifiers.AccessModifier? mut = null; | |
| 734 | let storage: Trees.Modifiers.StorageClass? mut = null; | |
| 735 | ||
| 736 | if let modifiers.access_modifier? /\ !access_modifier.is_private then | |
| 737 | access = access_modifier.copy(); | |
| 738 | fi | |
| 739 | ||
| 740 | // Drop INIT — it has no meaning on the body decl. Other | |
| 741 | // storage classes (FIELD, etc.) flow through. | |
| 742 | if let modifiers.storage_class? /\ !storage_class.is_init then | |
| 743 | storage = storage_class.copy(); | |
| 744 | fi | |
| 745 | ||
| 746 | return Trees.Modifiers.LIST(loc, access, storage); | |
| 747 | si | |
| 748 | ||
| 749 | // See `DESTRUCTURE_RESOLVER` for the precedence the synthesised | |
| 750 | // deconstruct slots into. Guard preconditions: | |
| 751 | // - User wrote no `deconstruct` of any arity in this class body. | |
| 752 | // - User exposed no backtick-numeric property (`0`, `1`, ...). | |
| 753 | // - At least one public-readable capture exists to expose. | |
| 754 | // The synthesised method is `public`, instance, with one `T ref` | |
| 755 | // parameter per included capture (named after the capture so the | |
| 756 | // body reads naturally; the destructure call site only ever | |
| 757 | // matches by arity + ref-ness). | |
| 758 | _maybe_synthesise_deconstruct( | |
| 759 | classy: Trees.Definitions.Classy, | |
| 760 | class_location: LOCATION, | |
| 761 | primary_params: Trees.Variables.LIST, | |
| 762 | param_for_capture: Collections.MAP[Trees.Definitions.PROPERTY, Trees.Variables.VARIABLE], | |
| 763 | already_captured_param_names: Collections.SET[string], | |
| 764 | kept: Collections.MutableList[Trees.Definitions.Definition] | |
| 765 | ) is | |
| 766 | for d in classy.body do | |
| 767 | if isa Trees.Definitions.FUNCTION(d) then | |
| 768 | let f = cast Trees.Definitions.FUNCTION?(d)!; | |
| 769 | if f.name? /\ f.name.name =~ "deconstruct" then | |
| 770 | return; | |
| 771 | fi | |
| 772 | elif isa Trees.Definitions.PROPERTY(d) then | |
| 773 | let p = cast Trees.Definitions.PROPERTY?(d)!; | |
| 774 | if p.name? /\ Semantic.Symbols.Symbol.is_positional_member_name(p.name.name) then | |
| 775 | return; | |
| 776 | fi | |
| 777 | fi | |
| 778 | od | |
| 779 | ||
| 780 | // Reverse-lookup table: primary-ctor parameter name -> its | |
| 781 | // capture property. Built once so the capture-collection loop | |
| 782 | // below stays O(captures). | |
| 783 | let capture_for_param_name = Collections.MAP[string, Trees.Definitions.PROPERTY](); | |
| 784 | for kvp in param_for_capture do | |
| 785 | let cap = kvp.key; | |
| 786 | let param = kvp.value; | |
| 787 | if let param.name? then | |
| 788 | capture_for_param_name[name.name] = cap; | |
| 789 | fi | |
| 790 | od | |
| 791 | ||
| 792 | let deconstruct_captures = Collections.LIST[Trees.Definitions.PROPERTY](); | |
| 793 | for p in primary_params do | |
| 794 | let name = p.name; | |
| 795 | if !name? then | |
| 796 | continue; | |
| 797 | fi | |
| 798 | if !already_captured_param_names.contains(name.name) then | |
| 799 | continue; | |
| 800 | fi | |
| 801 | if !capture_for_param_name.contains_key(name.name) then | |
| 802 | continue; | |
| 803 | fi | |
| 804 | let capture = capture_for_param_name[name.name]; | |
| 805 | if !_is_public_readable_capture(capture) then | |
| 806 | continue; | |
| 807 | fi | |
| 808 | deconstruct_captures.add(capture); | |
| 809 | od | |
| 810 | ||
| 811 | if deconstruct_captures.count == 0 then | |
| 812 | return; | |
| 813 | fi | |
| 814 | ||
| 815 | let deconstruct = _synthesise_deconstruct(class_location, deconstruct_captures); | |
| 816 | ||
| 817 | // Wrap in an @IL.name("Deconstruct") pragma so the method | |
| 818 | // satisfies the standard .NET `Deconstruct` contract used by | |
| 819 | // C# positional patterns and other cross-language consumers. | |
| 820 | kept.add(_wrap_in_il_name_pragma(class_location, "Deconstruct", deconstruct)); | |
| 821 | si | |
| 822 | ||
| 823 | _is_public_readable_capture(p: Trees.Definitions.PROPERTY) -> bool is | |
| 824 | // ghūl carries read-visibility two ways: an explicit | |
| 825 | // access modifier, or a leading `_` on the name (the | |
| 826 | // convention: "_x" -> protected for reading). Both must | |
| 827 | // be checked — the synthesised deconstruct is `public`, | |
| 828 | // and surfacing a protected member through it would | |
| 829 | // bypass the encapsulation the user signalled. | |
| 830 | if let p.modifiers?, modifiers.access_modifier? /\ access_modifier.is_private then | |
| 831 | return false; | |
| 832 | fi | |
| 833 | if p.name? /\ p.name.name.length > 0 /\ p.name.name.get_chars(0) == '_' then | |
| 834 | return false; | |
| 835 | fi | |
| 836 | return true; | |
| 837 | si | |
| 838 | ||
| 839 | _synthesise_deconstruct( | |
| 840 | class_location: LOCATION, | |
| 841 | captures: Collections.List[Trees.Definitions.PROPERTY] | |
| 842 | ) -> Trees.Definitions.FUNCTION is | |
| 843 | let arg_vars = Collections.LIST[Trees.Variables.VARIABLE](); | |
| 844 | let body_stmts = Collections.LIST[Trees.Statements.Statement](); | |
| 845 | ||
| 846 | for capture in captures do | |
| 847 | let cap_loc = capture.location; | |
| 848 | let arg_type = | |
| 849 | Trees.TypeExpressions.REFERENCE( | |
| 850 | cap_loc, | |
| 851 | capture.type_expression.copy() | |
| 852 | ); | |
| 853 | let arg = | |
| 854 | Trees.Variables.VARIABLE( | |
| 855 | cap_loc, | |
| 856 | capture.name!.copy(), | |
| 857 | arg_type, | |
| 858 | false, | |
| 859 | true, | |
| 860 | null | |
| 861 | ); | |
| 862 | arg.mark_argument(); | |
| 863 | arg_vars.add(arg); | |
| 864 | ||
| 865 | body_stmts.add(_make_deconstruct_assignment(capture)); | |
| 866 | od | |
| 867 | ||
| 868 | let arg_list = Trees.Variables.LIST(class_location, arg_vars); | |
| 869 | let body = | |
| 870 | Trees.Bodies.BLOCK( | |
| 871 | LOCATION.internal, | |
| 872 | Trees.Statements.LIST(LOCATION.internal, body_stmts) | |
| 873 | ); | |
| 874 | let modifiers = | |
| 875 | Trees.Modifiers.LIST( | |
| 876 | class_location, | |
| 877 | Trees.Modifiers.PUBLIC(class_location), | |
| 878 | null | |
| 879 | ); | |
| 880 | ||
| 881 | return | |
| 882 | Trees.Definitions.FUNCTION( | |
| 883 | class_location, | |
| 884 | Trees.Identifiers.Identifier(LOCATION.internal, "deconstruct"), | |
| 885 | Trees.TypeExpressions.LIST(LOCATION.internal, Collections.LIST[Trees.TypeExpressions.TypeExpression](0)), | |
| 886 | arg_list, | |
| 887 | Trees.TypeExpressions.INFER(class_location), | |
| 888 | modifiers, | |
| 889 | body | |
| 890 | ); | |
| 891 | si | |
| 892 | ||
| 893 | _make_deconstruct_assignment( | |
| 894 | capture: Trees.Definitions.PROPERTY | |
| 895 | ) -> Trees.Statements.Statement is | |
| 896 | let loc = capture.location; | |
| 897 | ||
| 898 | // <capture-name>! = self.<capture-name>; | |
| 899 | // | |
| 900 | // Postfix `!` on a `T ref` LHS lowers to a `stobj <T>` — | |
| 901 | // the write-through-ref form. Without it, | |
| 902 | // `<capture-name> = self.<...>` would error with | |
| 903 | // "Ghul.T is not assignable to Ghul.T ref". | |
| 904 | let arg_ref = | |
| 905 | Trees.Expressions.IDENTIFIER(loc, capture.name!.copy()); | |
| 906 | let unwrap = Trees.Expressions.UNWRAP(loc, arg_ref); | |
| 907 | let left = Trees.Expressions.SIMPLE_LEFT_EXPRESSION(loc, unwrap); | |
| 908 | ||
| 909 | let self_expr = Trees.Expressions.SELF(loc); | |
| 910 | let right = | |
| 911 | Trees.Expressions.MEMBER( | |
| 912 | loc, | |
| 913 | self_expr, | |
| 914 | capture.name!.copy(), | |
| 915 | loc | |
| 916 | ); | |
| 917 | ||
| 918 | return Trees.Statements.ASSIGNMENT(loc, left, right); | |
| 919 | si | |
| 920 | ||
| 921 | _wrap_in_il_name_pragma( | |
| 922 | loc: LOCATION, | |
| 923 | il_name: string, | |
| 924 | definition: Trees.Definitions.Definition | |
| 925 | ) -> Trees.Definitions.PRAGMA is | |
| 926 | let name_literal = Trees.Expressions.Literals.STRING(loc, il_name); | |
| 927 | let args = Collections.LIST[Trees.Expressions.Expression](); | |
| 928 | args.add(name_literal); | |
| 929 | let pragma = | |
| 930 | Trees.Pragmas.PRAGMA( | |
| 931 | loc, | |
| 932 | Trees.Identifiers.Identifier(loc, "IL.name"), | |
| 933 | Trees.Expressions.LIST(loc, args), | |
| 934 | null | |
| 935 | ); | |
| 936 | return Trees.Definitions.PRAGMA(loc, pragma, definition); | |
| 937 | si | |
| 938 | ||
| 939 | _make_super_init_call( | |
| 940 | super_call: Trees.Definitions.SUPER_CALL | |
| 941 | ) -> Trees.Statements.Statement is | |
| 942 | let loc = super_call.location; | |
| 943 | let arg_exprs = Collections.LIST[Trees.Expressions.Expression](); | |
| 944 | ||
| 945 | for arg_expr in super_call.args do | |
| 946 | arg_exprs.add(arg_expr); | |
| 947 | od | |
| 948 | ||
| 949 | // super.init(args) | |
| 950 | let super_expr = Trees.Expressions.SUPER(loc); | |
| 951 | let init_identifier = Trees.Identifiers.Identifier(loc, "init"); | |
| 952 | let member = | |
| 953 | Trees.Expressions.MEMBER( | |
| 954 | loc, | |
| 955 | super_expr, | |
| 956 | init_identifier, | |
| 957 | loc | |
| 958 | ); | |
| 959 | let call = | |
| 960 | Trees.Expressions.CALL( | |
| 961 | loc, | |
| 962 | member, | |
| 963 | Trees.Expressions.LIST(loc, arg_exprs) | |
| 964 | ); | |
| 965 | ||
| 966 | return Trees.Statements.EXPRESSION(loc, call); | |
| 967 | si | |
| 968 | ||
| 969 | _make_capture_assignment(capture: Trees.Definitions.PROPERTY, param: Trees.Variables.VARIABLE) -> Trees.Statements.Statement is | |
| 970 | let loc = capture.location; | |
| 971 | ||
| 972 | // self.<capture-name> = <param-name>; | |
| 973 | let self_expr = Trees.Expressions.SELF(loc); | |
| 974 | let member = | |
| 975 | Trees.Expressions.MEMBER( | |
| 976 | loc, | |
| 977 | self_expr, | |
| 978 | capture.name!.copy(), | |
| 979 | loc | |
| 980 | ); | |
| 981 | let left = Trees.Expressions.SIMPLE_LEFT_EXPRESSION(loc, member); | |
| 982 | let right = | |
| 983 | Trees.Expressions.IDENTIFIER( | |
| 984 | loc, | |
| 985 | param.name!.copy() | |
| 986 | ); | |
| 987 | ||
| 988 | return Trees.Statements.ASSIGNMENT(loc, left, right); | |
| 989 | si | |
| 990 | ||
| 991 | // Expand `..` in the variant's field list against the | |
| 992 | // enclosing union's primary parameters. The splice marker is | |
| 993 | // replaced in-place by deep-copies of the primary params, each | |
| 994 | // marked is_inherited_primary. They stay in variant.fields so | |
| 995 | // every existing consumer (arity, equality, hash, destructure) | |
| 996 | // continues to see the variant's full shape. declare_symbols | |
| 997 | // skips declaring inherited entries as variant-side fields — | |
| 998 | // the union base owns the storage and variants inherit through | |
| 999 | // the union → variant base-class relationship. The variant's | |
| 1000 | // synthesised init forwards inherited args to super.init(...); | |
| 1001 | // non-inherited fields get the usual self.<f> = <f>. Validates | |
| 1002 | // exactly one `..` per variant. | |
| 1003 | _expand_variant_splice(variant: Trees.Definitions.VARIANT, primary_params: Trees.Variables.LIST) is | |
| 1004 | if variant.is_poisoned then | |
| 1005 | return; | |
| 1006 | fi | |
| 1007 | ||
| 1008 | let splice_count = _count_splices(variant.fields); | |
| 1009 | ||
| 1010 | if splice_count == 0 then | |
| 1011 | if variant.fields.count == 0 then | |
| 1012 | // No field list — splice the primary parameters as if | |
| 1013 | // the user had written `(..)`. | |
| 1014 | let new_fields = Collections.LIST[Trees.Variables.VARIABLE](); | |
| 1015 | ||
| 1016 | for p in primary_params do | |
| 1017 | let inherited = _copy_variable(p); | |
| 1018 | inherited.mark_inherited_primary(); | |
| 1019 | new_fields.add(inherited); | |
| 1020 | od | |
| 1021 | ||
| 1022 | variant.set_fields(Trees.Variables.LIST(variant.fields.location, new_fields)); | |
| 1023 | return; | |
| 1024 | fi | |
| 1025 | ||
| 1026 | _logger.error( | |
| 1027 | variant.location, | |
| 1028 | "variant of a union with a primary constructor header must include .. to splice in the primary parameters" | |
| 1029 | ); | |
| 1030 | return; | |
| 1031 | fi | |
| 1032 | ||
| 1033 | if splice_count > 1 then | |
| 1034 | _logger.error( | |
| 1035 | variant.location, | |
| 1036 | "variant field list contains more than one .." | |
| 1037 | ); | |
| 1038 | fi | |
| 1039 | ||
| 1040 | let new_fields = Collections.LIST[Trees.Variables.VARIABLE](); | |
| 1041 | let expanded mut = false; | |
| 1042 | ||
| 1043 | for v in variant.fields do | |
| 1044 | if v.is_splice then | |
| 1045 | if !expanded then | |
| 1046 | for p in primary_params do | |
| 1047 | let inherited = _copy_variable(p); | |
| 1048 | inherited.mark_inherited_primary(); | |
| 1049 | new_fields.add(inherited); | |
| 1050 | od | |
| 1051 | ||
| 1052 | expanded = true; | |
| 1053 | fi | |
| 1054 | else | |
| 1055 | new_fields.add(v); | |
| 1056 | fi | |
| 1057 | od | |
| 1058 | ||
| 1059 | variant.set_fields(Trees.Variables.LIST(variant.fields.location, new_fields)); | |
| 1060 | si | |
| 1061 | ||
| 1062 | _reject_stray_splice(variant: Trees.Definitions.VARIANT) is | |
| 1063 | if variant.is_poisoned then | |
| 1064 | return; | |
| 1065 | fi | |
| 1066 | ||
| 1067 | for v in variant.fields do | |
| 1068 | if v.is_splice /\ !v.is_poisoned then | |
| 1069 | _logger.error( | |
| 1070 | v.location, | |
| 1071 | ".. requires the surrounding union to declare a primary constructor header" | |
| 1072 | ); | |
| 1073 | v.poison(true); | |
| 1074 | fi | |
| 1075 | od | |
| 1076 | si | |
| 1077 | ||
| 1078 | _expand_secondary_init(f: Trees.Definitions.FUNCTION, primary_params: Trees.Variables.LIST) is | |
| 1079 | // Build the new argument list: every existing argument | |
| 1080 | // except the splice marker, with the primary parameters | |
| 1081 | // spliced in at the first splice's position. Any extra | |
| 1082 | // splice markers (already flagged as errors above) are | |
| 1083 | // dropped — expanding more than one would duplicate the | |
| 1084 | // primary parameter names and cascade into redefinition | |
| 1085 | // diagnostics that the user's own error already accounts | |
| 1086 | // for. | |
| 1087 | let old_args = f.arguments.variables; | |
| 1088 | let new_args = Collections.LIST[Trees.Variables.VARIABLE](); | |
| 1089 | let expanded mut = false; | |
| 1090 | ||
| 1091 | for v in old_args do | |
| 1092 | if v.is_splice then | |
| 1093 | if !expanded then | |
| 1094 | for p in primary_params do | |
| 1095 | new_args.add(_copy_variable(p)); | |
| 1096 | od | |
| 1097 | ||
| 1098 | expanded = true; | |
| 1099 | fi | |
| 1100 | else | |
| 1101 | new_args.add(v); | |
| 1102 | fi | |
| 1103 | od | |
| 1104 | ||
| 1105 | let expanded_args = | |
| 1106 | Trees.Variables.LIST( | |
| 1107 | f.arguments.location, | |
| 1108 | new_args | |
| 1109 | ); | |
| 1110 | ||
| 1111 | f.arguments = expanded_args; | |
| 1112 | ||
| 1113 | // Prepend `self.init(<primary args>);` to the body. | |
| 1114 | if f.body? /\ isa Trees.Bodies.BLOCK(f.body) then | |
| 1115 | let block = cast Trees.Bodies.BLOCK(f.body); | |
| 1116 | let chain = _make_self_init_chain(f.location, primary_params); | |
| 1117 | ||
| 1118 | let new_statements = Collections.LIST[Trees.Statements.Statement](); | |
| 1119 | new_statements.add(chain); | |
| 1120 | ||
| 1121 | for s in block.statements do | |
| 1122 | new_statements.add(s); | |
| 1123 | od | |
| 1124 | ||
| 1125 | block.statements.replace_all(new_statements); | |
| 1126 | fi | |
| 1127 | si | |
| 1128 | ||
| 1129 | _make_self_init_chain(loc: LOCATION, primary_params: Trees.Variables.LIST) -> Trees.Statements.Statement is | |
| 1130 | let arg_exprs = Collections.LIST[Trees.Expressions.Expression](); | |
| 1131 | ||
| 1132 | for p in primary_params do | |
| 1133 | arg_exprs.add( | |
| 1134 | Trees.Expressions.IDENTIFIER(loc, p.name!.copy()) | |
| 1135 | ); | |
| 1136 | od | |
| 1137 | ||
| 1138 | let self_expr = Trees.Expressions.SELF(loc); | |
| 1139 | let init_identifier = Trees.Identifiers.Identifier(loc, "init"); | |
| 1140 | let member = | |
| 1141 | Trees.Expressions.MEMBER( | |
| 1142 | loc, | |
| 1143 | self_expr, | |
| 1144 | init_identifier, | |
| 1145 | loc | |
| 1146 | ); | |
| 1147 | let call = | |
| 1148 | Trees.Expressions.CALL( | |
| 1149 | loc, | |
| 1150 | member, | |
| 1151 | Trees.Expressions.LIST(loc, arg_exprs) | |
| 1152 | ); | |
| 1153 | ||
| 1154 | return Trees.Statements.EXPRESSION(loc, call); | |
| 1155 | si | |
| 1156 | ||
| 1157 | _copy_variable(v: Trees.Variables.VARIABLE) -> Trees.Variables.VARIABLE is | |
| 1158 | let result = | |
| 1159 | Trees.Variables.VARIABLE( | |
| 1160 | v.location, | |
| 1161 | v.name!.copy(), | |
| 1162 | v.type_expression.copy(), | |
| 1163 | false, | |
| 1164 | true, | |
| 1165 | null | |
| 1166 | ); | |
| 1167 | ||
| 1168 | result.mark_argument(); | |
| 1169 | ||
| 1170 | return result; | |
| 1171 | si | |
| 1172 | ||
| 1173 | _copy_variable_list(list: Trees.Variables.LIST) -> Trees.Variables.LIST is | |
| 1174 | let copied = Collections.LIST[Trees.Variables.VARIABLE](); | |
| 1175 | ||
| 1176 | for v in list do | |
| 1177 | copied.add(_copy_variable(v)); | |
| 1178 | od | |
| 1179 | ||
| 1180 | return Trees.Variables.LIST(list.location, copied); | |
| 1181 | si | |
| 1182 | si | |
| 1183 | si |