Conversation
Co-authored-by: Sijawusz Pur Rahnama <sija@sija.pl>
Co-authored-by: Sijawusz Pur Rahnama <sija@sija.pl>
Co-authored-by: Sijawusz Pur Rahnama <sija@sija.pl>
Co-authored-by: Sijawusz Pur Rahnama <sija@sija.pl>
Sija
left a comment
There was a problem hiding this comment.
I believe the points raised in #2803 (comment) and #2803 (comment) should be resolved and covered as well.
|
Hey @stakach, thanks for this contribution and many others in the past few days. Seems you're on a streak! 💪 Do you have a special motivation for picking up this specific feature? Also for the other PRs. Some of these discussions have been going and (unfortunately) stagnating for a while. I suppose this is certainly one of the most asked-for features, though. The main obstacle why this feature (and other similar ones) is not yet available is usually not the missing implementation, but lack of a proper discussion and specification, exploring sytanx and semantics options as well as effects on the overall language. Any substantial change must be presented and documented in form of an RFC. So currently, without an RFC this PR (and others) are not mergeable. The point is, if this feature (and others) are to land in the language, contributions to the RFC process are perhaps more important than the actual implementations. |
|
There is an RFC pending on this feature: crystal-lang/rfcs#28 |
Summary
Resolves #2803.
aliasnow accepts type parameters, so platform-, container-, and option-style aliases can be defined parametrically instead of being copy-pasted:Generic aliases work everywhere a regular type can appear: type declarations, restrictions, generic instantiations, and — importantly — inside
forall Trestrictions.What's preserved
The existing recursive-alias support is untouched. The classic JSON-style recursive alias still works exactly as before:
A regression test for
Alias = Int32 | Foo(Alias)is in the new spec block.What's new
Bar(Int32)as a metaclass expressionT = Int32: Bar(Int32)as a restriction / type declarationdef f(x : Bar(T)) forall TTfrom the argumentPair(Int32)for a 2-param alias)alias Foo(*T) = ...)Implementation
Alias): addstype_vars : Array(String)?.parse_aliascallsparse_type_varsafter the name. Splat params raise.AliasType: holdstype_varsand ageneric_instancescache.process_valueearly-returns for generic aliases (the body is unresolvable without arguments);instantiate(type_args)buildsfree_varsfrom the type parameters, runs the body throughlookup_type, and caches the result per argument tuple.type_lookup.cr#lookup(Generic)) and MainVisitor#visit(Generic): when aGenericnode's name resolves to anAliasTypewithtype_vars, validate arity and callinstantiate.MainVisitorusescheck_type_in_type_argsso the resulting node carries the instance type inside restrictions and the metaclass outside.restrictions.cr): a newexpand_generic_alias_restrictionhelper clones the alias body and substitutes eachPath("T")with the matching argument AST node, then re-entersrestrict. Wired into bothType#restrict(Generic)andGenericInstanceType#restrict(Generic). This is what makesforall Twork — the matcher sees the expanded restriction and unifies as it always would.recursive_struct_checker: skips generic aliases (they don't have a single resolvedaliased_type).to_s/formatter: emitFoo(T, U)when type params are present.Test plan
alias Foo(T) = Bar(T),alias Maybe(T) = T | Nil,alias Pair(K, V) = Tuple(K, V)alias_spec.cr:Maybe(Int32)→(Int32 | Nil).classStringKeyed(Int32)→TwoTypes(String, Int32)forall Trestriction (Boxed(T) = T,unbox(1)resolves T = Int32)KV(K, V) = Pair(K, V))Alias = Int32 | Foo(Alias))Out of scope
alias Foo(*T) = ...) — rejected at parse, semantics unclear