mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 21:20:11 +01:00
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>
277 lines
13 KiB
C++
277 lines
13 KiB
C++
// 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_CONVERT_H_
|
|
#define CARBON_TOOLCHAIN_CHECK_CONVERT_H_
|
|
|
|
#include "toolchain/check/context.h"
|
|
#include "toolchain/check/pending_block.h"
|
|
#include "toolchain/sem_ir/entity_with_params_base.h"
|
|
#include "toolchain/sem_ir/ids.h"
|
|
|
|
namespace Carbon::Check {
|
|
|
|
// Description of the target of a conversion.
|
|
struct ConversionTarget {
|
|
enum Kind : int8_t {
|
|
// Perform no conversion. The source expression must already have type
|
|
// `type_id`.
|
|
NoOp,
|
|
// Convert to a value of type `type_id`.
|
|
Value,
|
|
// Convert to either a value or a reference of type `type_id`.
|
|
ValueOrRef,
|
|
// Convert to a durable reference of type `type_id`.
|
|
DurableRef,
|
|
// Convert to a reference, suitable for binding to a reference parameter.
|
|
// This allows both durable and ephemeral references. The restriction that
|
|
// only a `ref self` parameter can bind to an ephemeral reference is
|
|
// enforced separately when handling `ref` tags on call arguments.
|
|
RefParam,
|
|
// Equivalent to RefParam, except that the source expression is not required
|
|
// to be marked with a `ref` tag, such as an argument to a `ref self`
|
|
// parameter or an operator operand.
|
|
UnmarkedRefParam,
|
|
// Convert to a reference of type `type_id`, for use as the argument to a
|
|
// C++ thunk.
|
|
CppThunkRef,
|
|
// Convert for an explicit `as` cast. This allows any expression category
|
|
// as the result, and uses the `As` interface instead of the `ImplicitAs`
|
|
// interface.
|
|
ExplicitAs,
|
|
// Convert for an explicit `unsafe as` cast. This allows any expression
|
|
// category as the result, and uses the `UnsafeAs` interface instead of the
|
|
// `As` or `ImplicitAs` interface.
|
|
ExplicitUnsafeAs,
|
|
// The result of the conversion is discarded. It can't be an initializing
|
|
// expression, but can be anything else.
|
|
Discarded,
|
|
// Convert to an initializing expression that uses `type_id`'s initializing
|
|
// representation. The resulting expression will usually be a
|
|
// repr-initializing expression, but may be an in-place initializing
|
|
// expression if the source expression was. If `storage_id` is present, it
|
|
// is used as the storage argument for the converted expression, and it must
|
|
// be present if the initializing representation might be in-place.
|
|
Initializing,
|
|
// Convert to an in-place initializing expression whose storage is
|
|
// designated by `storage_id` (which must not be `None`).
|
|
InPlaceInitializing,
|
|
Last = InPlaceInitializing
|
|
};
|
|
// The kind of the target for this conversion.
|
|
Kind kind;
|
|
// The target type for the conversion.
|
|
SemIR::TypeId type_id;
|
|
// The storage being initialized, if any. It must be valid to reference this
|
|
// instruction after splicing in `storage_access_block` (if specified), so it
|
|
// must either dominate the initializer or be one of the instructions in
|
|
// `storage_access_block`.
|
|
SemIR::InstId storage_id = SemIR::InstId::None;
|
|
// For an initializer, a block of pending instructions that `storage_id`
|
|
// depends on. This block will be spliced or merged before any reference to
|
|
// `storage_id`, and may be discarded if `storage_id` is not accessed.
|
|
PendingBlock* storage_access_block = nullptr;
|
|
// Whether failure of conversion is an error and is diagnosed to the user.
|
|
// When looking for a possible conversion but with graceful fallback,
|
|
// `diagnose` should be false. If `diagnose` is false, an `ErrorInst` may be
|
|
// returned, but it must be discarded.
|
|
bool diagnose = true;
|
|
|
|
// Are we converting this value into an initializer for an object?
|
|
auto is_initializer() const -> bool {
|
|
return kind == Initializing || kind == InPlaceInitializing;
|
|
}
|
|
// Is this some kind of explicit `as` conversion?
|
|
auto is_explicit_as() const -> bool {
|
|
return kind == ExplicitAs || kind == ExplicitUnsafeAs;
|
|
}
|
|
};
|
|
|
|
// Convert a value to another type and expression category.
|
|
auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
|
|
ConversionTarget target) -> SemIR::InstId;
|
|
|
|
// Performs initialization of `storage_id` from the expression `value_id`, which
|
|
// is converted to an initializing expression of the type of `storage_id` if
|
|
// necessary, and returns the possibly-converted initializing expression.
|
|
//
|
|
// `storage_id` is used as the storage argument of the resulting expression
|
|
// except as noted below. As a consequence, `storage_id` must dominate
|
|
// `value_id` and its subexpressions. This will typically only be the case if
|
|
// `storage_id` syntactically precedes `value_id`. Otherwise, some action will
|
|
// need to be taken to reorder the code, such as instead calling `Initialize`
|
|
// with a pending block containing `storage_id`, or creating a separate
|
|
// `InstBlock` to hold either the storage or the initializer.
|
|
//
|
|
// `for_return` indicates that this conversion is initializing the operand of a
|
|
// `return` statement. This means that `storage_id` will be the return slot
|
|
// parameter, which isn't valid to access if the type's initializing
|
|
// representation is not in-place, so in that case `storage_id` will be used
|
|
// solely for its type.
|
|
//
|
|
// This function does not guarantee to perform an in-place initialization, so
|
|
// the caller is responsible for passing the returned `InstId` to an inst that
|
|
// is documented as consuming it, such as `Assign`.
|
|
auto InitializeExisting(Context& context, SemIR::LocId loc_id,
|
|
SemIR::InstId storage_id, SemIR::InstId value_id,
|
|
bool for_return = false) -> SemIR::InstId;
|
|
|
|
// Result of Initialize.
|
|
struct InitializeResult {
|
|
// The storage location that contains the initialized value. This may be
|
|
// different from the `storage_id` that was passed to `Initialize` if the
|
|
// storage block was written over existing instructions rather than being
|
|
// spliced in.
|
|
SemIR::InstId storage_id;
|
|
// The converted initializing expression used to initialize the storage.
|
|
SemIR::InstId init_id;
|
|
};
|
|
|
|
// Performs initialization of `storage_id` from the expression `value_id`, which
|
|
// is converted to an initializing expression of the type of `storage_id` if
|
|
// necessary. `storage_access_block` should be used to supply a pending block
|
|
// that allocates the storage, and typically contains `storage_id`. The target
|
|
// of the initialization will be either `storage_id` itself, or an existing
|
|
// storage argument instruction that is overwritten to hold a copy of
|
|
// `storage_id` as an optimization for SemIR compactness.
|
|
//
|
|
// The storage instruction will only be written over an existing instruction if
|
|
// it is the sole instruction in the pending block. This is expected to be a
|
|
// common case. After this happens, the copy of the instruction in the pending
|
|
// block is expected to be unreachable from the SemIR::File. As a result, the
|
|
// `storage_id` instruction should not be referenced again after calling this
|
|
// function, and this function takes it by rvalue reference to remind the caller
|
|
// of this.
|
|
//
|
|
// If the overwrite optimization is not performed, `storage_access_block` will
|
|
// be inserted before any use of the storage by the initializer, and will be
|
|
// inserted even if the initializer does not actually use the storage. It must
|
|
// be valid to reference `storage_id` after splicing in `storage_access_block`,
|
|
// so `storage_id` must either dominate the initializer (but see the TODO below)
|
|
// or be one of the instructions in `storage_access_block`. If `storage_id` is
|
|
// known to always dominate the initializer, `InitializeExisting` should be used
|
|
// instead.
|
|
//
|
|
// TODO: We don't have an implementation of a proper dominance check, so we
|
|
// fake one up by comparing the order in which the insts were created.
|
|
//
|
|
// This function does not guarantee to perform an in-place initialization, so
|
|
// the caller is responsible for passing the returned `inst_id` to an inst that
|
|
// is documented as consuming it, such as `Assign`.
|
|
auto Initialize(Context& context, SemIR::LocId loc_id,
|
|
SemIR::InstId&& storage_id, PendingBlock&& storage_access_block,
|
|
SemIR::InstId value_id) -> InitializeResult;
|
|
|
|
// Convert the given expression to a value expression of the same type.
|
|
auto ConvertToValueExpr(Context& context, SemIR::InstId expr_id)
|
|
-> SemIR::InstId;
|
|
|
|
// Convert the given expression to a value or reference expression of the same
|
|
// type.
|
|
auto ConvertToValueOrRefExpr(Context& context, SemIR::InstId expr_id)
|
|
-> SemIR::InstId;
|
|
|
|
// Converts `expr_id` to a value expression of type `type_id`.
|
|
//
|
|
// If `diagnose` is true, errors are diagnosed to the user. Set it to false when
|
|
// looking to see if a conversion is possible but with graceful fallback. If
|
|
// `diagnose` is false, an `ErrorInst` may be returned, but it must be
|
|
// discarded.
|
|
auto ConvertToValueOfType(Context& context, SemIR::LocId loc_id,
|
|
SemIR::InstId expr_id, SemIR::TypeId type_id,
|
|
bool diagnose = true) -> SemIR::InstId;
|
|
|
|
// Convert the given expression to a value or reference expression of the given
|
|
// type.
|
|
auto ConvertToValueOrRefOfType(Context& context, SemIR::LocId loc_id,
|
|
SemIR::InstId expr_id, SemIR::TypeId type_id)
|
|
-> SemIR::InstId;
|
|
|
|
// Attempted to convert `expr_id` to a value expression of type `type_id`, with
|
|
// graceful failure, which does not result in diagnostics. An ErrorInst
|
|
// instruction is still returned on failure.
|
|
auto TryConvertToValueOfType(Context& context, SemIR::LocId loc_id,
|
|
SemIR::InstId expr_id, SemIR::TypeId type_id)
|
|
-> SemIR::InstId;
|
|
|
|
// Converts `value_id` to a value expression of type `bool`.
|
|
auto ConvertToBoolValue(Context& context, SemIR::LocId loc_id,
|
|
SemIR::InstId value_id) -> SemIR::InstId;
|
|
|
|
// Converts `value_id` to type `type_id` for an `as` expression.
|
|
auto ConvertForExplicitAs(Context& context, Parse::NodeId as_node,
|
|
SemIR::InstId value_id, SemIR::TypeId type_id,
|
|
bool unsafe) -> SemIR::InstId;
|
|
|
|
// Implicitly converts a set of arguments to match the parameter types in a
|
|
// function call. Returns a block containing the converted implicit and explicit
|
|
// argument values for runtime parameters. `is_desugared` indicates that this
|
|
// call was produced by desugaring, not written as a function call in user code,
|
|
// so arguments to `ref` parameters aren't required to have `ref` tags.
|
|
auto ConvertCallArgs(Context& context, SemIR::InstId self_id,
|
|
llvm::ArrayRef<SemIR::InstId> arg_refs,
|
|
SemIR::InstId return_arg_id, const SemIR::Function& callee,
|
|
SemIR::SpecificId callee_specific_id, bool is_desugared)
|
|
-> SemIR::InstBlockId;
|
|
|
|
// A type that has been converted for use as a type expression.
|
|
struct TypeExpr {
|
|
static const TypeExpr None;
|
|
|
|
// Returns a TypeExpr describing a type with no associated spelling or type
|
|
// sugar.
|
|
static auto ForUnsugared(Context& context, SemIR::TypeId type_id) -> TypeExpr;
|
|
|
|
// The converted expression of type `type`, or `ErrorInst::InstId`.
|
|
SemIR::TypeInstId inst_id;
|
|
// The corresponding type, or `ErrorInst::TypeId`.
|
|
SemIR::TypeId type_id;
|
|
};
|
|
|
|
inline constexpr TypeExpr TypeExpr::None = {.inst_id = SemIR::TypeInstId::None,
|
|
.type_id = SemIR::TypeId::None};
|
|
|
|
// Converts an expression for use as a type.
|
|
//
|
|
// If `diagnose` is true, errors are diagnosed to the user. Set it to false when
|
|
// looking to see if a conversion is possible but with graceful fallback. If
|
|
// `diagnose` is false, an `ErrorInst` may be returned, but it must be
|
|
// discarded.
|
|
//
|
|
// TODO: Most of the callers of this function discard the `inst_id` and lose
|
|
// track of the conversion. In most cases we should be retaining that as the
|
|
// operand of some downstream instruction.
|
|
auto ExprAsType(Context& context, SemIR::LocId loc_id, SemIR::InstId value_id,
|
|
bool diagnose = true) -> TypeExpr;
|
|
|
|
// Converts an expression in a form position for use as a form.
|
|
//
|
|
// Note that the right-hand side of a `->` return type declaration is not
|
|
// a form position for this purpose, because it uses a special syntax to specify
|
|
// forms. `ReturnExprAsForm` should be used instead in that case.
|
|
//
|
|
// `diagnose` has the same effect as in `ExprAsType`.
|
|
auto FormExprAsForm(Context& context, SemIR::LocId loc_id,
|
|
SemIR::InstId value_id) -> Context::FormExpr;
|
|
|
|
// Evaluates an expression in the return-type position (following `->`, not
|
|
// `->?`) for use as a form, following the special-case language rules for
|
|
// evaluating an expression in that position.
|
|
auto ReturnExprAsForm(Context& context, SemIR::LocId loc_id,
|
|
SemIR::InstId value_id) -> Context::FormExpr;
|
|
|
|
// Handles an expression whose result value is unused.
|
|
auto DiscardExpr(Context& context, SemIR::InstId expr_id) -> void;
|
|
|
|
// Given that `expr_id` is the result of a conversion from an expression of
|
|
// type `type_id`, this undoes that conversion. Currently, this only supports
|
|
// the case where the original conversion was a derived-to-base conversion.
|
|
auto UnsafeUndoConvert(Context& context, SemIR::LocId loc_id,
|
|
SemIR::InstId expr_id, SemIR::TypeId type_id)
|
|
-> SemIR::InstId;
|
|
|
|
} // namespace Carbon::Check
|
|
|
|
#endif // CARBON_TOOLCHAIN_CHECK_CONVERT_H_
|