diff --git a/common/fuzzing/carbon.proto b/common/fuzzing/carbon.proto index 1d761798e384..97644012200a 100644 --- a/common/fuzzing/carbon.proto +++ b/common/fuzzing/carbon.proto @@ -241,8 +241,27 @@ message ExpressionStatement { } message AssignStatement { + enum Operator { + Plain = 0; + Add = 1; + And = 2; + Div = 3; + Mul = 4; + Or = 5; + Sub = 6; + Mod = 7; + Xor = 8; + ShiftLeft = 9; + ShiftRight = 10; + } optional Expression lhs = 1; optional Expression rhs = 2; + optional Operator op = 3; +} + +message IncrementDecrementStatement { + optional Expression operand = 1; + optional bool is_increment = 2; } message VariableDefinitionStatement { @@ -321,6 +340,7 @@ message Statement { BreakStatement break_statement = 13; ContinueStatement continue_statement = 14; ForStatement for_statement = 15; + IncrementDecrementStatement inc_dec = 16; } } diff --git a/common/fuzzing/proto_to_carbon.cpp b/common/fuzzing/proto_to_carbon.cpp index 0ab4834d4da5..a22821f61e05 100644 --- a/common/fuzzing/proto_to_carbon.cpp +++ b/common/fuzzing/proto_to_carbon.cpp @@ -534,12 +534,54 @@ static auto StatementToCarbon(const Fuzzing::Statement& statement, case Fuzzing::Statement::kAssign: { const auto& assign_statement = statement.assign(); ExpressionToCarbon(assign_statement.lhs(), out); - out << " = "; + switch (assign_statement.op()) { + case Fuzzing::AssignStatement::Plain: + out << " = "; + break; + case Fuzzing::AssignStatement::Add: + out << " += "; + break; + case Fuzzing::AssignStatement::And: + out << " &= "; + break; + case Fuzzing::AssignStatement::Div: + out << " /= "; + break; + case Fuzzing::AssignStatement::Mod: + out << " %= "; + break; + case Fuzzing::AssignStatement::Mul: + out << " *= "; + break; + case Fuzzing::AssignStatement::Or: + out << " |= "; + break; + case Fuzzing::AssignStatement::ShiftLeft: + out << " <<= "; + break; + case Fuzzing::AssignStatement::ShiftRight: + out << " >>= "; + break; + case Fuzzing::AssignStatement::Sub: + out << " -= "; + break; + case Fuzzing::AssignStatement::Xor: + out << " ^= "; + break; + } ExpressionToCarbon(assign_statement.rhs(), out); out << ";"; break; } + case Fuzzing::Statement::kIncDec: { + const auto& inc_dec_statement = statement.inc_dec(); + out << (inc_dec_statement.is_increment() ? "++" : "--"); + ExpressionToCarbon(inc_dec_statement.operand(), out); + out << ";"; + break; + } + case Fuzzing::Statement::kVariableDefinition: { const auto& def = statement.variable_definition(); if (def.is_returned()) { diff --git a/explorer/ast/ast_rtti.txt b/explorer/ast/ast_rtti.txt index d1789aa097bd..4a0bdcb1b76b 100644 --- a/explorer/ast/ast_rtti.txt +++ b/explorer/ast/ast_rtti.txt @@ -36,6 +36,7 @@ class AlternativeSignature : AstNode; abstract class Statement : AstNode; class ExpressionStatement : Statement; class Assign : Statement; + class IncrementDecrement : Statement; class VariableDefinition : Statement; class If : Statement; abstract class Return : Statement; diff --git a/explorer/ast/statement.cpp b/explorer/ast/statement.cpp index 045439d2a2f2..d634f74686e4 100644 --- a/explorer/ast/statement.cpp +++ b/explorer/ast/statement.cpp @@ -72,7 +72,13 @@ void Statement::PrintDepth(int depth, llvm::raw_ostream& out) const { break; case StatementKind::Assign: { const auto& assign = cast(*this); - out << assign.lhs() << " = " << assign.rhs() << ";"; + out << assign.lhs() << " " << AssignOperatorToString(assign.op()) << " " + << assign.rhs() << ";"; + break; + } + case StatementKind::IncrementDecrement: { + const auto& inc_dec = cast(*this); + out << (inc_dec.is_increment() ? "++" : "--") << inc_dec.argument(); break; } case StatementKind::If: { @@ -137,4 +143,31 @@ void Statement::PrintDepth(int depth, llvm::raw_ostream& out) const { } } +auto AssignOperatorToString(AssignOperator op) -> std::string_view { + switch (op) { + case AssignOperator::Plain: + return "="; + case AssignOperator::Add: + return "+="; + case AssignOperator::Div: + return "/="; + case AssignOperator::Mul: + return "*="; + case AssignOperator::Mod: + return "%="; + case AssignOperator::Sub: + return "-="; + case AssignOperator::And: + return "&="; + case AssignOperator::Or: + return "|="; + case AssignOperator::Xor: + return "^="; + case AssignOperator::ShiftLeft: + return "<<="; + case AssignOperator::ShiftRight: + return ">>="; + } +} + } // namespace Carbon diff --git a/explorer/ast/statement.h b/explorer/ast/statement.h index f6cba44280a6..8a7311c593e5 100644 --- a/explorer/ast/statement.h +++ b/explorer/ast/statement.h @@ -86,11 +86,31 @@ class ExpressionStatement : public Statement { Nonnull expression_; }; +enum class AssignOperator { + Plain, + Add, + Div, + Mul, + Mod, + Sub, + And, + Or, + Xor, + ShiftLeft, + ShiftRight, +}; + +// Returns the spelling of this assignment operator token. +auto AssignOperatorToString(AssignOperator op) -> std::string_view; + class Assign : public Statement { public: - Assign(SourceLocation source_loc, Nonnull lhs, + Assign(SourceLocation source_loc, Nonnull lhs, AssignOperator op, Nonnull rhs) - : Statement(AstNodeKind::Assign, source_loc), lhs_(lhs), rhs_(rhs) {} + : Statement(AstNodeKind::Assign, source_loc), + lhs_(lhs), + rhs_(rhs), + op_(op) {} static auto classof(const AstNode* node) -> bool { return InheritsFromAssign(node->kind()); @@ -101,12 +121,66 @@ class Assign : public Statement { auto rhs() const -> const Expression& { return *rhs_; } auto rhs() -> Expression& { return *rhs_; } + auto op() const -> AssignOperator { return op_; } + // Can only be called by type-checking, if a conversion was required. void set_rhs(Nonnull rhs) { rhs_ = rhs; } + // Set the rewritten form of this statement. Can only be called during type + // checking. + auto set_rewritten_form(Nonnull rewritten_form) -> void { + CARBON_CHECK(!rewritten_form_.has_value()) << "rewritten form set twice"; + rewritten_form_ = rewritten_form; + } + + // Get the rewritten form of this statement. A rewritten form is used when + // the statement is rewritten as a function call on an interface. A + // rewritten form is not used when providing built-in operator semantics for + // a plain assignment. + auto rewritten_form() const -> std::optional> { + return rewritten_form_; + } + private: Nonnull lhs_; Nonnull rhs_; + AssignOperator op_; + std::optional> rewritten_form_; +}; + +class IncrementDecrement : public Statement { + public: + IncrementDecrement(SourceLocation source_loc, Nonnull argument, + bool is_increment) + : Statement(AstNodeKind::IncrementDecrement, source_loc), + argument_(argument), + is_increment_(is_increment) {} + + static auto classof(const AstNode* node) -> bool { + return InheritsFromIncrementDecrement(node->kind()); + } + + auto argument() const -> const Expression& { return *argument_; } + auto argument() -> Expression& { return *argument_; } + + bool is_increment() const { return is_increment_; } + + // Set the rewritten form of this statement. Can only be called during type + // checking. + auto set_rewritten_form(Nonnull rewritten_form) -> void { + CARBON_CHECK(!rewritten_form_.has_value()) << "rewritten form set twice"; + rewritten_form_ = rewritten_form; + } + + // Get the rewritten form of this statement. + auto rewritten_form() const -> std::optional> { + return rewritten_form_; + } + + private: + Nonnull argument_; + bool is_increment_; + std::optional> rewritten_form_; }; class VariableDefinition : public Statement { diff --git a/explorer/data/prelude.carbon b/explorer/data/prelude.carbon index a3b089fafa27..b3dffbe9e90b 100644 --- a/explorer/data/prelude.carbon +++ b/explorer/data/prelude.carbon @@ -501,6 +501,168 @@ external impl i32 as RightShiftWith(i32) where .Result = i32 { } } +// ----------------------------------- +// Assignment and compound assignment. +// ----------------------------------- + +interface AssignWith(U:! type) { + fn Op[addr self: Self*](other: U); +} +constraint Assign { extends AssignWith(Self); } + +interface AddAssignWith(U:! type) { + fn Op[addr self: Self*](other: U); +} +constraint AddAssign { extends AddAssignWith(Self); } + +interface SubAssignWith(U:! type) { + fn Op[addr self: Self*](other: U); +} +constraint SubAssign { extends SubAssignWith(Self); } + +interface MulAssignWith(U:! type) { + fn Op[addr self: Self*](other: U); +} +constraint MulAssign { extends MulAssignWith(Self); } + +interface DivAssignWith(U:! type) { + fn Op[addr self: Self*](other: U); +} +constraint DivAssign { extends DivAssignWith(Self); } + +interface ModAssignWith(U:! type) { + fn Op[addr self: Self*](other: U); +} +constraint ModAssign { extends ModAssignWith(Self); } + +interface BitAndAssignWith(U:! type) { + fn Op[addr self: Self*](other: U); +} +constraint BitAssignAnd { extends BitAndAssignWith(Self); } + +interface BitOrAssignWith(U:! type) { + fn Op[addr self: Self*](other: U); +} +constraint BitAssignOr { extends BitOrAssignWith(Self); } + +interface BitXorAssignWith(U:! type) { + fn Op[addr self: Self*](other: U); +} +constraint BitAssignXor { extends BitXorAssignWith(Self); } + +interface LeftShiftAssignWith(U:! type) { + fn Op[addr self: Self*](other: U); +} +constraint LeftShiftAssign { extends LeftShiftAssignWith(Self); } + +interface RightShiftAssignWith(U:! type) { + fn Op[addr self: Self*](other: U); +} +constraint RightShiftAssign { extends RightShiftAssignWith(Self); } + +// TODO: This is temporary, and should eventually be replaced by +// something more fine-grained. Not all class types should be +// assignable. +impl forall [T:! type, U:! ImplicitAs(T)] + T as AssignWith(U) { + fn Op[addr self: Self*](other: U) { + *self = other.Convert(); + } +} + +// TODO: Should `AddWith(U) & AssignWith(.Self.(AddWith(U).Result))` work? +impl forall [U:! type, T:! AddWith(U) where .Self is AssignWith(.Self.Result)] + T as AddAssignWith(U) { + fn Op[addr self: Self*](other: U) { + *self = *self + other; + } +} + +impl forall [U:! type, T:! SubWith(U) where .Self is AssignWith(.Self.Result)] + T as SubAssignWith(U) { + fn Op[addr self: Self*](other: U) { + *self = *self - other; + } +} + +impl forall [U:! type, T:! MulWith(U) where .Self is AssignWith(.Self.Result)] + T as MulAssignWith(U) { + fn Op[addr self: Self*](other: U) { + *self = *self * other; + } +} + +impl forall [U:! type, T:! DivWith(U) where .Self is AssignWith(.Self.Result)] + T as DivAssignWith(U) { + fn Op[addr self: Self*](other: U) { + *self = *self / other; + } +} + +impl forall [U:! type, T:! ModWith(U) where .Self is AssignWith(.Self.Result)] + T as ModAssignWith(U) { + fn Op[addr self: Self*](other: U) { + *self = *self % other; + } +} + +impl forall [U:! type, T:! BitAndWith(U) where .Self is AssignWith(.Self.Result)] + T as BitAndAssignWith(U) { + fn Op[addr self: Self*](other: U) { + *self = *self & other; + } +} + +impl forall [U:! type, T:! BitOrWith(U) where .Self is AssignWith(.Self.Result)] + T as BitOrAssignWith(U) { + fn Op[addr self: Self*](other: U) { + *self = *self | other; + } +} + +impl forall [U:! type, T:! BitXorWith(U) where .Self is AssignWith(.Self.Result)] + T as BitXorAssignWith(U) { + fn Op[addr self: Self*](other: U) { + *self = *self ^ other; + } +} + +impl forall [U:! type, T:! LeftShiftWith(U) where .Self is AssignWith(.Self.Result)] + T as LeftShiftAssignWith(U) { + fn Op[addr self: Self*](other: U) { + *self = *self << other; + } +} + +impl forall [U:! type, T:! RightShiftWith(U) where .Self is AssignWith(.Self.Result)] + T as RightShiftAssignWith(U) { + fn Op[addr self: Self*](other: U) { + *self = *self >> other; + } +} + +// ------------------------ +// Increment and decrement. +// ------------------------ + +interface Inc { + fn Op[addr self: Self*](); +} +interface Dec { + fn Op[addr self: Self*](); +} + +impl i32 as Inc { + fn Op[addr self: Self*]() { + *self = *self + 1; + } +} +impl i32 as Dec { + fn Op[addr self: Self*]() { + *self = *self - 1; + } +} + //------------------------- // Optional //------------------------- diff --git a/explorer/fuzzing/ast_to_proto.cpp b/explorer/fuzzing/ast_to_proto.cpp index 17af2bcb5430..ed04adfc8573 100644 --- a/explorer/fuzzing/ast_to_proto.cpp +++ b/explorer/fuzzing/ast_to_proto.cpp @@ -89,6 +89,34 @@ static auto OperatorToProtoEnum(const Operator op) } } +static auto AssignOperatorToProtoEnum(const AssignOperator op) + -> Fuzzing::AssignStatement::Operator { + switch (op) { + case AssignOperator::Plain: + return Fuzzing::AssignStatement::Plain; + case AssignOperator::Add: + return Fuzzing::AssignStatement::Add; + case AssignOperator::And: + return Fuzzing::AssignStatement::And; + case AssignOperator::Mul: + return Fuzzing::AssignStatement::Mul; + case AssignOperator::Div: + return Fuzzing::AssignStatement::Div; + case AssignOperator::Mod: + return Fuzzing::AssignStatement::Mod; + case AssignOperator::Or: + return Fuzzing::AssignStatement::Or; + case AssignOperator::ShiftLeft: + return Fuzzing::AssignStatement::ShiftLeft; + case AssignOperator::ShiftRight: + return Fuzzing::AssignStatement::ShiftRight; + case AssignOperator::Sub: + return Fuzzing::AssignStatement::Sub; + case AssignOperator::Xor: + return Fuzzing::AssignStatement::Xor; + } +} + static auto FieldInitializerToProto(const FieldInitializer& field) -> Fuzzing::FieldInitializer { Fuzzing::FieldInitializer field_proto; @@ -435,6 +463,15 @@ static auto StatementToProto(const Statement& statement) -> Fuzzing::Statement { auto* assign_proto = statement_proto.mutable_assign(); *assign_proto->mutable_lhs() = ExpressionToProto(assign.lhs()); *assign_proto->mutable_rhs() = ExpressionToProto(assign.rhs()); + assign_proto->set_op(AssignOperatorToProtoEnum(assign.op())); + break; + } + + case StatementKind::IncrementDecrement: { + const auto& inc_dec = cast(statement); + auto* inc_dec_proto = statement_proto.mutable_inc_dec(); + *inc_dec_proto->mutable_operand() = ExpressionToProto(inc_dec.argument()); + inc_dec_proto->set_is_increment(inc_dec.is_increment()); break; } diff --git a/explorer/interpreter/builtins.h b/explorer/interpreter/builtins.h index 04d3a5c42bc2..0cbdcb4ea575 100644 --- a/explorer/interpreter/builtins.h +++ b/explorer/interpreter/builtins.h @@ -51,7 +51,26 @@ class Builtins { LeftShiftWith, RightShiftWith, - Last = RightShiftWith + // Simple assignment. + AssignWith, + + // Compound assignment. + AddAssignWith, + SubAssignWith, + MulAssignWith, + DivAssignWith, + ModAssignWith, + BitAndAssignWith, + BitOrAssignWith, + BitXorAssignWith, + LeftShiftAssignWith, + RightShiftAssignWith, + + // Increment and decrement. + Inc, + Dec, + + Last = Dec }; // TODO: In C++20, replace with `using enum Builtin;`. static constexpr Builtin As = Builtin::As; @@ -61,6 +80,7 @@ class Builtins { static constexpr Builtin LessEqWith = Builtin::LessEqWith; static constexpr Builtin GreaterWith = Builtin::GreaterWith; static constexpr Builtin GreaterEqWith = Builtin::GreaterEqWith; + static constexpr Builtin CompareWith = Builtin::CompareWith; static constexpr Builtin Negate = Builtin::Negate; static constexpr Builtin AddWith = Builtin::AddWith; static constexpr Builtin SubWith = Builtin::SubWith; @@ -73,7 +93,19 @@ class Builtins { static constexpr Builtin BitXorWith = Builtin::BitXorWith; static constexpr Builtin LeftShiftWith = Builtin::LeftShiftWith; static constexpr Builtin RightShiftWith = Builtin::RightShiftWith; - static constexpr Builtin CompareWith = Builtin::CompareWith; + static constexpr Builtin AssignWith = Builtin::AssignWith; + static constexpr Builtin AddAssignWith = Builtin::AddAssignWith; + static constexpr Builtin SubAssignWith = Builtin::SubAssignWith; + static constexpr Builtin MulAssignWith = Builtin::MulAssignWith; + static constexpr Builtin DivAssignWith = Builtin::DivAssignWith; + static constexpr Builtin ModAssignWith = Builtin::ModAssignWith; + static constexpr Builtin BitAndAssignWith = Builtin::BitAndAssignWith; + static constexpr Builtin BitOrAssignWith = Builtin::BitOrAssignWith; + static constexpr Builtin BitXorAssignWith = Builtin::BitXorAssignWith; + static constexpr Builtin LeftShiftAssignWith = Builtin::LeftShiftAssignWith; + static constexpr Builtin RightShiftAssignWith = Builtin::RightShiftAssignWith; + static constexpr Builtin Inc = Builtin::Inc; + static constexpr Builtin Dec = Builtin::Dec; // Register a declaration that might be a builtin. void Register(Nonnull decl); @@ -90,11 +122,39 @@ class Builtins { private: static constexpr int NumBuiltins = static_cast(Builtin::Last) + 1; static constexpr const char* BuiltinNames[NumBuiltins] = { - "As", "ImplicitAs", "EqWith", "LessWith", - "LessEqWith", "GreaterWith", "GreaterEqWith", "CompareWith", - "Negate", "AddWith", "SubWith", "MulWith", - "DivWith", "ModWith", "BitComplement", "BitAndWith", - "BitOrWith", "BitXorWith", "LeftShiftWith", "RightShiftWith"}; + "As", + "ImplicitAs", + "EqWith", + "LessWith", + "LessEqWith", + "GreaterWith", + "GreaterEqWith", + "CompareWith", + "Negate", + "AddWith", + "SubWith", + "MulWith", + "DivWith", + "ModWith", + "BitComplement", + "BitAndWith", + "BitOrWith", + "BitXorWith", + "LeftShiftWith", + "RightShiftWith", + "AssignWith", + "AddAssignWith", + "SubAssignWith", + "MulAssignWith", + "DivAssignWith", + "ModAssignWith", + "BitAndAssignWith", + "BitOrAssignWith", + "BitXorAssignWith", + "LeftShiftAssignWith", + "RightShiftAssignWith", + "Inc", + "Dec"}; std::optional> builtins_[NumBuiltins] = {}; }; diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index 7a59e195e34e..ec2957eb1e26 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -1937,6 +1937,13 @@ auto Interpreter::StepStmt() -> ErrorOr { } case StatementKind::Assign: { const auto& assign = cast(stmt); + if (auto rewrite = assign.rewritten_form()) { + if (act.pos() == 0) { + return todo_.Spawn(std::make_unique(*rewrite)); + } else { + return todo_.FinishAction(); + } + } if (act.pos() == 0) { // { {(lv = e) :: C, E, F} :: S, H} // -> { {lv :: ([] = e) :: C, E, F} :: S, H} @@ -1958,6 +1965,15 @@ auto Interpreter::StepStmt() -> ErrorOr { return todo_.FinishAction(); } } + case StatementKind::IncrementDecrement: { + const auto& inc_dec = cast(stmt); + if (act.pos() == 0) { + return todo_.Spawn( + std::make_unique(*inc_dec.rewritten_form())); + } else { + return todo_.FinishAction(); + } + } case StatementKind::If: if (act.pos() == 0) { // { {(if (e) then_stmt else else_stmt) :: C, E, F} :: S, H} diff --git a/explorer/interpreter/resolve_control_flow.cpp b/explorer/interpreter/resolve_control_flow.cpp index fcb72a4d3942..70fe49902b86 100644 --- a/explorer/interpreter/resolve_control_flow.cpp +++ b/explorer/interpreter/resolve_control_flow.cpp @@ -130,6 +130,7 @@ static auto ResolveControlFlow(Nonnull statement, return Success(); case StatementKind::ExpressionStatement: case StatementKind::Assign: + case StatementKind::IncrementDecrement: case StatementKind::VariableDefinition: case StatementKind::Run: case StatementKind::Await: diff --git a/explorer/interpreter/resolve_names.cpp b/explorer/interpreter/resolve_names.cpp index 6c5fed44fe85..7be76f0b0019 100644 --- a/explorer/interpreter/resolve_names.cpp +++ b/explorer/interpreter/resolve_names.cpp @@ -380,6 +380,11 @@ static auto ResolveNames(Statement& statement, StaticScope& enclosing_scope) CARBON_RETURN_IF_ERROR(ResolveNames(assign.rhs(), enclosing_scope)); break; } + case StatementKind::IncrementDecrement: { + auto& inc_dec = cast(statement); + CARBON_RETURN_IF_ERROR(ResolveNames(inc_dec.argument(), enclosing_scope)); + break; + } case StatementKind::VariableDefinition: { auto& def = cast(statement); if (def.has_init()) { diff --git a/explorer/interpreter/resolve_unformed.cpp b/explorer/interpreter/resolve_unformed.cpp index 2ad8d1af3928..533cb9a6176e 100644 --- a/explorer/interpreter/resolve_unformed.cpp +++ b/explorer/interpreter/resolve_unformed.cpp @@ -222,7 +222,10 @@ static auto ResolveUnformed(Nonnull statement, } case StatementKind::Assign: { const auto& assign = cast(*statement); - if (assign.lhs().kind() == ExpressionKind::IdentifierExpression) { + if (assign.op() != AssignOperator::Plain) { + CARBON_RETURN_IF_ERROR(ResolveUnformed(&assign.lhs(), flow_facts, + FlowFacts::ActionType::Check)); + } else if (assign.lhs().kind() == ExpressionKind::IdentifierExpression) { CARBON_RETURN_IF_ERROR(ResolveUnformed(&assign.lhs(), flow_facts, FlowFacts::ActionType::Form)); } else { @@ -234,6 +237,12 @@ static auto ResolveUnformed(Nonnull statement, FlowFacts::ActionType::Check)); break; } + case StatementKind::IncrementDecrement: { + CARBON_RETURN_IF_ERROR( + ResolveUnformed(&cast(statement)->argument(), + flow_facts, FlowFacts::ActionType::Check)); + break; + } case StatementKind::ExpressionStatement: { const auto& exp_stmt = cast(*statement); CARBON_RETURN_IF_ERROR( diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index d3d339b7dc73..295fa74d337b 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -4090,6 +4090,36 @@ auto TypeChecker::TypeCheckGenericBinding(GenericBinding& binding, return Success(); } +// Get the builtin interface that should be used for the given kind of +// assignment operator. +static Builtins::Builtin GetBuiltinInterfaceForAssignOperator( + AssignOperator op) { + switch (op) { + case AssignOperator::Plain: + return Builtins::AssignWith; + case AssignOperator::Add: + return Builtins::AddAssignWith; + case AssignOperator::Sub: + return Builtins::SubAssignWith; + case AssignOperator::Mul: + return Builtins::MulAssignWith; + case AssignOperator::Div: + return Builtins::DivAssignWith; + case AssignOperator::Mod: + return Builtins::ModAssignWith; + case AssignOperator::And: + return Builtins::BitAndAssignWith; + case AssignOperator::Or: + return Builtins::BitOrAssignWith; + case AssignOperator::Xor: + return Builtins::BitXorAssignWith; + case AssignOperator::ShiftLeft: + return Builtins::LeftShiftAssignWith; + case AssignOperator::ShiftRight: + return Builtins::RightShiftAssignWith; + } +} + auto TypeChecker::TypeCheckStmt(Nonnull s, const ImplScope& impl_scope) -> ErrorOr { @@ -4232,11 +4262,38 @@ auto TypeChecker::TypeCheckStmt(Nonnull s, return ProgramError(assign.source_loc()) << "Cannot assign to rvalue '" << assign.lhs() << "'"; } + if (assign.op() == AssignOperator::Plain && + IsSameType(&assign.lhs().static_type(), &assign.rhs().static_type(), + impl_scope)) { + // TODO: Interface lookup. + CARBON_ASSIGN_OR_RETURN( + Nonnull converted_rhs, + ImplicitlyConvert("assignment", impl_scope, &assign.rhs(), + &assign.lhs().static_type())); + assign.set_rhs(converted_rhs); + } else { + CARBON_ASSIGN_OR_RETURN( + Nonnull rewritten, + BuildBuiltinMethodCall( + impl_scope, &assign.lhs(), + BuiltinInterfaceName{ + GetBuiltinInterfaceForAssignOperator(assign.op()), + {&assign.rhs().static_type()}}, + BuiltinMethodCall{"Op", {&assign.rhs()}})); + assign.set_rewritten_form(rewritten); + } + return Success(); + } + case StatementKind::IncrementDecrement: { + auto& inc_dec = cast(*s); CARBON_ASSIGN_OR_RETURN( - Nonnull converted_rhs, - ImplicitlyConvert("assignment", impl_scope, &assign.rhs(), - &assign.lhs().static_type())); - assign.set_rhs(converted_rhs); + Nonnull rewritten, + BuildBuiltinMethodCall( + impl_scope, &inc_dec.argument(), + BuiltinInterfaceName{ + inc_dec.is_increment() ? Builtins::Inc : Builtins::Dec, {}}, + BuiltinMethodCall{"Op"})); + inc_dec.set_rewritten_form(rewritten); return Success(); } case StatementKind::ExpressionStatement: { @@ -4380,6 +4437,7 @@ auto TypeChecker::ExpectReturnOnAllPaths( case StatementKind::Run: case StatementKind::Await: case StatementKind::Assign: + case StatementKind::IncrementDecrement: case StatementKind::ExpressionStatement: case StatementKind::While: case StatementKind::For: diff --git a/explorer/syntax/lexer.lpp b/explorer/syntax/lexer.lpp index 06c428479080..1cad583a93eb 100644 --- a/explorer/syntax/lexer.lpp +++ b/explorer/syntax/lexer.lpp @@ -31,92 +31,104 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception %s AFTER_OPERAND /* table-begin */ -ABSTRACT "abstract" -ADDR "addr" -ALIAS "alias" -AMPERSAND "&" -AND "and" -API "api" -ARROW "->" -AS "as" -AUTO "auto" -AWAIT "__await" -BASE "base" -BOOL "bool" -BREAK "break" -CARET "^" -CASE "case" -CHOICE "choice" -CLASS "class" -COLON ":" -COLON_BANG ":!" -COMMA "," -CONSTRAINT "constraint" -CONTINUATION "__continuation" -CONTINUATION_TYPE "__Continuation" -CONTINUE "continue" -DEFAULT "default" -DESTRUCTOR "destructor" -DOUBLE_ARROW "=>" -ELSE "else" -EQUAL "=" -EQUAL_EQUAL "==" -EXTENDS "extends" -EXTERNAL "external" -FALSE "false" -FN "fn" -FN_TYPE "__Fn" -FOR "for" -FORALL "forall" -GREATER ">" -GREATER_EQUAL ">=" -GREATER_GREATER ">>" -IF "if" -IMPL "impl" -IMPORT "import" -IN "in" -INTERFACE "interface" -IS "is" -LEFT_CURLY_BRACE "{" -LEFT_PARENTHESIS "(" -LEFT_SQUARE_BRACKET "[" -LESS "<" -LESS_EQUAL "<=" -LESS_LESS "<<" -LET "let" -LIBRARY "library" -MATCH "match" -MATCH_FIRST "__match_first" -MINUS "-" -MIX "__mix" -MIXIN "__mixin" -NOT "not" -NOT_EQUAL "!=" -OR "or" -PACKAGE "package" -PERCENT "%" -PERIOD "." -PIPE "|" -PLUS "+" -RETURN "return" -RETURNED "returned" -RIGHT_CURLY_BRACE "}" -RIGHT_PARENTHESIS ")" -RIGHT_SQUARE_BRACKET "]" -RUN "__run" -SELF "Self" -SEMICOLON ";" -SLASH "/" -STRING "String" -THEN "then" -TRUE "true" -TYPE "type" -UNDERSCORE "_" -UNIMPL_EXAMPLE "__unimplemented_example_infix" -VAR "var" -VIRTUAL "virtual" -WHERE "where" -WHILE "while" +ABSTRACT "abstract" +ADDR "addr" +ALIAS "alias" +AMPERSAND "&" +AMPERSAND_EQUAL "&=" +AND "and" +API "api" +ARROW "->" +AS "as" +AUTO "auto" +AWAIT "__await" +BASE "base" +BOOL "bool" +BREAK "break" +CARET "^" +CARET_EQUAL "^=" +CASE "case" +CHOICE "choice" +CLASS "class" +COLON ":" +COLON_BANG ":!" +COMMA "," +CONSTRAINT "constraint" +CONTINUATION "__continuation" +CONTINUATION_TYPE "__Continuation" +CONTINUE "continue" +DEFAULT "default" +DESTRUCTOR "destructor" +DOUBLE_ARROW "=>" +ELSE "else" +EQUAL "=" +EQUAL_EQUAL "==" +EXTENDS "extends" +EXTERNAL "external" +FALSE "false" +FN "fn" +FN_TYPE "__Fn" +FOR "for" +FORALL "forall" +GREATER ">" +GREATER_EQUAL ">=" +GREATER_GREATER ">>" +GREATER_GREATER_EQUAL ">>=" +IF "if" +IMPL "impl" +IMPORT "import" +IN "in" +INTERFACE "interface" +IS "is" +LEFT_CURLY_BRACE "{" +LEFT_PARENTHESIS "(" +LEFT_SQUARE_BRACKET "[" +LESS "<" +LESS_EQUAL "<=" +LESS_LESS "<<" +LESS_LESS_EQUAL "<<=" +LET "let" +LIBRARY "library" +MATCH "match" +MATCH_FIRST "__match_first" +MINUS "-" +MINUS_EQUAL "-=" +MINUS_MINUS "--" +MIX "__mix" +MIXIN "__mixin" +NOT "not" +NOT_EQUAL "!=" +OR "or" +PACKAGE "package" +PERCENT "%" +PERCENT_EQUAL "%=" +PERIOD "." +PIPE "|" +PIPE_EQUAL "|=" +PLUS "+" +PLUS_EQUAL "+=" +PLUS_PLUS "++" +RETURN "return" +RETURNED "returned" +RIGHT_CURLY_BRACE "}" +RIGHT_PARENTHESIS ")" +RIGHT_SQUARE_BRACKET "]" +RUN "__run" +SELF "Self" +SEMICOLON ";" +SLASH "/" +SLASH_EQUAL "/=" +STAR_EQUAL "*=" +STRING "String" +THEN "then" +TRUE "true" +TYPE "type" +UNDERSCORE "_" +UNIMPL_EXAMPLE "__unimplemented_example_infix" +VAR "var" +VIRTUAL "virtual" +WHERE "where" +WHILE "while" /* table-end */ /* This should be kept table-like, but isn't automatic due to spaces. */ @@ -140,89 +152,101 @@ operand_start [(A-Za-z0-9_\"] %} /* table-begin */ -{ABSTRACT} { return CARBON_SIMPLE_TOKEN(ABSTRACT); } -{ADDR} { return CARBON_SIMPLE_TOKEN(ADDR); } -{ALIAS} { return CARBON_SIMPLE_TOKEN(ALIAS); } -{AMPERSAND} { return CARBON_SIMPLE_TOKEN(AMPERSAND); } -{AND} { return CARBON_SIMPLE_TOKEN(AND); } -{API} { return CARBON_SIMPLE_TOKEN(API); } -{ARROW} { return CARBON_SIMPLE_TOKEN(ARROW); } -{AS} { return CARBON_SIMPLE_TOKEN(AS); } -{AUTO} { return CARBON_SIMPLE_TOKEN(AUTO); } -{AWAIT} { return CARBON_SIMPLE_TOKEN(AWAIT); } -{BASE} { return CARBON_SIMPLE_TOKEN(BASE); } -{BOOL} { return CARBON_SIMPLE_TOKEN(BOOL); } -{BREAK} { return CARBON_SIMPLE_TOKEN(BREAK); } -{CARET} { return CARBON_SIMPLE_TOKEN(CARET); } -{CASE} { return CARBON_SIMPLE_TOKEN(CASE); } -{CHOICE} { return CARBON_SIMPLE_TOKEN(CHOICE); } -{CLASS} { return CARBON_SIMPLE_TOKEN(CLASS); } -{COLON_BANG} { return CARBON_SIMPLE_TOKEN(COLON_BANG); } -{COLON} { return CARBON_SIMPLE_TOKEN(COLON); } -{COMMA} { return CARBON_SIMPLE_TOKEN(COMMA); } -{CONSTRAINT} { return CARBON_SIMPLE_TOKEN(CONSTRAINT); } -{CONTINUATION_TYPE} { return CARBON_SIMPLE_TOKEN(CONTINUATION_TYPE); } -{CONTINUATION} { return CARBON_SIMPLE_TOKEN(CONTINUATION); } -{CONTINUE} { return CARBON_SIMPLE_TOKEN(CONTINUE); } -{DEFAULT} { return CARBON_SIMPLE_TOKEN(DEFAULT); } -{DESTRUCTOR} { return CARBON_SIMPLE_TOKEN(DESTRUCTOR); } -{DOUBLE_ARROW} { return CARBON_SIMPLE_TOKEN(DOUBLE_ARROW); } -{ELSE} { return CARBON_SIMPLE_TOKEN(ELSE); } -{EQUAL_EQUAL} { return CARBON_SIMPLE_TOKEN(EQUAL_EQUAL); } -{EQUAL} { return CARBON_SIMPLE_TOKEN(EQUAL); } -{EXTENDS} { return CARBON_SIMPLE_TOKEN(EXTENDS); } -{EXTERNAL} { return CARBON_SIMPLE_TOKEN(EXTERNAL); } -{FALSE} { return CARBON_SIMPLE_TOKEN(FALSE); } -{FN_TYPE} { return CARBON_SIMPLE_TOKEN(FN_TYPE); } -{FN} { return CARBON_SIMPLE_TOKEN(FN); } -{FORALL} { return CARBON_SIMPLE_TOKEN(FORALL); } -{FOR} { return CARBON_SIMPLE_TOKEN(FOR); } -{GREATER_EQUAL} { return CARBON_SIMPLE_TOKEN(GREATER_EQUAL); } -{GREATER_GREATER} { return CARBON_SIMPLE_TOKEN(GREATER_GREATER); } -{GREATER} { return CARBON_SIMPLE_TOKEN(GREATER); } -{IF} { return CARBON_SIMPLE_TOKEN(IF); } -{IMPL} { return CARBON_SIMPLE_TOKEN(IMPL); } -{IMPORT} { return CARBON_SIMPLE_TOKEN(IMPORT); } -{INTERFACE} { return CARBON_SIMPLE_TOKEN(INTERFACE); } -{IN} { return CARBON_SIMPLE_TOKEN(IN); } -{IS} { return CARBON_SIMPLE_TOKEN(IS); } -{LEFT_CURLY_BRACE} { return CARBON_SIMPLE_TOKEN(LEFT_CURLY_BRACE); } -{LEFT_PARENTHESIS} { return CARBON_SIMPLE_TOKEN(LEFT_PARENTHESIS); } -{LEFT_SQUARE_BRACKET} { return CARBON_SIMPLE_TOKEN(LEFT_SQUARE_BRACKET); } -{LESS_EQUAL} { return CARBON_SIMPLE_TOKEN(LESS_EQUAL); } -{LESS_LESS} { return CARBON_SIMPLE_TOKEN(LESS_LESS); } -{LESS} { return CARBON_SIMPLE_TOKEN(LESS); } -{LET} { return CARBON_SIMPLE_TOKEN(LET); } -{LIBRARY} { return CARBON_SIMPLE_TOKEN(LIBRARY); } -{MATCH_FIRST} { return CARBON_SIMPLE_TOKEN(MATCH_FIRST); } -{MATCH} { return CARBON_SIMPLE_TOKEN(MATCH); } -{MINUS} { return CARBON_SIMPLE_TOKEN(MINUS); } -{MIXIN} { return CARBON_SIMPLE_TOKEN(MIXIN); } -{MIX} { return CARBON_SIMPLE_TOKEN(MIX); } -{NOT_EQUAL} { return CARBON_SIMPLE_TOKEN(NOT_EQUAL); } -{NOT} { return CARBON_SIMPLE_TOKEN(NOT); } -{OR} { return CARBON_SIMPLE_TOKEN(OR); } -{PACKAGE} { return CARBON_SIMPLE_TOKEN(PACKAGE); } -{PERCENT} { return CARBON_SIMPLE_TOKEN(PERCENT); } -{PERIOD} { return CARBON_SIMPLE_TOKEN(PERIOD); } -{PIPE} { return CARBON_SIMPLE_TOKEN(PIPE); } -{PLUS} { return CARBON_SIMPLE_TOKEN(PLUS); } -{RETURNED} { return CARBON_SIMPLE_TOKEN(RETURNED); } -{RETURN} { return CARBON_SIMPLE_TOKEN(RETURN); } -{RUN} { return CARBON_SIMPLE_TOKEN(RUN); } -{SELF} { return CARBON_SIMPLE_TOKEN(SELF); } -{SEMICOLON} { return CARBON_SIMPLE_TOKEN(SEMICOLON); } -{SLASH} { return CARBON_SIMPLE_TOKEN(SLASH); } -{STRING} { return CARBON_SIMPLE_TOKEN(STRING); } -{THEN} { return CARBON_SIMPLE_TOKEN(THEN); } -{TRUE} { return CARBON_SIMPLE_TOKEN(TRUE); } -{TYPE} { return CARBON_SIMPLE_TOKEN(TYPE); } -{UNDERSCORE} { return CARBON_SIMPLE_TOKEN(UNDERSCORE); } -{UNIMPL_EXAMPLE} { return CARBON_SIMPLE_TOKEN(UNIMPL_EXAMPLE); } -{VAR} { return CARBON_SIMPLE_TOKEN(VAR); } -{VIRTUAL} { return CARBON_SIMPLE_TOKEN(VIRTUAL); } -{WHERE} { return CARBON_SIMPLE_TOKEN(WHERE); } -{WHILE} { return CARBON_SIMPLE_TOKEN(WHILE); } +{ABSTRACT} { return CARBON_SIMPLE_TOKEN(ABSTRACT); } +{ADDR} { return CARBON_SIMPLE_TOKEN(ADDR); } +{ALIAS} { return CARBON_SIMPLE_TOKEN(ALIAS); } +{AMPERSAND_EQUAL} { return CARBON_SIMPLE_TOKEN(AMPERSAND_EQUAL); } +{AMPERSAND} { return CARBON_SIMPLE_TOKEN(AMPERSAND); } +{AND} { return CARBON_SIMPLE_TOKEN(AND); } +{API} { return CARBON_SIMPLE_TOKEN(API); } +{ARROW} { return CARBON_SIMPLE_TOKEN(ARROW); } +{AS} { return CARBON_SIMPLE_TOKEN(AS); } +{AUTO} { return CARBON_SIMPLE_TOKEN(AUTO); } +{AWAIT} { return CARBON_SIMPLE_TOKEN(AWAIT); } +{BASE} { return CARBON_SIMPLE_TOKEN(BASE); } +{BOOL} { return CARBON_SIMPLE_TOKEN(BOOL); } +{BREAK} { return CARBON_SIMPLE_TOKEN(BREAK); } +{CARET_EQUAL} { return CARBON_SIMPLE_TOKEN(CARET_EQUAL); } +{CARET} { return CARBON_SIMPLE_TOKEN(CARET); } +{CASE} { return CARBON_SIMPLE_TOKEN(CASE); } +{CHOICE} { return CARBON_SIMPLE_TOKEN(CHOICE); } +{CLASS} { return CARBON_SIMPLE_TOKEN(CLASS); } +{COLON_BANG} { return CARBON_SIMPLE_TOKEN(COLON_BANG); } +{COLON} { return CARBON_SIMPLE_TOKEN(COLON); } +{COMMA} { return CARBON_SIMPLE_TOKEN(COMMA); } +{CONSTRAINT} { return CARBON_SIMPLE_TOKEN(CONSTRAINT); } +{CONTINUATION_TYPE} { return CARBON_SIMPLE_TOKEN(CONTINUATION_TYPE); } +{CONTINUATION} { return CARBON_SIMPLE_TOKEN(CONTINUATION); } +{CONTINUE} { return CARBON_SIMPLE_TOKEN(CONTINUE); } +{DEFAULT} { return CARBON_SIMPLE_TOKEN(DEFAULT); } +{DESTRUCTOR} { return CARBON_SIMPLE_TOKEN(DESTRUCTOR); } +{DOUBLE_ARROW} { return CARBON_SIMPLE_TOKEN(DOUBLE_ARROW); } +{ELSE} { return CARBON_SIMPLE_TOKEN(ELSE); } +{EQUAL_EQUAL} { return CARBON_SIMPLE_TOKEN(EQUAL_EQUAL); } +{EQUAL} { return CARBON_SIMPLE_TOKEN(EQUAL); } +{EXTENDS} { return CARBON_SIMPLE_TOKEN(EXTENDS); } +{EXTERNAL} { return CARBON_SIMPLE_TOKEN(EXTERNAL); } +{FALSE} { return CARBON_SIMPLE_TOKEN(FALSE); } +{FN_TYPE} { return CARBON_SIMPLE_TOKEN(FN_TYPE); } +{FN} { return CARBON_SIMPLE_TOKEN(FN); } +{FORALL} { return CARBON_SIMPLE_TOKEN(FORALL); } +{FOR} { return CARBON_SIMPLE_TOKEN(FOR); } +{GREATER_EQUAL} { return CARBON_SIMPLE_TOKEN(GREATER_EQUAL); } +{GREATER_GREATER_EQUAL} { return CARBON_SIMPLE_TOKEN(GREATER_GREATER_EQUAL); } +{GREATER_GREATER} { return CARBON_SIMPLE_TOKEN(GREATER_GREATER); } +{GREATER} { return CARBON_SIMPLE_TOKEN(GREATER); } +{IF} { return CARBON_SIMPLE_TOKEN(IF); } +{IMPL} { return CARBON_SIMPLE_TOKEN(IMPL); } +{IMPORT} { return CARBON_SIMPLE_TOKEN(IMPORT); } +{INTERFACE} { return CARBON_SIMPLE_TOKEN(INTERFACE); } +{IN} { return CARBON_SIMPLE_TOKEN(IN); } +{IS} { return CARBON_SIMPLE_TOKEN(IS); } +{LEFT_CURLY_BRACE} { return CARBON_SIMPLE_TOKEN(LEFT_CURLY_BRACE); } +{LEFT_PARENTHESIS} { return CARBON_SIMPLE_TOKEN(LEFT_PARENTHESIS); } +{LEFT_SQUARE_BRACKET} { return CARBON_SIMPLE_TOKEN(LEFT_SQUARE_BRACKET); } +{LESS_EQUAL} { return CARBON_SIMPLE_TOKEN(LESS_EQUAL); } +{LESS_LESS_EQUAL} { return CARBON_SIMPLE_TOKEN(LESS_LESS_EQUAL); } +{LESS_LESS} { return CARBON_SIMPLE_TOKEN(LESS_LESS); } +{LESS} { return CARBON_SIMPLE_TOKEN(LESS); } +{LET} { return CARBON_SIMPLE_TOKEN(LET); } +{LIBRARY} { return CARBON_SIMPLE_TOKEN(LIBRARY); } +{MATCH_FIRST} { return CARBON_SIMPLE_TOKEN(MATCH_FIRST); } +{MATCH} { return CARBON_SIMPLE_TOKEN(MATCH); } +{MINUS_EQUAL} { return CARBON_SIMPLE_TOKEN(MINUS_EQUAL); } +{MINUS_MINUS} { return CARBON_SIMPLE_TOKEN(MINUS_MINUS); } +{MINUS} { return CARBON_SIMPLE_TOKEN(MINUS); } +{MIXIN} { return CARBON_SIMPLE_TOKEN(MIXIN); } +{MIX} { return CARBON_SIMPLE_TOKEN(MIX); } +{NOT_EQUAL} { return CARBON_SIMPLE_TOKEN(NOT_EQUAL); } +{NOT} { return CARBON_SIMPLE_TOKEN(NOT); } +{OR} { return CARBON_SIMPLE_TOKEN(OR); } +{PACKAGE} { return CARBON_SIMPLE_TOKEN(PACKAGE); } +{PERCENT_EQUAL} { return CARBON_SIMPLE_TOKEN(PERCENT_EQUAL); } +{PERCENT} { return CARBON_SIMPLE_TOKEN(PERCENT); } +{PERIOD} { return CARBON_SIMPLE_TOKEN(PERIOD); } +{PIPE_EQUAL} { return CARBON_SIMPLE_TOKEN(PIPE_EQUAL); } +{PIPE} { return CARBON_SIMPLE_TOKEN(PIPE); } +{PLUS_EQUAL} { return CARBON_SIMPLE_TOKEN(PLUS_EQUAL); } +{PLUS_PLUS} { return CARBON_SIMPLE_TOKEN(PLUS_PLUS); } +{PLUS} { return CARBON_SIMPLE_TOKEN(PLUS); } +{RETURNED} { return CARBON_SIMPLE_TOKEN(RETURNED); } +{RETURN} { return CARBON_SIMPLE_TOKEN(RETURN); } +{RUN} { return CARBON_SIMPLE_TOKEN(RUN); } +{SELF} { return CARBON_SIMPLE_TOKEN(SELF); } +{SEMICOLON} { return CARBON_SIMPLE_TOKEN(SEMICOLON); } +{SLASH_EQUAL} { return CARBON_SIMPLE_TOKEN(SLASH_EQUAL); } +{SLASH} { return CARBON_SIMPLE_TOKEN(SLASH); } +{STAR_EQUAL} { return CARBON_SIMPLE_TOKEN(STAR_EQUAL); } +{STRING} { return CARBON_SIMPLE_TOKEN(STRING); } +{THEN} { return CARBON_SIMPLE_TOKEN(THEN); } +{TRUE} { return CARBON_SIMPLE_TOKEN(TRUE); } +{TYPE} { return CARBON_SIMPLE_TOKEN(TYPE); } +{UNDERSCORE} { return CARBON_SIMPLE_TOKEN(UNDERSCORE); } +{UNIMPL_EXAMPLE} { return CARBON_SIMPLE_TOKEN(UNIMPL_EXAMPLE); } +{VAR} { return CARBON_SIMPLE_TOKEN(VAR); } +{VIRTUAL} { return CARBON_SIMPLE_TOKEN(VIRTUAL); } +{WHERE} { return CARBON_SIMPLE_TOKEN(WHERE); } +{WHILE} { return CARBON_SIMPLE_TOKEN(WHILE); } /* table-end */ /* More modern Bisons provide make_EOF. */ diff --git a/explorer/syntax/parser.ypp b/explorer/syntax/parser.ypp index 14252ea3ed08..cce8e5674bb1 100644 --- a/explorer/syntax/parser.ypp +++ b/explorer/syntax/parser.ypp @@ -121,6 +121,8 @@ %type >> interface_body %type >> impl_body %type > statement +%type > assign_statement +%type assign_operator %type > if_statement %type >> optional_else %type , bool>> return_expression @@ -207,6 +209,7 @@ ADDR ALIAS AMPERSAND + AMPERSAND_EQUAL AND API ARROW @@ -217,6 +220,7 @@ BOOL BREAK CARET + CARET_EQUAL CASE CHOICE CLASS @@ -243,6 +247,7 @@ GREATER GREATER_EQUAL GREATER_GREATER + GREATER_GREATER_EQUAL IF IMPL IMPORT @@ -255,21 +260,29 @@ LESS LESS_EQUAL LESS_LESS + LESS_LESS_EQUAL LET LIBRARY MATCH MATCH_FIRST MINUS + MINUS_EQUAL + MINUS_MINUS MIX MIXIN NOT NOT_EQUAL OR + OR_EQUAL PACKAGE PERCENT + PERCENT_EQUAL PERIOD PIPE + PIPE_EQUAL PLUS + PLUS_EQUAL + PLUS_PLUS RETURN RETURNED RIGHT_CURLY_BRACE @@ -279,6 +292,8 @@ SELF SEMICOLON SLASH + SLASH_EQUAL + STAR_EQUAL STRING THEN TRUE @@ -927,8 +942,7 @@ clause_list: } ; statement: - statement_expression EQUAL expression SEMICOLON - { $$ = arena->New(context.source_loc(), $1, $3); } + assign_statement | VAR pattern SEMICOLON { $$ = arena->New( @@ -989,6 +1003,38 @@ statement: | FOR LEFT_PARENTHESIS variable_declaration IN type_expression RIGHT_PARENTHESIS block { $$ = arena->New(context.source_loc(), $3, $5, $7); } ; +assign_statement: + statement_expression assign_operator expression SEMICOLON + { $$ = arena->New(context.source_loc(), $1, $2, $3); } +| PLUS_PLUS expression SEMICOLON + { $$ = arena->New(context.source_loc(), $2, true); } +| MINUS_MINUS expression SEMICOLON + { $$ = arena->New(context.source_loc(), $2, false); } +; +assign_operator: + EQUAL + { $$ = AssignOperator::Plain; } +| PLUS_EQUAL + { $$ = AssignOperator::Add; } +| SLASH_EQUAL + { $$ = AssignOperator::Div; } +| STAR_EQUAL + { $$ = AssignOperator::Mul; } +| PERCENT_EQUAL + { $$ = AssignOperator::Mod; } +| MINUS_EQUAL + { $$ = AssignOperator::Sub; } +| AMPERSAND_EQUAL + { $$ = AssignOperator::And; } +| PIPE_EQUAL + { $$ = AssignOperator::Or; } +| CARET_EQUAL + { $$ = AssignOperator::Xor; } +| LESS_LESS_EQUAL + { $$ = AssignOperator::ShiftLeft; } +| GREATER_GREATER_EQUAL + { $$ = AssignOperator::ShiftRight; } +; if_statement: IF LEFT_PARENTHESIS expression RIGHT_PARENTHESIS block optional_else { $$ = arena->New(context.source_loc(), $3, $5, $6); } diff --git a/explorer/testdata/basic_syntax/fail_missing_var.carbon b/explorer/testdata/basic_syntax/fail_missing_var.carbon index b5679a5ef351..722b7002ead3 100644 --- a/explorer/testdata/basic_syntax/fail_missing_var.carbon +++ b/explorer/testdata/basic_syntax/fail_missing_var.carbon @@ -10,7 +10,7 @@ package ExplorerTest api; fn Main() -> i32 { // error - // CHECK:STDERR: SYNTAX ERROR: {{.*}}/explorer/testdata/basic_syntax/fail_missing_var.carbon:[[@LINE+1]]: syntax error, unexpected COLON, expecting SLASH or binary * + // CHECK:STDERR: SYNTAX ERROR: {{.*}}/explorer/testdata/basic_syntax/fail_missing_var.carbon:[[@LINE+1]]: syntax error, unexpected COLON x : i32; return 1; } diff --git a/explorer/testdata/basic_syntax/fail_var_named_self.carbon b/explorer/testdata/basic_syntax/fail_var_named_self.carbon index 1bf1c3c9ce37..849afab2526e 100644 --- a/explorer/testdata/basic_syntax/fail_var_named_self.carbon +++ b/explorer/testdata/basic_syntax/fail_var_named_self.carbon @@ -12,7 +12,7 @@ fn Main() -> i32 { // Error: can't use keyword `Self` as the name of a variable. // TODO: Current error message is unclear, better would be to say // something like: unexpected `Self`, expecting identifier - // CHECK:STDERR: SYNTAX ERROR: {{.*}}/explorer/testdata/basic_syntax/fail_var_named_self.carbon:[[@LINE+1]]: syntax error, unexpected COLON, expecting SLASH or binary * + // CHECK:STDERR: SYNTAX ERROR: {{.*}}/explorer/testdata/basic_syntax/fail_var_named_self.carbon:[[@LINE+1]]: syntax error, unexpected COLON, expecting EQUAL or SEMICOLON var Self : i32 = 0; return Self; } diff --git a/explorer/testdata/let/fail_local_named_self.carbon b/explorer/testdata/let/fail_local_named_self.carbon index 63ec9f5d5f2e..6523b4c481a8 100644 --- a/explorer/testdata/let/fail_local_named_self.carbon +++ b/explorer/testdata/let/fail_local_named_self.carbon @@ -10,7 +10,7 @@ package ExplorerTest api; fn Main() -> i32 { // Error: Can't use keyword `Self` as the name of a local. - // CHECK:STDERR: SYNTAX ERROR: {{.*}}/explorer/testdata/let/fail_local_named_self.carbon:[[@LINE+1]]: syntax error, unexpected COLON, expecting SLASH or binary * + // CHECK:STDERR: SYNTAX ERROR: {{.*}}/explorer/testdata/let/fail_local_named_self.carbon:[[@LINE+1]]: syntax error, unexpected COLON, expecting EQUAL let Self: auto = 10; return 0; } diff --git a/explorer/testdata/operators/add.carbon b/explorer/testdata/operators/add.carbon index 782a62eade55..0824fe50ba8e 100644 --- a/explorer/testdata/operators/add.carbon +++ b/explorer/testdata/operators/add.carbon @@ -5,7 +5,10 @@ // AUTOUPDATE // RUN: %{explorer-run} // RUN: %{explorer-run-trace} -// CHECK:STDOUT: result: 12 +// CHECK:STDOUT: 12 +// CHECK:STDOUT: 19 +// CHECK:STDOUT: 26 +// CHECK:STDOUT: result: 0 package ExplorerTest api; @@ -18,5 +21,10 @@ external impl A as Add { fn Main() -> i32 { var a: A = {.n = 5}; var b: A = {.n = 7}; - return (a + b).n; + a = a + b; + Print("{0}", a.n); + a += b; + Print("{0}", a.n); + Print("{0}", (a + b).n); + return 0; } diff --git a/explorer/testdata/operators/assign_builtin.carbon b/explorer/testdata/operators/assign_builtin.carbon new file mode 100644 index 000000000000..529766c54bbe --- /dev/null +++ b/explorer/testdata/operators/assign_builtin.carbon @@ -0,0 +1,27 @@ +// 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 +// +// AUTOUPDATE +// RUN: %{explorer-run} +// RUN: %{explorer-run-trace} +// CHECK:STDOUT: Before: 1 +// CHECK:STDOUT: Interface: 2 +// CHECK:STDOUT: Op: 3 +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +class C { + var n: i32; +} + +fn Main() -> i32 { + var c: C = {.n = 1}; + Print("Before: {0}", c.n); + c.(AssignWith({.n: i32}).Op)({.n = 2}); + Print("Interface: {0}", c.n); + c = {.n = 3}; + Print("Op: {0}", c.n); + return 0; +} diff --git a/explorer/testdata/operators/bit_and.carbon b/explorer/testdata/operators/bit_and.carbon index ad75dc03234a..f0ad624f9545 100644 --- a/explorer/testdata/operators/bit_and.carbon +++ b/explorer/testdata/operators/bit_and.carbon @@ -5,7 +5,10 @@ // AUTOUPDATE // RUN: %{explorer-run} // RUN: %{explorer-run-trace} -// CHECK:STDOUT: result: 1 +// CHECK:STDOUT: 5 +// CHECK:STDOUT: 4 +// CHECK:STDOUT: 0 +// CHECK:STDOUT: result: 0 package ExplorerTest api; @@ -16,7 +19,11 @@ external impl A as BitAndWith(i32) where .Result = A { } fn Main() -> i32 { - var a: A = {.n = 5}; - a = a & 1; - return a.n; + var a: A = {.n = 13}; + a = a & 7; + Print("{0}", a.n); + a &= -2; + Print("{0}", a.n); + Print("{0}", (a & 3).n); + return 0; } diff --git a/explorer/testdata/operators/bit_or.carbon b/explorer/testdata/operators/bit_or.carbon index d31372cf3e84..48d137eeff2c 100644 --- a/explorer/testdata/operators/bit_or.carbon +++ b/explorer/testdata/operators/bit_or.carbon @@ -5,7 +5,10 @@ // AUTOUPDATE // RUN: %{explorer-run} // RUN: %{explorer-run-trace} -// CHECK:STDOUT: result: 5 +// CHECK:STDOUT: 3 +// CHECK:STDOUT: 7 +// CHECK:STDOUT: 15 +// CHECK:STDOUT: result: 0 package ExplorerTest api; @@ -16,7 +19,11 @@ external impl A as BitOrWith(i32) where .Result = A { } fn Main() -> i32 { - var a: A = {.n = 4}; - a = a | 1; - return a.n; + var a: A = {.n = 1}; + a = a | 2; + Print("{0}", a.n); + a |= 5; + Print("{0}", a.n); + Print("{0}", (a | 12).n); + return 0; } diff --git a/explorer/testdata/operators/bit_xor.carbon b/explorer/testdata/operators/bit_xor.carbon index 4139886cb75b..9e88b2b3092a 100644 --- a/explorer/testdata/operators/bit_xor.carbon +++ b/explorer/testdata/operators/bit_xor.carbon @@ -5,7 +5,10 @@ // AUTOUPDATE // RUN: %{explorer-run} // RUN: %{explorer-run-trace} -// CHECK:STDOUT: result: 4 +// CHECK:STDOUT: 6 +// CHECK:STDOUT: 0 +// CHECK:STDOUT: -1 +// CHECK:STDOUT: result: 0 package ExplorerTest api; @@ -17,6 +20,10 @@ external impl A as BitXorWith(i32) where .Result = A { fn Main() -> i32 { var a: A = {.n = 5}; - a = a ^ 1; - return a.n; + a = a ^ 3; + Print("{0}", a.n); + a ^= 6; + Print("{0}", a.n); + Print("{0}", (a ^ -1).n); + return 0; } diff --git a/explorer/testdata/operators/div.carbon b/explorer/testdata/operators/div.carbon index 924b86a808b6..98c32d455bf7 100644 --- a/explorer/testdata/operators/div.carbon +++ b/explorer/testdata/operators/div.carbon @@ -5,7 +5,10 @@ // AUTOUPDATE // RUN: %{explorer-run} // RUN: %{explorer-run-trace} -// CHECK:STDOUT: result: 2 +// CHECK:STDOUT: 6 +// CHECK:STDOUT: 3 +// CHECK:STDOUT: 1 +// CHECK:STDOUT: result: 0 package ExplorerTest api; @@ -16,7 +19,11 @@ external impl A as DivWith(i32) where .Result = A { } fn Main() -> i32 { - var a: A = {.n = 8}; + var a: A = {.n = 19}; a = a / 3; - return a.n; + Print("{0}", a.n); + a /= 2; + Print("{0}", a.n); + Print("{0}", (a / 2).n); + return 0; } diff --git a/explorer/testdata/operators/inc_dec.carbon b/explorer/testdata/operators/inc_dec.carbon new file mode 100644 index 000000000000..e759ff9f87b0 --- /dev/null +++ b/explorer/testdata/operators/inc_dec.carbon @@ -0,0 +1,48 @@ +// 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 +// +// AUTOUPDATE +// RUN: %{explorer-run} +// RUN: %{explorer-run-trace} +// CHECK:STDOUT: 6 +// CHECK:STDOUT: 5 +// CHECK:STDOUT: 6 +// CHECK:STDOUT: 5 +// CHECK:STDOUT: 6 +// CHECK:STDOUT: 5 +// CHECK:STDOUT: 6 +// CHECK:STDOUT: 5 +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +class A { var n: i32; } + +external impl A as Inc { + fn Op[addr self: Self*]() { ++self->n; } +} +external impl A as Dec { + fn Op[addr self: Self*]() { --self->n; } +} + +fn Main() -> i32 { + var a: A = {.n = 5}; + ++a.n; + Print("{0}", a.n); + --a.n; + Print("{0}", a.n); + ++a; + Print("{0}", a.n); + --a; + Print("{0}", a.n); + a.n.(Inc.Op)(); + Print("{0}", a.n); + a.n.(Dec.Op)(); + Print("{0}", a.n); + a.(Inc.Op)(); + Print("{0}", a.n); + a.(Dec.Op)(); + Print("{0}", a.n); + return 0; +} diff --git a/explorer/testdata/operators/mod.carbon b/explorer/testdata/operators/mod.carbon index 643254a9bcf1..cc7d911235e0 100644 --- a/explorer/testdata/operators/mod.carbon +++ b/explorer/testdata/operators/mod.carbon @@ -5,7 +5,10 @@ // AUTOUPDATE // RUN: %{explorer-run} // RUN: %{explorer-run-trace} -// CHECK:STDOUT: result: 1 +// CHECK:STDOUT: 7 +// CHECK:STDOUT: 3 +// CHECK:STDOUT: 0 +// CHECK:STDOUT: result: 0 package ExplorerTest api; @@ -16,7 +19,11 @@ external impl A as ModWith(i32) where .Result = A { } fn Main() -> i32 { - var a: A = {.n = 5}; - a = a % 2; - return a.n; + var a: A = {.n = 15}; + a = a % 8; + Print("{0}", a.n); + a %= 4; + Print("{0}", a.n); + Print("{0}", (a % 3).n); + return 0; } diff --git a/explorer/testdata/operators/mul.carbon b/explorer/testdata/operators/mul.carbon index 314c39edd5c6..c1ae845b1cad 100644 --- a/explorer/testdata/operators/mul.carbon +++ b/explorer/testdata/operators/mul.carbon @@ -5,7 +5,10 @@ // AUTOUPDATE // RUN: %{explorer-run} // RUN: %{explorer-run-trace} -// CHECK:STDOUT: result: 10 +// CHECK:STDOUT: 10 +// CHECK:STDOUT: 30 +// CHECK:STDOUT: 210 +// CHECK:STDOUT: result: 0 package ExplorerTest api; @@ -18,5 +21,9 @@ external impl A as MulWith(i32) where .Result = A { fn Main() -> i32 { var a: A = {.n = 5}; a = a * 2; - return a.n; + Print("{0}", a.n); + a *= 3; + Print("{0}", a.n); + Print("{0}", (a * 7).n); + return 0; } diff --git a/explorer/testdata/operators/shift.carbon b/explorer/testdata/operators/shift.carbon index 4aa182a8fd87..be1ad0826738 100644 --- a/explorer/testdata/operators/shift.carbon +++ b/explorer/testdata/operators/shift.carbon @@ -5,6 +5,8 @@ // AUTOUPDATE // RUN: %{explorer-run} // RUN: %{explorer-run-trace} +// CHECK:STDOUT: 8 +// CHECK:STDOUT: 4 // CHECK:STDOUT: result: 0 package ExplorerTest api; @@ -21,5 +23,11 @@ fn Main() -> i32 { if (not (-1 >> 1 == -1)) { return 9; } if (not (-2 >> 1 == -1)) { return 10; } + var n: i32 = 1; + n <<= 3; + Print("{0}", n); + n >>= 1; + Print("{0}", n); + return 0; } diff --git a/explorer/testdata/operators/sub.carbon b/explorer/testdata/operators/sub.carbon index 531ef0e43a4e..1f33cbb0cd88 100644 --- a/explorer/testdata/operators/sub.carbon +++ b/explorer/testdata/operators/sub.carbon @@ -5,7 +5,10 @@ // AUTOUPDATE // RUN: %{explorer-run} // RUN: %{explorer-run-trace} -// CHECK:STDOUT: result: 4 +// CHECK:STDOUT: 4 +// CHECK:STDOUT: 3 +// CHECK:STDOUT: 2 +// CHECK:STDOUT: result: 0 package ExplorerTest api; @@ -18,5 +21,9 @@ external impl A as SubWith(i32) where .Result = A { fn Main() -> i32 { var a: A = {.n = 5}; a = a - 1; - return a.n; + Print("{0}", a.n); + a -= 1; + Print("{0}", a.n); + Print("{0}", (a - 1).n); + return 0; } diff --git a/explorer/testdata/struct/fail_assign_different_types.carbon b/explorer/testdata/struct/fail_assign_different_types.carbon index e15cd9a82342..27f59a1c75b8 100644 --- a/explorer/testdata/struct/fail_assign_different_types.carbon +++ b/explorer/testdata/struct/fail_assign_different_types.carbon @@ -10,7 +10,7 @@ package ExplorerTest api; fn Main() -> i32 { var p: auto = {.x = 0, .y = 0}; - // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/struct/fail_assign_different_types.carbon:[[@LINE+1]]: type error in assignment: '{.y: i32}' is not implicitly convertible to '{.x: i32, .y: i32}' + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/struct/fail_assign_different_types.carbon:[[@LINE+1]]: could not find implementation of interface AssignWith(U = {.y: i32}) for {.x: i32, .y: i32} p = {.y = 0}; return 0; } diff --git a/explorer/testdata/unformed/static/fail_compound_assign.carbon b/explorer/testdata/unformed/static/fail_compound_assign.carbon new file mode 100644 index 000000000000..9fb25dbc5935 --- /dev/null +++ b/explorer/testdata/unformed/static/fail_compound_assign.carbon @@ -0,0 +1,16 @@ +// 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 +// +// AUTOUPDATE +// RUN: %{not} %{explorer-run} +// RUN: %{not} %{explorer-run-trace} + +package ExplorerTest api; + +fn Main() -> i32 { + var x: i32; + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/unformed/static/fail_compound_assign.carbon:[[@LINE+1]]: use of uninitialized variable x + x += 1; + return x; +} diff --git a/explorer/testdata/unformed/static/fail_increment.carbon b/explorer/testdata/unformed/static/fail_increment.carbon new file mode 100644 index 000000000000..25860aa5f292 --- /dev/null +++ b/explorer/testdata/unformed/static/fail_increment.carbon @@ -0,0 +1,16 @@ +// 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 +// +// AUTOUPDATE +// RUN: %{not} %{explorer-run} +// RUN: %{not} %{explorer-run-trace} + +package ExplorerTest api; + +fn Main() -> i32 { + var x: i32; + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/unformed/static/fail_increment.carbon:[[@LINE+1]]: use of uninitialized variable x + ++x; + return x; +}