Replace :! binding syntax with phase keywords and contextual defaults (#7479)

Implement the toolchain side of proposal #7254, removing the `:!`
binding
syntax for generic and template parameters in favor of the keywords
`generic`,
`template`, and `runtime` plus contextual defaults for phase.

For valid programs this is semantics-preserving: each binding resolves
to the
same phase, and produces the same SemIR, as it did under `:!`/`:`. The
parser
derives a binding's phase from its syntactic context plus any explicit
phase
keyword; new diagnostics and error recovery for misused keywords are
described
below.

Implementation details for each component:

- Lexer: remove the `:!` (`ColonExclaim`) token, move its virtual
parse-node
  budget onto `:`, and add the `generic` and `runtime` keywords.
- Parser: thread a `BindingContext` (`ExplicitParam`, `DeducedParam`, or
`CompileTimeEntityParam`) from declaration introducers down through
parameter
lists to each binding pattern, using a one-token lookahead to
distinguish a
name-qualifier parameter list from a declaration's own final list.
Parameters
of a compile-time entity (`class`, `interface`, `constraint`, `choice`,
`alias`, `export`, `namespace`) and deduced `[]` parameters default to
checked
generic; explicit function parameters and local bindings default to
runtime.
`HandleBindingPattern` resolves the phase from that context plus the
keyword: a
`generic` keyword needs no node of its own (the phase is carried by the
  binding's node kind), while a `runtime` keyword is preserved as a
`RuntimeBindingName` node so `check` can name it in a diagnostic. A
phase
keyword that is merely redundant with the contextual default is
diagnosed
  here, without invalidating the parse tree.
- Check: a phase keyword that is invalid for its context (for example
`runtime`
on a checked-generic parameter) is diagnosed here, and recovers by
building an
error binding that still introduces the name so that later uses of it do
not
  produce cascading errors.

The removed `:!` syntax is now rejected as an ordinary parse error.

The `form`/`:?`/`->?` ("extended types") portion of proposal #7254 is
left for a
separate change.

Assisted-by: Claude Code
This commit is contained in:
Chandler Carruth
2026-07-11 01:22:44 +00:00
committed by GitHub
parent bf106c3b4b
commit 8be274cf60
482 changed files with 16917 additions and 16226 deletions
+33 -14
View File
@@ -52,21 +52,21 @@ static auto GetLeafBindingPatternInstKind(Parse::NodeKind node_kind,
}
// Returns true if a parameter is valid in the given `introducer_kind`.
static auto IsValidParamForIntroducer(Context& context, Parse::NodeId node_id,
// `is_deduced` is whether this is a deduced (`[...]`) parameter.
static auto IsValidParamForIntroducer(Context& context, SemIR::LocId loc_id,
SemIR::NameId name_id,
Lex::TokenKind introducer_kind,
bool is_generic) -> bool {
bool is_generic, bool is_deduced,
bool is_var) -> bool {
switch (introducer_kind) {
case Lex::TokenKind::Fn: {
// `self` in the implicit parameter list is diagnosed separately (see
// `SelfInImplicitParamList`), so skip it here to avoid a redundant
// diagnostic.
if (context.full_pattern_stack().CurrentKind() ==
FullPatternStack::Kind::ImplicitParamList &&
!(is_generic || name_id == SemIR::NameId::SelfValue)) {
if (is_deduced && !(is_generic || name_id == SemIR::NameId::SelfValue)) {
CARBON_DIAGNOSTIC(ImplictParamMustBeConstant, Error,
"implicit parameters of functions must be constant");
context.emitter().Emit(node_id, ImplictParamMustBeConstant);
context.emitter().Emit(loc_id, ImplictParamMustBeConstant);
return false;
}
// Parameters can have incomplete types in a function declaration, but not
@@ -80,8 +80,7 @@ static auto IsValidParamForIntroducer(Context& context, Parse::NodeId node_id,
// choice type itself.
// Implicit param lists are prevented during parse.
CARBON_CHECK(context.full_pattern_stack().CurrentKind() !=
FullPatternStack::Kind::ImplicitParamList,
CARBON_CHECK(!is_deduced,
"choice alternative with implicit parameters");
// Don't fall through to the `Class` logic for choice alternatives.
return true;
@@ -93,13 +92,20 @@ static auto IsValidParamForIntroducer(Context& context, Parse::NodeId node_id,
if (name_id == SemIR::NameId::SelfValue) {
CARBON_DIAGNOSTIC(SelfParameterNotAllowed, Error,
"`self` parameter only allowed on functions");
context.emitter().Emit(node_id, SelfParameterNotAllowed);
context.emitter().Emit(loc_id, SelfParameterNotAllowed);
return false;
}
if (!is_generic) {
CARBON_DIAGNOSTIC(GenericParamMustBeConstant, Error,
"parameters of generic types must be constant");
context.emitter().Emit(node_id, GenericParamMustBeConstant);
auto builder =
context.emitter().Build(loc_id, GenericParamMustBeConstant);
if (is_var) {
CARBON_DIAGNOSTIC(VarParamIsRuntime, Note,
"`var` parameters are runtime");
builder.Note(loc_id, VarParamIsRuntime);
}
builder.Emit();
return false;
}
return true;
@@ -111,7 +117,7 @@ static auto IsValidParamForIntroducer(Context& context, Parse::NodeId node_id,
namespace {
// Information about the expression in the type position of a binding pattern,
// i.e. the position following the `:`/`:?`/`:!` separator. Note that this
// i.e. the position following the `:` or `:?` separator. Note that this
// expression may be interpreted as a type or a form, depending on the binding
// kind.
struct BindingPatternTypeInfo {
@@ -197,6 +203,10 @@ static auto HandleAnyBindingPattern(
// A non-generic template binding is diagnosed by the parser.
is_template &= is_generic;
// The name in a runtime binding may be wrapped in `runtime`; discard it.
context.node_stack()
.PopAndDiscardSoloNodeIdIf<Parse::NodeKind::RuntimeBindingName>();
// The name in a runtime binding may be wrapped in `ref`.
bool is_ref =
context.node_stack()
@@ -290,8 +300,8 @@ static auto HandleAnyBindingPattern(
// TODO: We should re-evaluate the contents of the eval block in a
// synthesized specific to form these values, in order to propagate the
// values.
return context.TODO(node_id,
"local `let :!` bindings are currently unsupported");
return context.TODO(
node_id, "local generic `let` bindings are currently unsupported");
}
// Allocate an instruction of the appropriate kind, linked to the name for
@@ -299,8 +309,11 @@ static auto HandleAnyBindingPattern(
switch (context.full_pattern_stack().CurrentKind()) {
case FullPatternStack::Kind::ImplicitParamList:
case FullPatternStack::Kind::ExplicitParamList: {
bool is_deduced = context.full_pattern_stack().CurrentKind() ==
FullPatternStack::Kind::ImplicitParamList;
bool is_var = node_kind == Parse::NodeKind::VarBindingPattern;
if (!IsValidParamForIntroducer(context, node_id, name_id, introducer.kind,
is_generic)) {
is_generic, is_deduced, is_var)) {
if (name_id != SemIR::NameId::Underscore) {
AddNameToLookup(context, name_id, SemIR::ErrorInst::InstId);
}
@@ -557,6 +570,12 @@ auto HandleParseNode(Context& context, Parse::RefBindingNameId node_id)
return true;
}
auto HandleParseNode(Context& context, Parse::RuntimeBindingNameId node_id)
-> bool {
context.node_stack().Push(node_id);
return true;
}
auto HandleParseNode(Context& context, Parse::TemplateBindingNameId node_id)
-> bool {
context.node_stack().Push(node_id);