Skip to content

Conversation

@danakj
Copy link
Contributor

@danakj danakj commented Dec 5, 2025

We encode the state of looking that the parent scope is a Class into a type so that we can avoid extra lookups. Then use that in refactoring where diagnostics are generated for an explicit Self in an extend impl declaration.

We add tests that we don't double-diagnose the Self type when it's already an error, and make the behaviour of extend require match that of extend impl as.

This avoids some fragile/complex parse-node lookups (such as context.parse_tree_and_subtrees().ExtractAs<Parse::ImplTypeAs>) by using the parse node at the point where we are handling it instead of much later.

This is part of #6420 which is being split up into a chain of smaller PRs.

Copy link
Contributor

@zygoloid zygoloid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some thoughts but apart from the "existant" -> "existent" spelling fix, these can all be deferred -- I appreciate this is part of a big refactor and it might be easier to handle these comments at the end of that, if they still apply at all.

Comment on lines +127 to +151
// --- fail_extend_require_with_nonexistant_type.carbon
library "[[@TEST_NAME]]";

interface Y {}

interface Z {
// CHECK:STDERR: fail_extend_require_with_nonexistant_type.carbon:[[@LINE+4]]:18: error: name `nonexistant` not found [NameNotFound]
// CHECK:STDERR: extend require nonexistant impls Y;
// CHECK:STDERR: ^~~~~~~~~~~
// CHECK:STDERR:
extend require nonexistant impls Y;
}

// --- fail_extend_require_with_nonexistant_type_pointer.carbon
library "[[@TEST_NAME]]";

interface Y {}

interface Z {
// CHECK:STDERR: fail_extend_require_with_nonexistant_type_pointer.carbon:[[@LINE+4]]:18: error: name `nonexistant` not found [NameNotFound]
// CHECK:STDERR: extend require nonexistant* impls Y;
// CHECK:STDERR: ^~~~~~~~~~~
// CHECK:STDERR:
extend require nonexistant* impls Y;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// --- fail_extend_require_with_nonexistant_type.carbon
library "[[@TEST_NAME]]";
interface Y {}
interface Z {
// CHECK:STDERR: fail_extend_require_with_nonexistant_type.carbon:[[@LINE+4]]:18: error: name `nonexistant` not found [NameNotFound]
// CHECK:STDERR: extend require nonexistant impls Y;
// CHECK:STDERR: ^~~~~~~~~~~
// CHECK:STDERR:
extend require nonexistant impls Y;
}
// --- fail_extend_require_with_nonexistant_type_pointer.carbon
library "[[@TEST_NAME]]";
interface Y {}
interface Z {
// CHECK:STDERR: fail_extend_require_with_nonexistant_type_pointer.carbon:[[@LINE+4]]:18: error: name `nonexistant` not found [NameNotFound]
// CHECK:STDERR: extend require nonexistant* impls Y;
// CHECK:STDERR: ^~~~~~~~~~~
// CHECK:STDERR:
extend require nonexistant* impls Y;
}
// --- fail_extend_require_with_nonexistent_type.carbon
library "[[@TEST_NAME]]";
interface Y {}
interface Z {
// CHECK:STDERR: fail_extend_require_with_nonexistant_type.carbon:[[@LINE+4]]:18: error: name `nonexistent` not found [NameNotFound]
// CHECK:STDERR: extend require nonexistent impls Y;
// CHECK:STDERR: ^~~~~~~~~~~
// CHECK:STDERR:
extend require nonexistent impls Y;
}
// --- fail_extend_require_with_nonexistent_type_pointer.carbon
library "[[@TEST_NAME]]";
interface Y {}
interface Z {
// CHECK:STDERR: fail_extend_require_with_nonexistent_type_pointer.carbon:[[@LINE+4]]:18: error: name `nonexistent` not found [NameNotFound]
// CHECK:STDERR: extend require nonexistent* impls Y;
// CHECK:STDERR: ^~~~~~~~~~~
// CHECK:STDERR:
extend require nonexistent* impls Y;
}

Comment on lines +75 to +77
auto class_scope =
TryAsClassScope(context, context.decl_name_stack().PeekParentScopeId());
if (class_scope) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
auto class_scope =
TryAsClassScope(context, context.decl_name_stack().PeekParentScopeId());
if (class_scope) {
if (auto class_scope =
TryAsClassScope(context, context.decl_name_stack().PeekParentScopeId())) {

Comment on lines +83 to +101
auto diag = context.emitter().Build(extend_node, ExtendImplSelfAs);

if (self_type.type_id == GetImplDefaultSelfType(context, *class_scope)) {
// If the explicit self type is the default, suggest removing it with a
// diagnostic, but continue as if no error occurred since the self-type
// is semantically valid.
CARBON_DIAGNOSTIC(ExtendImplSelfAsDefault, Note,
"remove the explicit `Self` type here");
diag.Note(self_node, ExtendImplSelfAsDefault);
if (self_type.type_id != SemIR::ErrorInst::TypeId) {
diag.Emit();
}
} else if (self_type.type_id != SemIR::ErrorInst::TypeId) {
// Otherwise, the self-type is an error.
diag.Emit();
class_scope->name_scope->set_has_error();
self_type.inst_id = SemIR::ErrorInst::TypeInstId;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should also call name_scope->set_has_error() if there's an error in the self type itself. So maybe:

Suggested change
auto diag = context.emitter().Build(extend_node, ExtendImplSelfAs);
if (self_type.type_id == GetImplDefaultSelfType(context, *class_scope)) {
// If the explicit self type is the default, suggest removing it with a
// diagnostic, but continue as if no error occurred since the self-type
// is semantically valid.
CARBON_DIAGNOSTIC(ExtendImplSelfAsDefault, Note,
"remove the explicit `Self` type here");
diag.Note(self_node, ExtendImplSelfAsDefault);
if (self_type.type_id != SemIR::ErrorInst::TypeId) {
diag.Emit();
}
} else if (self_type.type_id != SemIR::ErrorInst::TypeId) {
// Otherwise, the self-type is an error.
diag.Emit();
class_scope->name_scope->set_has_error();
self_type.inst_id = SemIR::ErrorInst::TypeInstId;
}
}
auto diag =
self_type.type_id == SemIR::ErrorInst::TypeId
? context.emitter().BuildSuppressed()
: context.emitter().Build(extend_node, ExtendImplSelfAs);
if (self_type.type_id == GetImplDefaultSelfType(context, *class_scope)) {
// If the explicit self type is the default, suggest removing it with a
// diagnostic, but continue as if no error occurred since the self-type
// is semantically valid.
CARBON_DIAGNOSTIC(ExtendImplSelfAsDefault, Note,
"remove the explicit `Self` type here");
diag.Note(self_node, ExtendImplSelfAsDefault);
} else {
// Otherwise, the self-type is an error.
class_scope->name_scope->set_has_error();
self_type.inst_id = SemIR::ErrorInst::TypeInstId;
}
}
diag.Emit();

Comment on lines +117 to +119
auto class_scope =
TryAsClassScope(context, context.decl_name_stack().PeekParentScopeId());
if (class_scope) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
auto class_scope =
TryAsClassScope(context, context.decl_name_stack().PeekParentScopeId());
if (class_scope) {
if (auto class_scope =
TryAsClassScope(context, context.decl_name_stack().PeekParentScopeId())) {

Comment on lines +101 to +102
}
self_type.inst_id = SemIR::ErrorInst::TypeInstId;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we set the scope to be invalid here too?

// TODO: If the explicit self-type matches a lookup of NameId::SelfType,
// add a note to the diagnostic: "remove the explicit `Self` type here",
// and continue without an ErrorInst. See ExtendImplSelfAsDefault.
context.emitter().Emit(self_node_id, RequireImplsExtendWithExplicitSelf);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems a bit inconsistent to be diagnosing this here but diagnosing the class case when handling the ImplTypeAs node.

@zygoloid zygoloid removed the request for review from josh11b December 5, 2025 23:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants