Appearance
| 1 | namespace Semantic is | |
| 2 | use System.Text.StringBuilder; | |
| 3 | ||
| 4 | use Semantic.Types.Type; | |
| 5 | ||
| 6 | // A constraint recorded against an INFERRED_VARIABLE_TYPE | |
| 7 | // placeholder's origin during the body-retry walk. Each | |
| 8 | // constraint captures one operation site that consumed a | |
| 9 | // placeholder-typed value and that the eventual resolved type | |
| 10 | // must satisfy. The constraint-aware LUB uses these to filter | |
| 11 | // candidate types: a candidate is only a valid resolution for | |
| 12 | // the placeholder if every accumulated constraint discharges | |
| 13 | // against it. | |
| 14 | // | |
| 15 | // This sits alongside `Symbols.Variable._lub_map` (which holds | |
| 16 | // type-bound constraints). The two together — types we know | |
| 17 | // the placeholder must be *compatible with*, and operations | |
| 18 | // the resolved type must *support* — give the solver the full | |
| 19 | // picture of how the placeholder is used across the function | |
| 20 | // body without needing a separate dataflow pass. | |
| 21 | // | |
| 22 | // Subclasses MUST override `equals` and `get_hash_code` so | |
| 23 | // that two constraints emitted from semantically equivalent | |
| 24 | // operation sites collapse into a single set entry. The | |
| 25 | // Variable-side accumulator stores constraints in a | |
| 26 | // `Collections.SET` which uses .NET's `Object.Equals` / | |
| 27 | // `Object.GetHashCode` contract; ghūl exposes these as | |
| 28 | // `equals(other: object?) -> bool` and `get_hash_code() -> int`. | |
| 29 | class Constraint abstract is | |
| 30 | init() is | |
| 31 | super.init(); | |
| 32 | si | |
| 33 | ||
| 34 | // Returns true iff `candidate` carries the operation this | |
| 35 | // constraint requires. Conservative by contract: | |
| 36 | // subclasses MUST return false rather than risk a false | |
| 37 | // positive, because a false positive lets the LUB pick a | |
| 38 | // candidate that subsequently emits broken IL or fails | |
| 39 | // member lookup in generated code. | |
| 40 | try_discharge(candidate: Type?) -> bool => false; | |
| 41 | ||
| 42 | // Base class disagrees with everything. Subclasses | |
| 43 | // override and return true only for same-subclass + | |
| 44 | // same-field matches. `equals` is the operator the SET | |
| 45 | // accumulator uses for dedup; `get_hash_code` must agree. | |
| 46 | equals(other: object?) -> bool => false; | |
| 47 | ||
| 48 | get_hash_code() -> int => 0; | |
| 49 | ||
| 50 | to_string() -> string => "<constraint>"; | |
| 51 | ||
| 52 | // Returns the closed-root Classy referenced by `candidate`, | |
| 53 | // or null if candidate isn't a NAMED wrapping a closed root | |
| 54 | // (possibly through a Symbols.GENERIC). Closed-root shapes | |
| 55 | // are union or ghūl-declared closed class. Used by subclasses | |
| 56 | // whose discharge accepts a wide root when any subtype | |
| 57 | // satisfies the constraint — see | |
| 58 | // _discharge_against_closed_subtypes. | |
| 59 | _try_get_closed_root_classy(candidate: Types.Type) -> Symbols.Classy? is | |
| 60 | let named = cast Types.NAMED?(candidate); | |
| 61 | if !named? then | |
| 62 | return null; | |
| 63 | fi | |
| 64 | ||
| 65 | let classy = cast Symbols.Classy?(named.symbol.unspecialized_symbol); | |
| 66 | if !classy? \/ !classy.is_closed_root then | |
| 67 | return null; | |
| 68 | fi | |
| 69 | ||
| 70 | return classy; | |
| 71 | si | |
| 72 | ||
| 73 | // Returns true iff `candidate` is a closed root whose any | |
| 74 | // subtype (union variant or closed-class subclass) satisfies | |
| 75 | // the subclass's `_subtype_matches`. Subtype-specific shape | |
| 76 | // isn't visible on the wide root itself — it's only reachable | |
| 77 | // inside an `if .is_X then` narrow. Constraints recorded on | |
| 78 | // a still-placeholder receiver in a context where narrowing | |
| 79 | // couldn't fire accept the wide root when at least one | |
| 80 | // subtype carries the shape; once the placeholder resolves | |
| 81 | // the subtype becomes reachable at the use site via | |
| 82 | // narrowing. | |
| 83 | _discharge_against_closed_subtypes(candidate: Types.Type) -> bool is | |
| 84 | let root_classy = _try_get_closed_root_classy(candidate); | |
| 85 | if !root_classy? then | |
| 86 | return false; | |
| 87 | fi | |
| 88 | ||
| 89 | for subtype in root_classy.closed_alternatives do | |
| 90 | if _subtype_matches(subtype) then | |
| 91 | return true; | |
| 92 | fi | |
| 93 | od | |
| 94 | ||
| 95 | return false; | |
| 96 | si | |
| 97 | ||
| 98 | // Default = no subtype ever matches. Overridden by | |
| 99 | // subclasses whose discharge accepts wide closed roots via | |
| 100 | // any-subtype logic. | |
| 101 | _subtype_matches(subtype_classy: Symbols.Classy) -> bool => false; | |
| 102 | si | |
| 103 | ||
| 104 | // The placeholder must resolve to a type that has a member | |
| 105 | // named `member_name`. Emitted at MEMBER-expression visit | |
| 106 | // sites when the receiver is an INFERRED_VARIABLE_TYPE. | |
| 107 | // | |
| 108 | // `call_arity = -1` (the default) leaves discharge name-only. | |
| 109 | // When the MEMBER expression is the LHS of a CALL, the | |
| 110 | // CALL-site emits an additional MEMBER_CONSTRAINT carrying the | |
| 111 | // call's arg-count so the resolved member must be callable at | |
| 112 | // that arity — useful when a name collides across types but | |
| 113 | // the arities don't (e.g. `o.add(x)` excludes a type whose | |
| 114 | // only `add` is two-arg). Type-aware refinement is what | |
| 115 | // `CALL_CONSTRAINT` captures separately; this is the cheap | |
| 116 | // structural check. | |
| 117 | class MEMBER_CONSTRAINT: Constraint is | |
| 118 | member_name: string; | |
| 119 | call_arity: int; | |
| 120 | ||
| 121 | init(member_name: string) is | |
| 122 | super.init(); | |
| 123 | self.member_name = member_name; | |
| 124 | self.call_arity = -1; | |
| 125 | si | |
| 126 | ||
| 127 | init(member_name: string, call_arity: int) is | |
| 128 | super.init(); | |
| 129 | self.member_name = member_name; | |
| 130 | self.call_arity = call_arity; | |
| 131 | si | |
| 132 | ||
| 133 | try_discharge(candidate: Type?) -> bool is | |
| 134 | if !candidate? \/ candidate.is_sentinel then | |
| 135 | return false; | |
| 136 | fi | |
| 137 | ||
| 138 | let direct = candidate.find_member(member_name); | |
| 139 | ||
| 140 | if direct? then | |
| 141 | return check_arity(direct); | |
| 142 | fi | |
| 143 | ||
| 144 | return _discharge_against_closed_subtypes(candidate); | |
| 145 | si | |
| 146 | ||
| 147 | _subtype_matches(subtype_classy: Symbols.Classy) -> bool is | |
| 148 | let m = subtype_classy.find_member(member_name); | |
| 149 | return m? /\ check_arity(m); | |
| 150 | si | |
| 151 | ||
| 152 | // call_arity = -1 means "no arity check"; any found member | |
| 153 | // passes. Otherwise we accept only when the symbol resolves | |
| 154 | // to a callable that takes call_arity arguments: a Function | |
| 155 | // with matching arg count, a FUNCTION_GROUP whose | |
| 156 | // overload set contains a matching arity, or any other | |
| 157 | // kind (Property / Field carrying a callable type) — we | |
| 158 | // can't statically verify those without resolving the | |
| 159 | // field's type, so we let them through and trust the | |
| 160 | // overload resolver at the call site to catch a real | |
| 161 | // mismatch. | |
| 162 | check_arity(member: Symbols.Symbol) -> bool is | |
| 163 | if call_arity < 0 then | |
| 164 | return true; | |
| 165 | fi | |
| 166 | ||
| 167 | let function = cast Symbols.Function?(member); | |
| 168 | if function? /\ function.are_arguments_declared then | |
| 169 | return function.arguments.count == call_arity; | |
| 170 | fi | |
| 171 | ||
| 172 | if let group: Symbols.FUNCTION_GROUP = member then | |
| 173 | for f in group.functions do | |
| 174 | if f.are_arguments_declared /\ f.arguments.count == call_arity then | |
| 175 | return true; | |
| 176 | fi | |
| 177 | od | |
| 178 | return false; | |
| 179 | fi | |
| 180 | ||
| 181 | return true; | |
| 182 | si | |
| 183 | ||
| 184 | // Two member constraints collide iff they share a member | |
| 185 | // name AND arity. An arity-aware emission is strictly more | |
| 186 | // restrictive than the name-only version, so we keep both | |
| 187 | // in the set; the candidate must discharge each. | |
| 188 | equals(other: object?) -> bool is | |
| 189 | if !other? \/ !isa MEMBER_CONSTRAINT(other) then | |
| 190 | return false; | |
| 191 | fi | |
| 192 | ||
| 193 | let m = other; | |
| 194 | return member_name == m.member_name /\ call_arity == m.call_arity; | |
| 195 | si | |
| 196 | ||
| 197 | get_hash_code() -> int => | |
| 198 | member_name.get_hash_code() * 31 + call_arity; | |
| 199 | ||
| 200 | to_string() -> string => | |
| 201 | if call_arity < 0 then | |
| 202 | "member {member_name}"; | |
| 203 | else | |
| 204 | "member {member_name}/{call_arity}"; | |
| 205 | fi; | |
| 206 | si | |
| 207 | ||
| 208 | // The placeholder must resolve to a type that is callable | |
| 209 | // with the captured argument types. Emitted at `_visit(call)` | |
| 210 | // when the receiver is itself an INFERRED_VARIABLE_TYPE — | |
| 211 | // i.e., a local lambda or local-bound function is invoked | |
| 212 | // before its type is pinned, including self-applied shapes | |
| 213 | // like `(f, x) => f(f(x))`. | |
| 214 | // | |
| 215 | // Discharge is "is this a Function/Action type whose argument | |
| 216 | // count matches and whose arg-types accept the captured | |
| 217 | // actuals". The shape-side check is strict (heads must be | |
| 218 | // Function/Action) — we don't accept arbitrary types with an | |
| 219 | // `invoke` member. | |
| 220 | // | |
| 221 | // The captured argument types may themselves contain | |
| 222 | // placeholders (the inner `f(x)` above supplies `x`'s | |
| 223 | // placeholder as its single arg). Per-slot check looks through | |
| 224 | // placeholder args via `_resolved_arg_type` and skips slots | |
| 225 | // whose actual is still unresolved — an unresolved placeholder | |
| 226 | // doesn't refute the candidate. On the next retry iteration | |
| 227 | // the placeholder's origin may have settled, the look-through | |
| 228 | // substitutes its resolved type, and the slot compares | |
| 229 | // cleanly. Resolved-into-concrete and concrete-original args | |
| 230 | // are full-compared as before. | |
| 231 | class CALL_CONSTRAINT: Constraint is | |
| 232 | argument_types: Collections.List[Type] public; | |
| 233 | ||
| 234 | init(argument_types: Collections.List[Type]) is | |
| 235 | super.init(); | |
| 236 | self.argument_types = argument_types; | |
| 237 | si | |
| 238 | ||
| 239 | try_discharge(candidate: Type?) -> bool is | |
| 240 | if !candidate? \/ candidate.is_sentinel then | |
| 241 | return false; | |
| 242 | fi | |
| 243 | ||
| 244 | if !candidate.is_function /\ !candidate.is_action then | |
| 245 | return false; | |
| 246 | fi | |
| 247 | ||
| 248 | // Function carries one extra slot for the return type; | |
| 249 | // Action carries arg-slots only. Compare just the | |
| 250 | // argument prefix. | |
| 251 | let candidate_args = | |
| 252 | if isa Types.NAMED(candidate) then | |
| 253 | candidate.arguments | |
| 254 | else | |
| 255 | null | |
| 256 | fi; | |
| 257 | ||
| 258 | if !candidate_args? then | |
| 259 | return false; | |
| 260 | fi | |
| 261 | ||
| 262 | let expected_arg_count = | |
| 263 | if candidate.is_function then | |
| 264 | candidate_args.count - 1 | |
| 265 | else | |
| 266 | candidate_args.count | |
| 267 | fi; | |
| 268 | ||
| 269 | if expected_arg_count != argument_types.count then | |
| 270 | return false; | |
| 271 | fi | |
| 272 | ||
| 273 | // Per-slot check with placeholder look-through: | |
| 274 | // | |
| 275 | // - concrete actual — compare against the | |
| 276 | // candidate's formal; if it doesn't fit, the | |
| 277 | // candidate is wrong → reject. | |
| 278 | // | |
| 279 | // - placeholder actual whose origin has settled — | |
| 280 | // `_resolved_arg_type` substitutes the origin's | |
| 281 | // resolved type and compare. Picks up inner-lambda | |
| 282 | // call args (`f(x)` captures x while x is still a | |
| 283 | // phantom; x later resolves via call-site match propagation, | |
| 284 | // but the captured placeholder instance in this | |
| 285 | // constraint's args still reads as "inferred" | |
| 286 | // without the look-through). | |
| 287 | // | |
| 288 | // - placeholder actual whose origin is still unresolved — | |
| 289 | // skip this slot. The candidate isn't refuted by an | |
| 290 | // unresolved placeholder — once the placeholder's | |
| 291 | // origin settles on a later iter the discharge re- | |
| 292 | // runs and compares cleanly. | |
| 293 | // | |
| 294 | // Formal is resolved through `_resolved_arg_type` too so the | |
| 295 | // candidate's args — themselves potentially placeholder- | |
| 296 | // instances whose origins have settled — read as their | |
| 297 | // concrete type for the per-slot compare. | |
| 298 | for i in 0..argument_types.count do | |
| 299 | let formal = _resolved_arg_type(candidate_args[i]); | |
| 300 | let raw_actual = argument_types[i]; | |
| 301 | let actual = _resolved_arg_type(raw_actual); | |
| 302 | ||
| 303 | if !actual.contains_inferred then | |
| 304 | if cast int(formal.compare(actual)) > cast int(Types.MATCH.CONVERTABLE) then | |
| 305 | return false; | |
| 306 | fi | |
| 307 | fi | |
| 308 | od | |
| 309 | ||
| 310 | return true; | |
| 311 | si | |
| 312 | ||
| 313 | // Follow an INFERRED_VARIABLE_TYPE arg through to its origin | |
| 314 | // symbol's resolved type when settled. Returns the input | |
| 315 | // unchanged for non-placeholder args, or for a placeholder | |
| 316 | // whose origin hasn't resolved yet. | |
| 317 | _resolved_arg_type(a: Type) -> Type is | |
| 318 | if !isa Types.INFERRED_VARIABLE_TYPE(a) then | |
| 319 | return a; | |
| 320 | fi | |
| 321 | ||
| 322 | let placeholder = cast Types.INFERRED_VARIABLE_TYPE(a); | |
| 323 | ||
| 324 | if !placeholder.origin.type? then | |
| 325 | return a; | |
| 326 | fi | |
| 327 | ||
| 328 | let resolved = placeholder.origin.type; | |
| 329 | ||
| 330 | if resolved.is_settled then | |
| 331 | return resolved; | |
| 332 | fi | |
| 333 | ||
| 334 | return a; | |
| 335 | si | |
| 336 | ||
| 337 | equals(other: object?) -> bool is | |
| 338 | if !other? \/ !isa CALL_CONSTRAINT(other) then | |
| 339 | return false; | |
| 340 | fi | |
| 341 | ||
| 342 | let c = other; | |
| 343 | ||
| 344 | if argument_types.count != c.argument_types.count then return false; fi | |
| 345 | ||
| 346 | for i in 0..argument_types.count do | |
| 347 | let a = argument_types[i]; | |
| 348 | let b = c.argument_types[i]; | |
| 349 | ||
| 350 | if !(a.matches(b)) then return false; fi | |
| 351 | od | |
| 352 | ||
| 353 | return true; | |
| 354 | si | |
| 355 | ||
| 356 | // Hash on arity + each arg's hash. Arg `matches` returns true | |
| 357 | // for INFERRED_VARIABLE_TYPE against anything, so two | |
| 358 | // constraints with placeholder args still collide | |
| 359 | // structurally even when their placeholders differ — | |
| 360 | // that matches the de-duplication policy for re-walks of | |
| 361 | // the same call site. | |
| 362 | get_hash_code() -> int is | |
| 363 | let h mut = 17; | |
| 364 | ||
| 365 | h = h * 31 + argument_types.count; | |
| 366 | ||
| 367 | for a in argument_types do | |
| 368 | h = h * 31 + a.get_hash_code(); | |
| 369 | od | |
| 370 | ||
| 371 | return h; | |
| 372 | si | |
| 373 | ||
| 374 | to_string() -> string is | |
| 375 | let buffer = StringBuilder(); | |
| 376 | buffer.append("call("); | |
| 377 | let first mut = true; | |
| 378 | for a in argument_types do | |
| 379 | if !first then buffer.append(", "); fi | |
| 380 | first = false; | |
| 381 | buffer.append(a.to_string()); | |
| 382 | od | |
| 383 | buffer.append(")"); | |
| 384 | return buffer.to_string(); | |
| 385 | si | |
| 386 | si | |
| 387 | ||
| 388 | // The placeholder must resolve to a type that can be | |
| 389 | // destructured into at least `member_count` positions — | |
| 390 | // either a value-tuple of the right arity, or any other type | |
| 391 | // whose `find_destructure_member(0..member_count-1)` all | |
| 392 | // return non-null. Emitted at destructure-binding sites | |
| 393 | // (`let (a, b) = p`) when the RHS type is still an | |
| 394 | // INFERRED_VARIABLE_TYPE so the body-retry loop has a basis | |
| 395 | // to filter candidate types instead of bailing out with | |
| 396 | // "cannot destructure" on a placeholder. | |
| 397 | class DESTRUCTURE_CONSTRAINT: Constraint is | |
| 398 | member_count: int public; | |
| 399 | ||
| 400 | init(member_count: int) is | |
| 401 | super.init(); | |
| 402 | self.member_count = member_count; | |
| 403 | si | |
| 404 | ||
| 405 | try_discharge(candidate: Type?) -> bool is | |
| 406 | if !candidate? \/ candidate.is_sentinel then | |
| 407 | return false; | |
| 408 | fi | |
| 409 | ||
| 410 | if has_destructure_shape(candidate) then | |
| 411 | return true; | |
| 412 | fi | |
| 413 | ||
| 414 | return _discharge_against_closed_subtypes(candidate); | |
| 415 | si | |
| 416 | ||
| 417 | _subtype_matches(subtype_classy: Symbols.Classy) -> bool => | |
| 418 | has_destructure_shape(subtype_classy.type); | |
| 419 | ||
| 420 | // True iff `t` directly satisfies the destructure shape — | |
| 421 | // a value tuple of matching arity, or a type whose | |
| 422 | // `find_destructure_member(0..N-1)` all return non-null. | |
| 423 | // Used both for the direct candidate and for each subtype | |
| 424 | // when the candidate is a closed root. | |
| 425 | has_destructure_shape(t: Type?) -> bool is | |
| 426 | if !t? then | |
| 427 | return false; | |
| 428 | fi | |
| 429 | ||
| 430 | if t.is_value_tuple then | |
| 431 | return t.arguments.count == member_count; | |
| 432 | fi | |
| 433 | ||
| 434 | for i in 0..member_count do | |
| 435 | if !t.find_destructure_member(i)? then | |
| 436 | return false; | |
| 437 | fi | |
| 438 | od | |
| 439 | ||
| 440 | return true; | |
| 441 | si | |
| 442 | ||
| 443 | equals(other: object?) -> bool is | |
| 444 | if !other? \/ !isa DESTRUCTURE_CONSTRAINT(other) then | |
| 445 | return false; | |
| 446 | fi | |
| 447 | ||
| 448 | let d = other; | |
| 449 | return member_count == d.member_count; | |
| 450 | si | |
| 451 | ||
| 452 | get_hash_code() -> int => member_count; | |
| 453 | ||
| 454 | to_string() -> string => "destructure/{member_count}"; | |
| 455 | si | |
| 456 | ||
| 457 | // The placeholder must resolve to a type the for-loop expression | |
| 458 | // path treats as iterable: either it carries `move_next` (the | |
| 459 | // direct iterator shape) or `$get_iterator` (the iterable | |
| 460 | // shape — a property returning an iterator). Emitted at | |
| 461 | // `for x in t` sites when `t`'s type is still an | |
| 462 | // `INFERRED_VARIABLE_TYPE` so the body-retry loop can filter | |
| 463 | // candidate types by iterability instead of letting set_iterator_for | |
| 464 | // fire a hard "not iterable" on a placeholder it couldn't have | |
| 465 | // checked anyway. | |
| 466 | // | |
| 467 | // Carries no payload — every iterable constraint at every | |
| 468 | // for-loop site is structurally identical, so equality / hash | |
| 469 | // are constant and the SET accumulator dedupes to a single | |
| 470 | // entry per placeholder. | |
| 471 | class ITERABLE_CONSTRAINT: Constraint is | |
| 472 | init() is | |
| 473 | super.init(); | |
| 474 | si | |
| 475 | ||
| 476 | try_discharge(candidate: Type?) -> bool is | |
| 477 | if !candidate? \/ candidate.is_sentinel then | |
| 478 | return false; | |
| 479 | fi | |
| 480 | ||
| 481 | if has_iterable_shape(candidate) then | |
| 482 | return true; | |
| 483 | fi | |
| 484 | ||
| 485 | return _discharge_against_closed_subtypes(candidate); | |
| 486 | si | |
| 487 | ||
| 488 | _subtype_matches(subtype_classy: Symbols.Classy) -> bool => | |
| 489 | has_iterable_shape(subtype_classy.type); | |
| 490 | ||
| 491 | has_iterable_shape(t: Type?) -> bool is | |
| 492 | if !t? then | |
| 493 | return false; | |
| 494 | fi | |
| 495 | ||
| 496 | if t.find_member("move_next")? then | |
| 497 | return true; | |
| 498 | fi | |
| 499 | ||
| 500 | if t.find_member("$get_iterator")? then | |
| 501 | return true; | |
| 502 | fi | |
| 503 | ||
| 504 | return false; | |
| 505 | si | |
| 506 | ||
| 507 | equals(other: object?) -> bool => | |
| 508 | other? /\ isa ITERABLE_CONSTRAINT(other); | |
| 509 | ||
| 510 | get_hash_code() -> int => 23; | |
| 511 | ||
| 512 | to_string() -> string => "iterable"; | |
| 513 | si | |
| 514 | ||
| 515 | // The placeholder must resolve to a type that admits indexing | |
| 516 | // by `index_type` — i.e. exposes either `get_Item` / | |
| 517 | // `get_item` (the convention auto-synthesised on classes with | |
| 518 | // an indexer) or an explicit `get_item(index)` member whose | |
| 519 | // first formal accepts `index_type`. Emitted at `t[i]` sites | |
| 520 | // when `t`'s type is still an `INFERRED_VARIABLE_TYPE`. | |
| 521 | // | |
| 522 | // The index_type itself may contain inference placeholders; | |
| 523 | // discharge defers conservatively in that case so the LUB | |
| 524 | // gets to re-check on the next iteration once placeholders | |
| 525 | // resolve. | |
| 526 | class INDEX_CONSTRAINT: Constraint is | |
| 527 | index_type: Type public; | |
| 528 | ||
| 529 | init(index_type: Type) is | |
| 530 | super.init(); | |
| 531 | self.index_type = index_type; | |
| 532 | si | |
| 533 | ||
| 534 | try_discharge(candidate: Type?) -> bool is | |
| 535 | if !candidate? \/ candidate.is_sentinel then | |
| 536 | return false; | |
| 537 | fi | |
| 538 | ||
| 539 | if !index_type.is_settled then | |
| 540 | return false; | |
| 541 | fi | |
| 542 | ||
| 543 | if has_indexable_shape(candidate) then | |
| 544 | return true; | |
| 545 | fi | |
| 546 | ||
| 547 | return _discharge_against_closed_subtypes(candidate); | |
| 548 | si | |
| 549 | ||
| 550 | _subtype_matches(subtype_classy: Symbols.Classy) -> bool => | |
| 551 | has_indexable_shape(subtype_classy.type); | |
| 552 | ||
| 553 | has_indexable_shape(t: Type?) -> bool is | |
| 554 | if !t? then | |
| 555 | return false; | |
| 556 | fi | |
| 557 | ||
| 558 | // `get_Item` is the .NET-imported convention; `get_item` | |
| 559 | // is the ghūl-source convention. | |
| 560 | return is_indexer_match(t, "get_Item") \/ is_indexer_match(t, "get_item"); | |
| 561 | si | |
| 562 | ||
| 563 | // True iff `t` exposes a `member_name` callable whose first | |
| 564 | // formal accepts `index_type`. Function or FUNCTION_GROUP | |
| 565 | // surface; other kinds aren't considered indexers. | |
| 566 | is_indexer_match(t: Type, member_name: string) -> bool is | |
| 567 | let member = t.find_member(member_name); | |
| 568 | if !member? then | |
| 569 | return false; | |
| 570 | fi | |
| 571 | ||
| 572 | if let function: Symbols.Function = member then | |
| 573 | return accepts_index(function); | |
| 574 | fi | |
| 575 | ||
| 576 | if let group: Symbols.FUNCTION_GROUP = member then | |
| 577 | for f in group.functions do | |
| 578 | if accepts_index(f) then | |
| 579 | return true; | |
| 580 | fi | |
| 581 | od | |
| 582 | return false; | |
| 583 | fi | |
| 584 | ||
| 585 | return false; | |
| 586 | si | |
| 587 | ||
| 588 | accepts_index(f: Symbols.Function) -> bool is | |
| 589 | if !f.are_arguments_declared \/ f.arguments.count == 0 then | |
| 590 | return false; | |
| 591 | fi | |
| 592 | ||
| 593 | let formal = f.arguments[0]; | |
| 594 | if formal.is_error then | |
| 595 | return false; | |
| 596 | fi | |
| 597 | ||
| 598 | return formal.is_assignable_from(index_type); | |
| 599 | si | |
| 600 | ||
| 601 | equals(other: object?) -> bool is | |
| 602 | if !other? \/ !isa INDEX_CONSTRAINT(other) then | |
| 603 | return false; | |
| 604 | fi | |
| 605 | ||
| 606 | let i = other; | |
| 607 | return index_type.matches(i.index_type); | |
| 608 | si | |
| 609 | ||
| 610 | get_hash_code() -> int => index_type.get_hash_code() * 31 + 29; | |
| 611 | ||
| 612 | to_string() -> string => "index[{index_type}]"; | |
| 613 | si | |
| 614 | si |