Check Array(T, N: Comptime(Int)) sizes #67
Labels
No labels
bug
design
epic
risk
user-story
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
jacksonopp/twill#67
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Follow-up to #66 (and relates to #55/#56).
#66 taught the parser/AST that a generic type argument can be either a type (
GenericArg::Type) or a bare comptime value (GenericArg::Value), specifically soArray(Int, 3)parses — the array's size sitting in type-argument position alongside its element type. That PR is parser/AST-level only; no checker semantics were built.This issue is the checker-side follow-up, once array literals (#56) are underway:
— done in #71/#72: no dedicatedTyKind::Arrayneeds a size field (or equivalent), soArray(Int, 3)andArray(Int, 5)are genuinely different typesTyKind::Array; sizes live inTyKind::Instantiated { args: [.., InstantiationArg::Value(_)] }, andcheck_array_literal_different_sizes_are_distinct_tyscovers it.The checker needs to understand— done in #71 (Comptime(Int)-kinded generic parameters generally — resolving aGenericArg::Valueagainst that parameter, and rejecting aGenericArg::Type(or vice versa) where the kind doesn't match.GenericParamKindmismatch diagnostics).— both done.[3]Int[1, 2, 3]-style array literals need their declared size checked against the literal's actual element count and, once size is type-level, against whateverArray(Int, N)the literal is being assigned/passed into.ArraySizeMismatch/ArrayTypeMismatch/InvalidArraySizecover the literal-vs-itself case (check_array_size,array_members_contains_mismatch). The assigned/passed-into case is done via a newexpected: Option<Ty<'db>>threaded throughcheck_let→check_expr→check_array_literal: a bare[1, 2, 3]now adopts a sizedArray(Int, N)target instead of always inferringList(Int), checking both element count and element type against it. Covered bycheck_let_sized_array_literal_matches_same_size_array_annotation,check_let_sized_array_literal_against_different_size_annotation_reports_mismatch,check_let_bare_array_literal_matches_same_size_array_annotation, andcheck_let_bare_array_literal_against_mismatched_size_annotation_reports_mismatch.Decide whether— literals only, for now. This isn't a fresh decision so much as the ceiling already set by #66/#71:Comptime(Int)-kinded params can be arbitrary comptime-evaluated expressions (Array(Int, computeSize())) or literals only, for a first pass.GenericArg::Valueholds aSpanned<Literal>andInstantiationArg::Valueholds aLiteral, not an arbitraryExpr;check_array_sizeexplicitly skips non-literal sizes sinceScopeonly tracks types, not values. Supporting arbitrary comptime expressions is real, separate work — split out to a new issue.Closing this out — all four bullets are resolved (the last one by scoping it out rather than building it). Arbitrary comptime-evaluated sizes are tracked in a new issue.
Repointing the Comptime(Int)/generic-instantiation dependency: #69 got scoped down to just List(T) for its first pass, with Array/Map (and the Comptime(_) value-slot machinery this issue needs) split out to #71. This issue's blocked-on dependency is #71, not #69.
Confirmed via TDD while starting #72:
check_letnever threads alet's annotation down as an expected type into its value's own checking — it resolves the annotation and the value independently (check_type_expr/check_expr), then just compares the twoTys for equality. That means a bare literal ([1, 2, 3], no[N]T[...]/[]T[...]self-annotation) always infers itself asList(T)viacheck_array_literal's(None, None)branch, regardless of any surrounding annotation.Tested all three container kinds with
check_let:let bar: List(Int) = [1, 2, 3]— passes (bare literal's ownList(Int)default happens to match the annotation).let baz: Map(Int, Int) = [1: 2, 3: 4, 5: 6]— passes, same reason.let foo: Array(Int, 3) = [1, 2, 3]— fails, reports a falseValueAnnotationTypeMismatch(expectedArray(Int, 3), foundList(Int)) — because the bare RHS never gets a chance to be checked againstArray(Int, 3), only independently of it.So
List/Mappassing isn't evidence that expected-type propagation exists — it's coincidence, since their bare-literal default happens to equal the annotated type.Arrayis just the first case where the two diverge and the gap becomes visible. Worth folding into this issue's scope alongside the declared-size-vs-literal-count check already listed here:check_array_literal's bare branch (and reallycheck_let/check_exprgenerally) needs some notion of an expected type propagated in from the annotation, not just independent bottom-up inference compared after the fact.