Three fixes to the parametric-polymorphism checker.
src/check_expr.cpp
- find_or_generate_polymorphic_procedure: close a check-then-act race in the
generated-procedure cache. Both lookups ran under a shared lock that was
released before the insert, so two threads instantiating the same
specialization could both miss and each publish a distinct entity. Re-check
under the exclusive lock, now held across construction and the array_add so
find-then-publish is atomic. Factor the reuse path into
reuse_gen_polymorphic_procedure.
- is_polymorphic_type_assignable: require BOTH components to match for maps and
enumerated arrays (|| -> &&). The old || accepted e.g. map[int]$V against
map[string]f32 (binding $V and deferring the key mismatch to a confusing
assignment error); it now fails cleanly at inference. Both sides are still
evaluated, so modify_type side effects are unchanged.
src/check_type.cpp, src/check_expr.cpp
- check_struct_type: release the originating record's gen_types mutex right after
the instantiation is published, before checking its fields. Holding it across
field-checking (which instantiates other records) allowed a cross-record ABBA
deadlock between mutually-recursive generic structs instantiated concurrently;
concurrent finders now synchronize via the existing fields_wait_signal.
Canonicalize the instantiation name before publishing
(set_polymorphic_record_instantiation_name) so the early release cannot expose
a torn Named.name. Unions are left unchanged: they lack a variants-ready
signal, so early release is not yet safe for them.
`check_scope_decls` iterates a scope's entity map while
`check_entity_decl` can insert into that same scope: a procedure alias
(`f :: e`) goes through `override_entity_in_scope`, which calls
`scope_map_insert`. The map grows at 75% load (12 of 16 slots, then 24
of 32), and `scope_map_insert` grows before it probes for the key, so
even an alias that only replaces an existing value rehashes and
reallocates the table in the middle of the iteration.
`ScopeMapIterator` re-read its map on every step while `end` had
captured the capacity at loop start, so the walk continued over the new
table under the old sentinel: it ran past the end of the reallocated
table and dereferenced whatever followed it as an `Entity *` - a nullptr
dereference for a procedure scope holding exactly 12 or 24 entities - or
it stopped at the stale end index and silently skipped every declaration
that the rehash had moved behind it, which left unused declarations
without any diagnostic at all.
The iterator now snapshots the table pointer and capacity when it is
constructed, so iteration is invariant under any insert or grow and
cannot leave the table it began on. A scope that does not grow behaves
exactly as before: the snapshot points at the same table.
Refs #7598
Parse `[...]` as `term { (+|-) term }` where each term is `operand [ (*|<<|>>) operand ]`.
This allows for constant-only terms to be folded in-place, especially for things like displacement.
e.g. `[p + 8*32]` means `[p + 256]`.
Term order is also free: `[8 + p]` == `[p + 8]`
string -> ""
bool -> false
bit_set -> {}
anything with a nil -> nil
everything else -> {}
This adds a hint for structs, arrays, matrices, bit_fields, `#no_nil` unions, `rawptr`, `typeid`, the four string types and `bool`. A `bit_set` now suggests `{}` rather than `nil`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`convert_untyped_error` sets `operand->mode = Addressing_Invalid` as its last
statement. Every call site assigns it on the line just before the call too,
which is dead except for one side effect: it suppresses the `Did you want
'nil'?` hint, gated on `operand->mode == Addressing_Constant`.
PR 7403 removed the assignment at the union call site. This removes the
remaining 10 in `convert_to_typed`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The hack made `0` legal against `os.Error` by blanking the constant's
`ExactValue` via `update_untyped_expr_value`, which is a silent no-op when
the expression has not been registered as an untyped expression yet. Since
#7087, `check_expr_base` calls `convert_to_typed` before `add_untyped` runs,
so the blanking is lost and the backend receives a constant typed `os.Error`
still holding integer `0` with no variant type, tripping
`GB_ASSERT_MSG(value_type != nullptr)` in `lb_const_value`.
Also drop the redundant `operand->mode = Addressing_Invalid;` before the
union `convert_untyped_error` call, which revives the `Did you want 'nil'?`
hint, and guard that hint on `type_has_nil` and an Integer/Float zero.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Note that with this commit, casting through
multiply-nested unions is forbidden, e.g.
```
U :: union {int, V}
V :: union {bool}
x: U = true
```
does not compile.
(Previously the compiler would simply crash)
This is to avoid situations where adding variants
can lead to unexpected changes in the value.
For example if `U` is changed to have a `bool`
variant of its own:
```
U :: union {int, bool, V}
```
Then `x: U = true` would equal
`U(true)` instead of `U(V(true))`.
Single-variant unions are exempt, primarily to
improve the ergonomics of `Maybe` in cases like:
```
x: Maybe(union{int, bool}) = 1
```
Fix#6100Fix#6699Fix#6895Fix#6896Fix#6897Fix#7036Fix#7083Fix#7091