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 | // The @pragma(...) chain wrapping the user's `init(..)`, if | |
| 276 | // any - outermost first, reattached around the synthesised | |
| 277 | // primary init below so every pragma written against the | |
| 278 | // user's declaration (there can be more than one stacked) | |
| 279 | // still reaches wherever that constructor's diagnostics are | |
| 280 | // anchored, rather than being discarded along with the node | |
| 281 | // it wrapped. | |
| 282 | let user_primary_init_pragmas: Collections.LIST[Trees.Definitions.PRAGMA]? mut = null | |
| 283 | let secondary_inits = Collections.LIST[Trees.Definitions.FUNCTION]() | |
| 284 | let secondary_init_pragmas = Collections.MAP[Trees.Definitions.FUNCTION, Collections.LIST[Trees.Definitions.PRAGMA]]() | |
| 285 | let kept = Collections.LIST[Trees.Definitions.Definition]() | |
| 286 | ||
| 287 | // Auto-gen props ride at the head of the body so they precede | |
| 288 | // user-written members, matching the order of the primary | |
| 289 | // header. | |
| 290 | for synthesised in auto_gen_props do | |
| 291 | captures.add(synthesised) | |
| 292 | kept.add(synthesised) | |
| 293 | od | |
| 294 | ||
| 295 | for d in classy.body do | |
| 296 | if isa Trees.Definitions.SUPER_CALL(d) then | |
| 297 | let sc = cast Trees.Definitions.SUPER_CALL(d) | |
| 298 | ||
| 299 | if super_call? then | |
| 300 | _logger.error(sc.location, "duplicate super(...) declaration") | |
| 301 | else | |
| 302 | super_call = sc | |
| 303 | fi | |
| 304 | elif _has_init_modifier(d) then | |
| 305 | _logger.error( | |
| 306 | d.location, | |
| 307 | "init modifier is only valid on a primary constructor parameter" | |
| 308 | ) | |
| 309 | kept.add(d) | |
| 310 | elif isa Trees.Definitions.PROPERTY(d) /\ chosen_captures.contains(cast Trees.Definitions.PROPERTY(d)) then | |
| 311 | let capture = cast Trees.Definitions.PROPERTY(d) | |
| 312 | captures.add(capture) | |
| 313 | kept.add(d) | |
| 314 | elif _is_unmatched_capture_shorthand(d) then | |
| 315 | // Field-shaped property with INFER type but no | |
| 316 | // matching primary parameter (or the matching one | |
| 317 | // was already captured by `_foo`, leaving the bare | |
| 318 | // `foo;` shorthand stranded with no type to fall | |
| 319 | // back to). Either way the field can't reach | |
| 320 | // downstream phases — flag it and drop it. | |
| 321 | let p = cast Trees.Definitions.PROPERTY?(d)! | |
| 322 | let stripped = _strip_underscore(p.name!.name) | |
| 323 | ||
| 324 | if already_captured_param_names.contains(stripped) then | |
| 325 | _logger.error( | |
| 326 | p.location, | |
| 327 | "primary parameter {stripped} is already captured by _{stripped}" | |
| 328 | ) | |
| 329 | else | |
| 330 | _logger.error( | |
| 331 | p.location, | |
| 332 | "no primary parameter named {stripped} to capture" | |
| 333 | ) | |
| 334 | fi | |
| 335 | elif _is_init_function(d.without_pragmas) then | |
| 336 | let f = cast Trees.Definitions.FUNCTION?(d.without_pragmas)! | |
| 337 | let d_pragmas = _collect_pragmas(d) | |
| 338 | let has_splice = _arg_list_has_splice(f.arguments) | |
| 339 | ||
| 340 | if has_splice then | |
| 341 | let splice_count = _count_splices(f.arguments) | |
| 342 | ||
| 343 | if splice_count > 1 then | |
| 344 | _logger.error(f.location, "init parameter list contains more than one ..") | |
| 345 | fi | |
| 346 | ||
| 347 | if _is_primary_init(f) then | |
| 348 | if user_primary_init? then | |
| 349 | _logger.error(f.location, "duplicate primary constructor init body") | |
| 350 | else | |
| 351 | user_primary_init = f | |
| 352 | user_primary_init_pragmas = d_pragmas | |
| 353 | fi | |
| 354 | else | |
| 355 | secondary_inits.add(f) | |
| 356 | ||
| 357 | if d_pragmas.count > 0 then | |
| 358 | secondary_init_pragmas[f] = d_pragmas | |
| 359 | fi | |
| 360 | fi | |
| 361 | else | |
| 362 | kept.add(d) | |
| 363 | fi | |
| 364 | else | |
| 365 | kept.add(d) | |
| 366 | fi | |
| 367 | od | |
| 368 | ||
| 369 | // Fill in INFER type expressions from the chosen primary | |
| 370 | // parameter. Captures with an explicit type (e.g. | |
| 371 | // `x: int public;` to widen visibility) keep their type. | |
| 372 | for capture in captures do | |
| 373 | let matching = param_for_capture[capture] | |
| 374 | ||
| 375 | if capture.type_expression.is_inferred then | |
| 376 | capture.set_type_expression(matching.type_expression.copy()) | |
| 377 | fi | |
| 378 | od | |
| 379 | ||
| 380 | // Synthesise the per-capture assignment statements. | |
| 381 | let auto_statements = Collections.LIST[Trees.Statements.Statement]() | |
| 382 | ||
| 383 | if super_call? then | |
| 384 | auto_statements.add(_make_super_init_call(super_call)) | |
| 385 | fi | |
| 386 | ||
| 387 | for capture in captures do | |
| 388 | auto_statements.add(_make_capture_assignment(capture, param_for_capture[capture])) | |
| 389 | od | |
| 390 | ||
| 391 | // If the user wrote `init(..) is body si`, splice their | |
| 392 | // statements in after the auto-generated assignments. | |
| 393 | if user_primary_init? /\ user_primary_init.body? /\ isa Trees.Bodies.BLOCK(user_primary_init.body) then | |
| 394 | let user_block = cast Trees.Bodies.BLOCK(user_primary_init.body) | |
| 395 | ||
| 396 | for s in user_block.statements do | |
| 397 | auto_statements.add(s) | |
| 398 | od | |
| 399 | fi | |
| 400 | ||
| 401 | // The BLOCK's own location is internal: the body has no | |
| 402 | // source text the user wrote, and giving it the class span | |
| 403 | // would make the incremental body re-walk treat every | |
| 404 | // interface symbol on the class header (the class itself, | |
| 405 | // its auto-properties) as `inside` this body and drop them | |
| 406 | // from the symbol-definition map after an EDIT. | |
| 407 | let primary_init_body = | |
| 408 | Trees.Bodies.BLOCK( | |
| 409 | LOCATION.internal, | |
| 410 | Trees.Statements.LIST(LOCATION.internal, auto_statements) | |
| 411 | ) | |
| 412 | ||
| 413 | // Deep-copy the primary parameters into a fresh argument | |
| 414 | // list for the synthesised init — keeps the original list | |
| 415 | // available for the secondary-init splice expansion below | |
| 416 | // without aliasing the same VARIABLE instances across two | |
| 417 | // FUNCTION nodes. | |
| 418 | let primary_init_args = _copy_variable_list(primary_params) | |
| 419 | ||
| 420 | // Anchor the synthesised init's overall span to the class | |
| 421 | // header (class name through primary parameter list). A | |
| 422 | // user-written `init(...)` at the same signature picks up | |
| 423 | // the duplicate-method diagnostic against this range; the | |
| 424 | // synthesised init also surfaces in the VSCE outline at | |
| 425 | // the class-header position. | |
| 426 | // | |
| 427 | // The name identifier is anchored separately: when the | |
| 428 | // user wrote `init(..) is ... si`, point it at the user's | |
| 429 | // own `init` source location so hover, goto-definition, | |
| 430 | // and the semantic-tokens classifier resolve to this | |
| 431 | // symbol at the user-typed keyword. Without that, the | |
| 432 | // user's `init` has no recorded symbol use and falls | |
| 433 | // back to TextMate colouring with no hover. For the | |
| 434 | // auto-generated case (no user `init(..)`) the name stays | |
| 435 | // at the class header. | |
| 436 | let header_location = classy.name.location :: primary_params.location | |
| 437 | ||
| 438 | let primary_init_name = | |
| 439 | if user_primary_init? /\ user_primary_init.name? then | |
| 440 | user_primary_init.name.copy() | |
| 441 | else | |
| 442 | Trees.Identifiers.Identifier(header_location, "init") | |
| 443 | fi | |
| 444 | ||
| 445 | let primary_init = | |
| 446 | Trees.Definitions.FUNCTION( | |
| 447 | header_location, | |
| 448 | primary_init_name, | |
| 449 | Trees.TypeExpressions.LIST(LOCATION.internal, Collections.LIST[Trees.TypeExpressions.TypeExpression](0)), | |
| 450 | primary_init_args, | |
| 451 | Trees.TypeExpressions.INFER(class_location), | |
| 452 | Trees.Modifiers.LIST(class_location, null, null), | |
| 453 | primary_init_body | |
| 454 | ) | |
| 455 | ||
| 456 | primary_init.is_primary_constructor = true | |
| 457 | ||
| 458 | if user_primary_init_pragmas? then | |
| 459 | kept.add(_rewrap_pragmas(user_primary_init_pragmas, primary_init)) | |
| 460 | else | |
| 461 | kept.add(primary_init) | |
| 462 | fi | |
| 463 | ||
| 464 | // Re-attach each secondary init with its splice expanded | |
| 465 | // and an implicit chain to the primary init prepended. | |
| 466 | for f in secondary_inits do | |
| 467 | _expand_secondary_init(f, primary_params) | |
| 468 | ||
| 469 | let f_pragmas: Collections.LIST[Trees.Definitions.PRAGMA] mut | |
| 470 | ||
| 471 | if secondary_init_pragmas.try_get_value(f, f_pragmas ref) then | |
| 472 | kept.add(_rewrap_pragmas(f_pragmas, f)) | |
| 473 | else | |
| 474 | kept.add(f) | |
| 475 | fi | |
| 476 | od | |
| 477 | ||
| 478 | // Auto-deconstruct synthesis. When the user has not written a | |
| 479 | // `deconstruct(...)` method and has not exposed any | |
| 480 | // conventionally-named positional members (`0`, `1`, ...), | |
| 481 | // synthesise a `deconstruct` exposing every public-readable | |
| 482 | // capture in primary-header order. The resolver's precedence | |
| 483 | // (tuple > deconstruct > positional > by-name) means a | |
| 484 | // user-supplied `0`/`1`/... continues to be the explicit opt-in | |
| 485 | // to positional access — co-existing with the synthesised | |
| 486 | // method would make it unreachable. | |
| 487 | _maybe_synthesise_deconstruct( | |
| 488 | classy, | |
| 489 | class_location, | |
| 490 | primary_params, | |
| 491 | param_for_capture, | |
| 492 | already_captured_param_names, | |
| 493 | kept | |
| 494 | ) | |
| 495 | ||
| 496 | // Replace the body's contents with the curated list. | |
| 497 | classy.body.clear_definitions() | |
| 498 | ||
| 499 | for d in kept do | |
| 500 | classy.body.add(d) | |
| 501 | od | |
| 502 | ||
| 503 | classy.set_primary_params(null) | |
| 504 | si | |
| 505 | ||
| 506 | _find_chosen_capture( | |
| 507 | param_name: string, | |
| 508 | field_props_by_name: Collections.MAP[string, Trees.Definitions.PROPERTY], | |
| 509 | already_chosen: Collections.SET[Trees.Definitions.PROPERTY] | |
| 510 | ) -> Trees.Definitions.PROPERTY? is | |
| 511 | let underscore_name = "_{param_name}" | |
| 512 | ||
| 513 | if field_props_by_name.contains_key(underscore_name) then | |
| 514 | let candidate = field_props_by_name[underscore_name] | |
| 515 | ||
| 516 | if !already_chosen.contains(candidate) then | |
| 517 | return candidate | |
| 518 | fi | |
| 519 | fi | |
| 520 | ||
| 521 | if field_props_by_name.contains_key(param_name) then | |
| 522 | let candidate = field_props_by_name[param_name] | |
| 523 | ||
| 524 | if !already_chosen.contains(candidate) then | |
| 525 | return candidate | |
| 526 | fi | |
| 527 | fi | |
| 528 | ||
| 529 | return null | |
| 530 | si | |
| 531 | ||
| 532 | _filter_unusable_primary_params(args: Trees.Variables.LIST) -> Trees.Variables.LIST is | |
| 533 | let kept = Collections.LIST[Trees.Variables.VARIABLE]() | |
| 534 | ||
| 535 | for v in args do | |
| 536 | if v.name? then | |
| 537 | kept.add(v) | |
| 538 | fi | |
| 539 | od | |
| 540 | ||
| 541 | if kept.count == args.count then | |
| 542 | return args | |
| 543 | fi | |
| 544 | ||
| 545 | return Trees.Variables.LIST(args.location, kept) | |
| 546 | si | |
| 547 | ||
| 548 | si | |
| 549 | si |