Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions toolchain/check/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ cc_library(
"name_component.cpp",
"name_lookup.cpp",
"name_ref.cpp",
"name_scope.cpp",
"operator.cpp",
"pattern.cpp",
"pattern_match.cpp",
Expand Down Expand Up @@ -113,6 +114,7 @@ cc_library(
"name_component.h",
"name_lookup.h",
"name_ref.h",
"name_scope.h",
"operator.h",
"param_and_arg_refs_stack.h",
"pattern.h",
Expand Down
55 changes: 50 additions & 5 deletions toolchain/check/handle_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "toolchain/check/inst.h"
#include "toolchain/check/modifiers.h"
#include "toolchain/check/name_lookup.h"
#include "toolchain/check/name_scope.h"
#include "toolchain/check/pattern_match.h"
#include "toolchain/check/type.h"
#include "toolchain/check/type_completion.h"
Expand All @@ -24,6 +25,16 @@

namespace Carbon::Check {

// Returns the implicit `Self` type for an `impl` when it's in a `class`
// declaration.
//
// TODO: Mixin scopes also have a default `Self` type.
static auto GetImplDefaultSelfType(Context& context,
const ClassScope& class_scope)
-> SemIR::TypeId {
return context.classes().Get(class_scope.class_decl.class_id).self_type_id;
}

auto HandleParseNode(Context& context, Parse::ImplIntroducerId node_id)
-> bool {
// This might be a generic impl.
Expand Down Expand Up @@ -56,23 +67,57 @@ auto HandleParseNode(Context& context, Parse::ForallId /*node_id*/) -> bool {

auto HandleParseNode(Context& context, Parse::ImplTypeAsId node_id) -> bool {
auto [self_node, self_id] = context.node_stack().PopExprWithNodeId();
auto self_type_inst_id = ExprAsType(context, self_node, self_id).inst_id;
context.node_stack().Push(node_id, self_type_inst_id);
auto self_type = ExprAsType(context, self_node, self_id);

const auto& introducer = context.decl_introducer_state_stack().innermost();
if (introducer.modifier_set.HasAnyOf(KeywordModifierSet::Extend)) {
// TODO: Also handle the parent scope being a mixin.
auto class_scope =
TryAsClassScope(context, context.decl_name_stack().PeekParentScopeId());
if (class_scope) {
Comment on lines +75 to +77
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())) {

// If we're not inside a class at all, that will be diagnosed against the
// `extend` elsewhere.
auto extend_node = introducer.modifier_node_id(ModifierOrder::Extend);
CARBON_DIAGNOSTIC(ExtendImplSelfAs, Error,
"cannot `extend` an `impl` with an explicit self type");
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;
}
}
Comment on lines +83 to +101
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();

}

// Introduce `Self`. Note that we add this name lexically rather than adding
// to the `NameScopeId` of the `impl`, because this happens before we enter
// the `impl` scope or even identify which `impl` we're declaring.
// TODO: Revisit this once #3714 is resolved.
AddNameToLookup(context, SemIR::NameId::SelfType, self_type_inst_id);
AddNameToLookup(context, SemIR::NameId::SelfType, self_type.inst_id);
context.node_stack().Push(node_id, self_type.inst_id);
return true;
}

auto HandleParseNode(Context& context, Parse::ImplDefaultSelfAsId node_id)
-> bool {
auto self_inst_id = SemIR::TypeInstId::None;

if (auto self_type_id = GetImplDefaultSelfType(context);
self_type_id.has_value()) {
auto class_scope =
TryAsClassScope(context, context.decl_name_stack().PeekParentScopeId());
if (class_scope) {
Comment on lines +117 to +119
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())) {

auto self_type_id = GetImplDefaultSelfType(context, *class_scope);
// Build the implicit access to the enclosing `Self`.
// TODO: Consider calling `HandleNameAsExpr` to build this implicit `Self`
// expression. We've already done the work to check that the enclosing
Expand Down
18 changes: 12 additions & 6 deletions toolchain/check/handle_require.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ auto HandleParseNode(Context& context, Parse::RequireDefaultSelfImplsId node_id)
}

CARBON_CHECK(context.types().Is<SemIR::FacetType>(self_type_id));
// TODO: We could simplify with a call to ExprAsType, like below?
auto self_facet_as_type = AddTypeInst<SemIR::FacetAccessType>(
context, node_id,
{.type_id = SemIR::TypeType::TypeId,
Expand All @@ -86,16 +87,21 @@ auto HandleParseNode(Context& context, Parse::RequireDefaultSelfImplsId node_id)
auto HandleParseNode(Context& context, Parse::RequireTypeImplsId node_id)
-> bool {
auto [self_node_id, self_inst_id] = context.node_stack().PopExprWithNodeId();
auto self_type = ExprAsType(context, self_node_id, self_inst_id);

auto introducer = context.decl_introducer_state_stack().innermost();
const auto& introducer = context.decl_introducer_state_stack().innermost();
if (introducer.modifier_set.HasAnyOf(KeywordModifierSet::Extend)) {
CARBON_DIAGNOSTIC(RequireImplsExtendWithExplicitSelf, Error,
"`extend require impls` with explicit type");
context.emitter().Emit(self_node_id, RequireImplsExtendWithExplicitSelf);
self_inst_id = SemIR::ErrorInst::InstId;
if (self_type.type_id != SemIR::ErrorInst::TypeId) {
CARBON_DIAGNOSTIC(RequireImplsExtendWithExplicitSelf, Error,
"`extend require impls` with explicit type");
// 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.

}
self_type.inst_id = SemIR::ErrorInst::TypeInstId;
Comment on lines +101 to +102
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?

}

auto self_type = ExprAsType(context, self_node_id, self_inst_id);
context.node_stack().Push(node_id, self_type.inst_id);
return true;
}
Expand Down
42 changes: 1 addition & 41 deletions toolchain/check/impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -399,24 +399,10 @@ static auto TryAsClassScope(Context& context, SemIR::NameScopeId scope_id)
return context.insts().TryGetAs<SemIR::ClassDecl>(scope.inst_id());
}

auto GetImplDefaultSelfType(Context& context) -> SemIR::TypeId {
auto parent_scope_id = context.decl_name_stack().PeekParentScopeId();

if (auto class_decl = TryAsClassScope(context, parent_scope_id)) {
return context.classes().Get(class_decl->class_id).self_type_id;
}

// TODO: This is also valid in a mixin.

return SemIR::TypeId::None;
}

// Process an `extend impl` declaration by extending the impl scope with the
// `impl`'s scope.
static auto ExtendImpl(Context& context, Parse::NodeId extend_node,
SemIR::LocId loc_id, SemIR::ImplId impl_id,
Parse::NodeId self_type_node_id,
SemIR::TypeId self_type_id,
SemIR::LocId implicit_params_loc_id,
SemIR::TypeInstId constraint_type_inst_id,
SemIR::TypeId constraint_type_id) -> bool {
Expand All @@ -443,31 +429,6 @@ static auto ExtendImpl(Context& context, Parse::NodeId extend_node,

const auto& impl = context.impls().Get(impl_id);

if (context.parse_tree().node_kind(self_type_node_id) ==
Parse::NodeKind::ImplTypeAs) {
CARBON_DIAGNOSTIC(ExtendImplSelfAs, Error,
"cannot `extend` an `impl` with an explicit self type");
auto diag = context.emitter().Build(extend_node, ExtendImplSelfAs);

// If the explicit self type is not the default, just bail out.
if (self_type_id != GetImplDefaultSelfType(context)) {
diag.Emit();
parent_scope.set_has_error();
return false;
}

// The explicit self type is the same as the default self type, so suggest
// removing it and recover as if it were not present.
if (auto self_as =
context.parse_tree_and_subtrees().ExtractAs<Parse::ImplTypeAs>(
self_type_node_id)) {
CARBON_DIAGNOSTIC(ExtendImplSelfAsDefault, Note,
"remove the explicit `Self` type here");
diag.Note(self_as->type_expr, ExtendImplSelfAsDefault);
}
diag.Emit();
}

if (impl.witness_id == SemIR::ErrorInst::InstId) {
parent_scope.set_has_error();
} else {
Expand Down Expand Up @@ -618,8 +579,7 @@ auto StartImplDecl(Context& context, SemIR::LocId loc_id,
stored_impl_info.generic_id)});
}
if (!ExtendImpl(context, extend_impl->extend_node_id, loc_id,
impl_decl.impl_id, extend_impl->self_type_node_id,
self_type_id, implicit_params_loc_id, constraint_id,
impl_decl.impl_id, implicit_params_loc_id, constraint_id,
extend_impl->constraint_type_id)) {
// Don't allow the invalid impl to be used.
FillImplWitnessWithErrors(context, stored_impl_info);
Expand Down
4 changes: 0 additions & 4 deletions toolchain/check/impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,6 @@ auto CheckConstraintIsInterface(Context& context, SemIR::InstId impl_decl_id,
SemIR::TypeInstId constraint_id)
-> SemIR::SpecificInterface;

// Returns the implicit `Self` type for an `impl` when it's in a `class`
// declaration.
auto GetImplDefaultSelfType(Context& context) -> SemIR::TypeId;

// For `StartImplDecl`, additional details for an `extend impl` declaration.
struct ExtendImplDecl {
Parse::NodeId self_type_node_id;
Expand Down
25 changes: 25 additions & 0 deletions toolchain/check/name_scope.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include "toolchain/check/name_scope.h"

namespace Carbon::Check {

auto TryAsClassScope(Context& context, SemIR::NameScopeId scope_id)
-> std::optional<ClassScope> {
if (!scope_id.has_value()) {
return std::nullopt;
}
auto& scope = context.name_scopes().Get(scope_id);
if (!scope.inst_id().has_value()) {
return std::nullopt;
}
auto class_decl = context.insts().TryGetAs<SemIR::ClassDecl>(scope.inst_id());
if (!class_decl) {
return std::nullopt;
}
return {{.class_decl = *class_decl, .name_scope = &scope}};
}

} // namespace Carbon::Check
28 changes: 28 additions & 0 deletions toolchain/check/name_scope.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#ifndef CARBON_TOOLCHAIN_CHECK_NAME_SCOPE_H_
#define CARBON_TOOLCHAIN_CHECK_NAME_SCOPE_H_

#include <optional>

#include "toolchain/check/context.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/typed_insts.h"

namespace Carbon::Check {

struct ClassScope {
SemIR::ClassDecl class_decl;
SemIR::NameScope* name_scope;
};

// If the specified name scope corresponds to a class, returns the corresponding
// class declaration.
auto TryAsClassScope(Context& context, SemIR::NameScopeId scope_id)
-> std::optional<ClassScope>;

} // namespace Carbon::Check

#endif // CARBON_TOOLCHAIN_CHECK_NAME_SCOPE_H_
26 changes: 26 additions & 0 deletions toolchain/check/testdata/facet/require_invalid.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,29 @@ interface Z {
// CHECK:STDERR:
extend require () impls Y;
}

// --- 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;
}
Comment on lines +127 to +151
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;
}

14 changes: 14 additions & 0 deletions toolchain/check/testdata/impl/extend_impl.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,20 @@ interface I {}

class C {
// CHECK:STDERR: fail_extend_impl_nonexistent.carbon:[[@LINE+4]]:15: error: name `nonexistent` not found [NameNotFound]
// CHECK:STDERR: extend impl nonexistent as I {}
// CHECK:STDERR: ^~~~~~~~~~~
// CHECK:STDERR:
extend impl nonexistent as I {}
}

// --- fail_extend_impl_nonexistent_pointer.carbon

library "[[@TEST_NAME]]";

interface I {}

class C {
// CHECK:STDERR: fail_extend_impl_nonexistent_pointer.carbon:[[@LINE+4]]:15: error: name `nonexistent` not found [NameNotFound]
// CHECK:STDERR: extend impl nonexistent* as I {}
// CHECK:STDERR: ^~~~~~~~~~~
// CHECK:STDERR:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ class E {
// CHECK:STDOUT: %Int.type: type = generic_class_type @Int [concrete]
// CHECK:STDOUT: %Int.generic: %Int.type = struct_value () [concrete]
// CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [concrete]
// CHECK:STDOUT: %I.impl_witness.943: <witness> = impl_witness @C.%I.impl_witness_table [concrete]
// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete]
// CHECK:STDOUT: %complete_type.357: <witness> = complete_type_witness %empty_struct_type [concrete]
// CHECK:STDOUT: %D: type = class_type @D [concrete]
Expand Down Expand Up @@ -101,7 +100,7 @@ class E {
// CHECK:STDOUT: !requires:
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: impl @i32.as.I.impl: %i32 as %I.ref {
// CHECK:STDOUT: impl @<error>.as.I.impl: <error> as %I.ref {
// CHECK:STDOUT: !members:
// CHECK:STDOUT: witness = <error>
// CHECK:STDOUT: }
Expand All @@ -114,13 +113,11 @@ class E {
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: class @C {
// CHECK:STDOUT: impl_decl @i32.as.I.impl [concrete] {} {
// CHECK:STDOUT: impl_decl @<error>.as.I.impl [concrete] {} {
// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
// CHECK:STDOUT: %i32: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
// CHECK:STDOUT: %I.ref: type = name_ref I, file.%I.decl [concrete = constants.%I.type]
// CHECK:STDOUT: }
// CHECK:STDOUT: %I.impl_witness_table = impl_witness_table (), @i32.as.I.impl [concrete]
// CHECK:STDOUT: %I.impl_witness: <witness> = impl_witness %I.impl_witness_table [concrete = constants.%I.impl_witness.943]
// CHECK:STDOUT: %complete_type: <witness> = complete_type_witness constants.%empty_struct_type [concrete = constants.%complete_type.357]
// CHECK:STDOUT: complete_type_witness = %complete_type
// CHECK:STDOUT:
Expand Down
Loading