mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 22:02:23 +01:00
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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user