Move self to the explicit () parameter list (proposal #7016) (#7272)

Implements proposal #7016: `self` moves from the deduced implicit list
(`fn F[self: Self]()`) to the front of the explicit list. Its type may
be written explicitly (`fn F(self: Self)`) or omitted, in which case it
defaults to `Self` (`fn F(self)`, `fn F(ref self)`); `self` in the
implicit list is rejected.

Throughout checking, `self` is modeled as the first explicit parameter.
Because a method is just a function whose first parameter is `self`, it
can also be called as an ordinary function with the receiver passed
explicitly (`Type.M(obj, ...)`), not only as `obj.M(...)`. A new
`SemIR::CallArgParamPatterns` helper chooses the parameters matched
against the explicit arguments, excluding a leading `self` only when it
is supplied as a method-call receiver; arity checking, conversion, and
generic deduction use it. The resulting SemIR and lowering are
unchanged: `self` is still `call_param0`, and witnesses, thunks, and
vtables are unaffected.

An omitted `self` type is parsed as a `SelfBindingPattern` node with no
type expression; checking synthesizes the `Self` type so it behaves
exactly like `self: Self`. However, the exact spelling used must match
between a forward declaration and a definition, following #3763's rules
around declaration matching.

Generated functions, thunks, and C++ interop import/export build `self`
as the first explicit parameter, and the `self`-type override (e.g.
Derived->Base for a virtual override) applies to the explicit `self`.
Placement is validated by new diagnostics: `SelfInImplicitParamList`,
`SelfNotFirstParam`, and `SelfOutsideParamList`. The benchmark source
generator and the documentation adopt the `(self)` shorthand; the
prelude, the examples, and the test data are migrated in the following
commits.

Assisted-by: Claude Code with Claude Opus 4.7

---------

Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com>
This commit is contained in:
Chandler Carruth
2026-06-10 15:30:43 +00:00
committed by GitHub
co-authored by josh11b
parent fb05da761f
commit 7871237c15
296 changed files with 6101 additions and 4899 deletions
+15 -7
View File
@@ -275,7 +275,10 @@ static auto EstimateAvgFunctionDeclLines(SourceGen::FunctionDeclParams params)
static auto EstimateAvgMethodDeclLines(SourceGen::MethodDeclParams params)
-> double {
// Currently model a uniform distribution [0, max] parameters. Assume a line
// break before the first parameter for >2 and after every 4th.
// break before the first parameter for >2 and after every 4th. A Carbon
// method also emits a leading `self`, but `self` only rarely tips a method
// onto an additional wrapped line, so the estimate ignores it; this stays
// calibrated against the emitter (see `source_gen_test`).
int param_lines = 0;
for (int num_params : llvm::seq_inclusive(0, params.max_params)) {
if (num_params > NumSingleLineMethodParams) {
@@ -797,10 +800,6 @@ auto SourceGen::GenerateFunctionDecl(
os << indent << "// TODO: make better comment text\n";
if (!IsCpp()) {
os << indent << (is_private ? "private " : "") << "fn " << name;
if (is_method) {
os << "[self: Self]";
}
} else {
os << indent;
if (!is_method) {
@@ -815,10 +814,19 @@ auto SourceGen::GenerateFunctionDecl(
(is_method ? NumSingleLineMethodParams : NumSingleLineFunctionParams)) {
os << "\n" << indent << " ";
}
// For Carbon methods, `self` is the first explicit parameter. Its type is
// omitted, which defaults it to `Self`.
bool is_carbon_method = is_method && !IsCpp();
if (is_carbon_method) {
os << "self";
}
UniqueIdentifierPopper unique_param_names(*this, param_names);
for (int i : llvm::seq(param_count)) {
if (i > 0) {
if ((i % MaxParamsPerLine) == 0) {
// `self` occupies the first slot for Carbon methods, so shift the index
// used for separators and line wrapping.
int slot = i + (is_carbon_method ? 1 : 0);
if (slot > 0) {
if ((slot % MaxParamsPerLine) == 0) {
os << ",\n" << indent << " ";
} else {
os << ", ";