diff --git a/executable_semantics/ast/expression.cpp b/executable_semantics/ast/expression.cpp index 2d2d57837d18..f79aa261246b 100644 --- a/executable_semantics/ast/expression.cpp +++ b/executable_semantics/ast/expression.cpp @@ -64,10 +64,11 @@ static void PrintOp(llvm::raw_ostream& out, Operator op) { } static void PrintFields(llvm::raw_ostream& out, - const std::vector& fields) { + const std::vector& fields, + std::string_view separator) { llvm::ListSeparator sep; for (const auto& field : fields) { - out << sep << field.name << " = " << *field.expression; + out << sep << "." << field.name << separator << *field.expression; } } @@ -85,9 +86,19 @@ void Expression::Print(llvm::raw_ostream& out) const { } case Expression::Kind::TupleLiteral: out << "("; - PrintFields(out, cast(*this).Fields()); + PrintFields(out, cast(*this).Fields(), " = "); out << ")"; break; + case Expression::Kind::StructLiteral: + out << "{"; + PrintFields(out, cast(*this).fields(), " = "); + out << "}"; + break; + case Expression::Kind::StructTypeLiteral: + out << "{"; + PrintFields(out, cast(*this).fields(), ": "); + out << "}"; + break; case Expression::Kind::IntLiteral: out << cast(*this).Val(); break; diff --git a/executable_semantics/ast/expression.h b/executable_semantics/ast/expression.h index ffe0a747849a..6e48b9bce082 100644 --- a/executable_semantics/ast/expression.h +++ b/executable_semantics/ast/expression.h @@ -35,6 +35,8 @@ class Expression { StringLiteral, StringTypeLiteral, TupleLiteral, + StructLiteral, + StructTypeLiteral, TypeTypeLiteral, IdentifierExpression, IntrinsicExpression, @@ -230,6 +232,57 @@ class TupleLiteral : public Expression { std::vector fields; }; +// A non-empty literal value of a struct type. +// +// It can't be empty because the syntax `{}` is a struct type literal as well +// as a literal value of that type, so for consistency we always represent it +// as a StructTypeLiteral rather than let it oscillate unpredictably between +// the two. +class StructLiteral : public Expression { + public: + explicit StructLiteral(SourceLocation loc, + std::vector fields) + : Expression(Kind::StructLiteral, loc), fields_(std::move(fields)) { + CHECK(!fields_.empty()) + << "`{}` is represented as a StructTypeLiteral, not a StructLiteral."; + } + + static auto classof(const Expression* exp) -> bool { + return exp->Tag() == Kind::StructLiteral; + } + + auto fields() const -> const std::vector& { + return fields_; + } + + private: + std::vector fields_; +}; + +// A literal representing a struct type. +// +// Code that handles this type may sometimes need to have special-case handling +// for `{}`, which is a struct value in addition to being a struct type. +class StructTypeLiteral : public Expression { + public: + explicit StructTypeLiteral(SourceLocation loc) : StructTypeLiteral(loc, {}) {} + + explicit StructTypeLiteral(SourceLocation loc, + std::vector fields) + : Expression(Kind::StructTypeLiteral, loc), fields_(std::move(fields)) {} + + static auto classof(const Expression* exp) -> bool { + return exp->Tag() == Kind::StructTypeLiteral; + } + + auto fields() const -> const std::vector& { + return fields_; + } + + private: + std::vector fields_; +}; + class PrimitiveOperatorExpression : public Expression { public: explicit PrimitiveOperatorExpression( diff --git a/executable_semantics/interpreter/interpreter.cpp b/executable_semantics/interpreter/interpreter.cpp index 2cd6279ba092..40279d87bc93 100644 --- a/executable_semantics/interpreter/interpreter.cpp +++ b/executable_semantics/interpreter/interpreter.cpp @@ -141,8 +141,8 @@ void Interpreter::InitEnv(const Declaration& d, Env* env) { } } } - auto st = arena->New(class_def.name(), std::move(fields), - std::move(methods)); + auto st = arena->New( + class_def.name(), std::move(fields), std::move(methods)); auto a = heap.AllocateValue(st); env->Set(class_def.name(), a); break; @@ -211,6 +211,18 @@ auto Interpreter::CreateTuple(Nonnull act, return arena->New(std::move(elements)); } +auto Interpreter::CreateStruct(const std::vector& fields, + const std::vector>& values) + -> Nonnull { + CHECK(fields.size() == values.size()); + std::vector elements; + for (size_t i = 0; i < fields.size(); ++i) { + elements.push_back({.name = fields[i].name, .value = values[i]}); + } + + return arena->New(std::move(elements)); +} + auto Interpreter::PatternMatch(Nonnull p, Nonnull v, SourceLocation loc) -> std::optional { switch (p->Tag()) { @@ -255,6 +267,24 @@ auto Interpreter::PatternMatch(Nonnull p, Nonnull v, default: FATAL() << "expected a tuple value in pattern, not " << *v; } + case Value::Kind::StructValue: { + const auto& p_struct = cast(*p); + const auto& v_struct = cast(*v); + CHECK(p_struct.elements().size() == v_struct.elements().size()); + Env values(arena); + for (size_t i = 0; i < p_struct.elements().size(); ++i) { + CHECK(p_struct.elements()[i].name == v_struct.elements()[i].name); + std::optional matches = PatternMatch( + p_struct.elements()[i].value, v_struct.elements()[i].value, loc); + if (!matches) { + return std::nullopt; + } + for (const auto& [name, value] : *matches) { + values.Set(name, value); + } + } + return values; + } case Value::Kind::AlternativeValue: switch (v->Tag()) { case Value::Kind::AlternativeValue: { @@ -426,6 +456,8 @@ auto Interpreter::StepLvalue() -> Transition { return Done{CreateTuple(act, exp)}; } } + case Expression::Kind::StructLiteral: + case Expression::Kind::StructTypeLiteral: case Expression::Kind::IntLiteral: case Expression::Kind::BoolLiteral: case Expression::Kind::CallExpression: @@ -492,6 +524,29 @@ auto Interpreter::StepExp() -> Transition { return Done{CreateTuple(act, exp)}; } } + case Expression::Kind::StructLiteral: { + const auto& literal = cast(*exp); + if (act->Pos() < static_cast(literal.fields().size())) { + Nonnull elt = + literal.fields()[act->Pos()].expression; + return Spawn{arena->New(elt)}; + } else { + return Done{CreateStruct(literal.fields(), act->Results())}; + } + } + case Expression::Kind::StructTypeLiteral: { + const auto& struct_type = cast(*exp); + if (act->Pos() < static_cast(struct_type.fields().size())) { + return Spawn{arena->New( + struct_type.fields()[act->Pos()].expression)}; + } else { + VarValues fields; + for (size_t i = 0; i < struct_type.fields().size(); ++i) { + fields.push_back({struct_type.fields()[i].name, act->Results()[i]}); + } + return Done{arena->New(std::move(fields))}; + } + } case Expression::Kind::FieldAccessExpression: { const auto& access = cast(*exp); if (act->Pos() == 0) { @@ -548,10 +603,10 @@ auto Interpreter::StepExp() -> Transition { // { { v2 :: v1([]) :: C, E, F} :: S, H} // -> { {C',E',F'} :: {C, E, F} :: S, H} switch (act->Results()[0]->Tag()) { - case Value::Kind::ClassType: { + case Value::Kind::NominalClassType: { Nonnull arg = CopyVal(arena, act->Results()[1], exp->SourceLoc()); - return Done{arena->New(act->Results()[0], arg)}; + return Done{arena->New(act->Results()[0], arg)}; } case Value::Kind::AlternativeConstructorValue: { const auto& alt = diff --git a/executable_semantics/interpreter/interpreter.h b/executable_semantics/interpreter/interpreter.h index a47f84238cf6..9cb1203defe8 100644 --- a/executable_semantics/interpreter/interpreter.h +++ b/executable_semantics/interpreter/interpreter.h @@ -138,6 +138,9 @@ class Interpreter { auto CreateTuple(Nonnull act, Nonnull exp) -> Nonnull; + auto CreateStruct(const std::vector& fields, + const std::vector>& values) + -> Nonnull; auto EvalPrim(Operator op, const std::vector>& args, SourceLocation loc) -> Nonnull; diff --git a/executable_semantics/interpreter/type_checker.cpp b/executable_semantics/interpreter/type_checker.cpp index 477b217ce299..8d600733a51f 100644 --- a/executable_semantics/interpreter/type_checker.cpp +++ b/executable_semantics/interpreter/type_checker.cpp @@ -84,8 +84,16 @@ auto TypeChecker::ReifyType(Nonnull t, SourceLocation loc) } return arena->New(loc, args); } - case Value::Kind::ClassType: - return arena->New(loc, cast(*t).Name()); + case Value::Kind::StructType: { + std::vector args; + for (const auto& [name, type] : cast(*t).fields()) { + args.push_back(FieldInitializer(name, ReifyType(type, loc))); + } + return arena->New(loc, args); + } + case Value::Kind::NominalClassType: + return arena->New( + loc, cast(*t).Name()); case Value::Kind::ChoiceType: return arena->New(loc, cast(*t).Name()); case Value::Kind::PointerType: @@ -109,6 +117,7 @@ auto TypeChecker::ReifyType(Nonnull t, SourceLocation loc) case Value::Kind::PointerValue: case Value::Kind::StringValue: case Value::Kind::StructValue: + case Value::Kind::NominalClassValue: FATAL() << "expected a type, not " << *t; } } @@ -153,6 +162,27 @@ static auto ArgumentDeduction(SourceLocation loc, TypeEnv deduced, } return deduced; } + case Value::Kind::StructType: { + if (arg->Tag() != Value::Kind::StructType) { + ExpectType(loc, "argument deduction", param, arg); + } + const auto& param_struct = cast(*param); + const auto& arg_struct = cast(*arg); + if (param_struct.fields().size() != arg_struct.fields().size()) { + ExpectType(loc, "argument deduction", param, arg); + } + for (size_t i = 0; i < param_struct.fields().size(); ++i) { + if (param_struct.fields()[i].first != arg_struct.fields()[i].first) { + FATAL_COMPILATION_ERROR(loc) + << "mismatch in field names, " << param_struct.fields()[i].first + << " != " << arg_struct.fields()[i].first; + } + deduced = + ArgumentDeduction(loc, deduced, param_struct.fields()[i].second, + arg_struct.fields()[i].second); + } + return deduced; + } case Value::Kind::FunctionType: { if (arg->Tag() != Value::Kind::FunctionType) { ExpectType(loc, "argument deduction", param, arg); @@ -178,7 +208,7 @@ static auto ArgumentDeduction(SourceLocation loc, TypeEnv deduced, } // For the following cases, we check for type equality. case Value::Kind::ContinuationType: - case Value::Kind::ClassType: + case Value::Kind::NominalClassType: case Value::Kind::ChoiceType: case Value::Kind::IntType: case Value::Kind::BoolType: @@ -192,6 +222,7 @@ static auto ArgumentDeduction(SourceLocation loc, TypeEnv deduced, case Value::Kind::FunctionValue: case Value::Kind::PointerValue: case Value::Kind::StructValue: + case Value::Kind::NominalClassValue: case Value::Kind::AlternativeValue: case Value::Kind::BindingPlaceholderValue: case Value::Kind::AlternativeConstructorValue: @@ -221,6 +252,14 @@ auto TypeChecker::Substitute(TypeEnv dict, Nonnull type) } return arena->New(elts); } + case Value::Kind::StructType: { + VarValues fields; + for (const auto& [name, value] : cast(*type).fields()) { + auto new_type = Substitute(dict, value); + fields.push_back({name, new_type}); + } + return arena->New(std::move(fields)); + } case Value::Kind::FunctionType: { const auto& fn_type = cast(*type); auto param = Substitute(dict, fn_type.Param()); @@ -236,7 +275,7 @@ auto TypeChecker::Substitute(TypeEnv dict, Nonnull type) case Value::Kind::IntType: case Value::Kind::BoolType: case Value::Kind::TypeType: - case Value::Kind::ClassType: + case Value::Kind::NominalClassType: case Value::Kind::ChoiceType: case Value::Kind::ContinuationType: case Value::Kind::StringType: @@ -247,6 +286,7 @@ auto TypeChecker::Substitute(TypeEnv dict, Nonnull type) case Value::Kind::FunctionValue: case Value::Kind::PointerValue: case Value::Kind::StructValue: + case Value::Kind::NominalClassValue: case Value::Kind::AlternativeValue: case Value::Kind::BindingPlaceholderValue: case Value::Kind::AlternativeConstructorValue: @@ -305,13 +345,64 @@ auto TypeChecker::TypeCheckExp(Nonnull e, TypeEnv types, auto tuple_t = arena->New(std::move(arg_types)); return TCExpression(tuple_e, tuple_t, new_types); } + case Expression::Kind::StructLiteral: { + std::vector new_args; + VarValues arg_types; + auto new_types = types; + for (const auto& arg : cast(*e).fields()) { + auto arg_res = TypeCheckExp(arg.expression, new_types, values); + new_types = arg_res.types; + new_args.push_back(FieldInitializer(arg.name, arg_res.exp)); + arg_types.push_back({arg.name, arg_res.type}); + } + auto new_e = arena->New(e->SourceLoc(), new_args); + auto type = arena->New(std::move(arg_types)); + return TCExpression(new_e, type, new_types); + } + case Expression::Kind::StructTypeLiteral: { + const auto& struct_type = cast(*e); + std::vector new_args; + auto new_types = types; + for (const auto& arg : struct_type.fields()) { + auto arg_res = TypeCheckExp(arg.expression, new_types, values); + new_types = arg_res.types; + Nonnull type = interpreter.InterpExp(values, arg_res.exp); + new_args.push_back( + FieldInitializer(arg.name, ReifyType(type, e->SourceLoc()))); + } + auto new_e = arena->New(e->SourceLoc(), new_args); + Nonnull type; + if (struct_type.fields().empty()) { + // `{}` is the type of `{}`, just as `()` is the type of `()`. + // This applies only if there are no fields, because (unlike with + // tuples) non-empty struct types are syntactically disjoint + // from non-empty struct values. + type = arena->New(); + } else { + type = arena->New(); + } + return TCExpression(new_e, type, new_types); + } case Expression::Kind::FieldAccessExpression: { auto& access = cast(*e); auto res = TypeCheckExp(access.Aggregate(), types, values); auto t = res.type; switch (t->Tag()) { - case Value::Kind::ClassType: { - const auto& t_class = cast(*t); + case Value::Kind::StructType: { + const auto& struct_type = cast(*t); + for (const auto& [field_name, field_type] : struct_type.fields()) { + if (access.Field() == field_name) { + Nonnull new_e = arena->New( + access.SourceLoc(), res.exp, access.Field()); + return TCExpression(new_e, field_type, res.types); + } + } + FATAL_COMPILATION_ERROR(access.SourceLoc()) + << "struct " << struct_type << " does not have a field named " + << access.Field(); + } + case Value::Kind::NominalClassType: { + const auto& t_class = cast(*t); // Search for a field for (auto& field : t_class.Fields()) { if (access.Field() == field.first) { @@ -951,8 +1042,8 @@ auto TypeChecker::TypeOfClassDef(const ClassDefinition* sd, TypeEnv /*types*/, } } } - return arena->New(sd->name(), std::move(fields), - std::move(methods)); + return arena->New(sd->name(), std::move(fields), + std::move(methods)); } static auto GetName(const Declaration& d) -> const std::string& { @@ -1042,7 +1133,7 @@ void TypeChecker::TopLevel(Nonnull d, TypeCheckContext* tops) { tops->values.Set(class_def.name(), a); // Is this obsolete? std::vector field_types; for (const auto& [field_name, field_value] : - cast(*st).Fields()) { + cast(*st).Fields()) { field_types.push_back({.name = field_name, .value = field_value}); } auto fun_ty = arena->New( diff --git a/executable_semantics/interpreter/value.cpp b/executable_semantics/interpreter/value.cpp index 338c4f385cf4..a13d972f3410 100644 --- a/executable_semantics/interpreter/value.cpp +++ b/executable_semantics/interpreter/value.cpp @@ -44,6 +44,16 @@ auto FieldsEqual(const VarValues& ts1, const VarValues& ts2) -> bool { } } +auto StructValue::FindField(const std::string& name) const + -> std::optional> { + for (const TupleElement& element : elements_) { + if (element.name == name) { + return element.value; + } + } + return std::nullopt; +} + auto TupleValue::FindField(const std::string& name) const -> std::optional> { for (const TupleElement& element : elements) { @@ -62,7 +72,15 @@ auto GetMember(Nonnull arena, Nonnull v, switch (v->Tag()) { case Value::Kind::StructValue: { std::optional> field = - cast(*cast(*v).Inits()).FindField(f); + cast(*v).FindField(f); + if (field == std::nullopt) { + FATAL_RUNTIME_ERROR(loc) << "member " << f << " not in " << *v; + } + return *field; + } + case Value::Kind::NominalClassValue: { + std::optional> field = + cast(*cast(*v).Inits()).FindField(f); if (field == std::nullopt) { FATAL_RUNTIME_ERROR(loc) << "member " << f << " not in " << *v; } @@ -111,8 +129,22 @@ auto SetFieldImpl(Nonnull arena, Nonnull value, } switch (value->Tag()) { case Value::Kind::StructValue: { - return SetFieldImpl(arena, cast(*value).Inits(), path_begin, - path_end, field_value, loc); + std::vector elements = cast(*value).elements(); + auto it = std::find_if(elements.begin(), elements.end(), + [path_begin](const TupleElement& element) { + return element.name == *path_begin; + }); + if (it == elements.end()) { + FATAL_RUNTIME_ERROR(loc) + << "field " << *path_begin << " not in " << *value; + } + it->value = SetFieldImpl(arena, it->value, path_begin + 1, path_end, + field_value, loc); + return arena->New(elements); + } + case Value::Kind::NominalClassValue: { + return SetFieldImpl(arena, cast(*value).Inits(), + path_begin, path_end, field_value, loc); } case Value::Kind::TupleValue: { std::vector elements = cast(*value).Elements(); @@ -167,8 +199,18 @@ void Value::Print(llvm::raw_ostream& out) const { break; } case Value::Kind::StructValue: { - const auto& s = cast(*this); - out << cast(*s.Type()).Name() << *s.Inits(); + const auto& struct_val = cast(*this); + out << "{"; + llvm::ListSeparator sep; + for (const TupleElement& element : struct_val.elements()) { + out << sep << "." << element.name << " = " << *element.value; + } + out << "}"; + break; + } + case Value::Kind::NominalClassValue: { + const auto& s = cast(*this); + out << cast(*s.Type()).Name() << *s.Inits(); break; } case Value::Kind::TupleValue: { @@ -228,8 +270,17 @@ void Value::Print(llvm::raw_ostream& out) const { out << *fn_type.Param() << " -> " << *fn_type.Ret(); break; } - case Value::Kind::ClassType: - out << "struct " << cast(*this).Name(); + case Value::Kind::StructType: { + out << "{"; + llvm::ListSeparator sep; + for (const auto& [name, type] : cast(*this).fields()) { + out << sep << "." << name << ": " << *type; + } + out << "}"; + break; + } + case Value::Kind::NominalClassType: + out << "class " << cast(*this).Name(); break; case Value::Kind::ChoiceType: out << "choice " << cast(*this).Name(); @@ -274,9 +325,17 @@ auto CopyVal(Nonnull arena, Nonnull val, return arena->New(alt.AltName(), alt.ChoiceName(), arg); } case Value::Kind::StructValue: { - const auto& s = cast(*val); + std::vector elements; + for (const TupleElement& element : cast(*val).elements()) { + elements.push_back({.name = element.name, + .value = CopyVal(arena, element.value, loc)}); + } + return arena->New(std::move(elements)); + } + case Value::Kind::NominalClassValue: { + const auto& s = cast(*val); Nonnull inits = CopyVal(arena, s.Inits(), loc); - return arena->New(s.Type(), inits); + return arena->New(s.Type(), inits); } case Value::Kind::IntValue: return arena->New(cast(*val).Val()); @@ -315,8 +374,15 @@ auto CopyVal(Nonnull arena, Nonnull val, return arena->New(); case Value::Kind::StringValue: return arena->New(cast(*val).Val()); + case Value::Kind::StructType: { + VarValues fields; + for (const auto& [name, type] : cast(*val).fields()) { + fields.push_back({name, CopyVal(arena, type, loc)}); + } + return arena->New(fields); + } case Value::Kind::VariableType: - case Value::Kind::ClassType: + case Value::Kind::NominalClassType: case Value::Kind::ChoiceType: case Value::Kind::BindingPlaceholderValue: case Value::Kind::AlternativeConstructorValue: @@ -339,8 +405,24 @@ auto TypeEqual(Nonnull t1, Nonnull t2) -> bool { return TypeEqual(fn1.Param(), fn2.Param()) && TypeEqual(fn1.Ret(), fn2.Ret()); } - case Value::Kind::ClassType: - return cast(*t1).Name() == cast(*t2).Name(); + case Value::Kind::StructType: { + const auto& struct1 = cast(*t1); + const auto& struct2 = cast(*t2); + if (struct1.fields().size() != struct2.fields().size()) { + return false; + } + for (size_t i = 0; i < struct1.fields().size(); ++i) { + if (struct1.fields()[i].first != struct2.fields()[i].first || + !TypeEqual(struct1.fields()[i].second, + struct2.fields()[i].second)) { + return false; + } + } + return true; + } + case Value::Kind::NominalClassType: + return cast(*t1).Name() == + cast(*t2).Name(); case Value::Kind::ChoiceType: return cast(*t1).Name() == cast(*t2).Name(); case Value::Kind::TupleValue: { @@ -420,6 +502,9 @@ auto ValueEqual(Nonnull v1, Nonnull v2, case Value::Kind::TupleValue: return FieldsValueEqual(cast(*v1).Elements(), cast(*v2).Elements(), loc); + case Value::Kind::StructValue: + return FieldsValueEqual(cast(*v1).elements(), + cast(*v2).elements(), loc); case Value::Kind::StringValue: return cast(*v1).Val() == cast(*v2).Val(); case Value::Kind::IntType: @@ -428,13 +513,14 @@ auto ValueEqual(Nonnull v1, Nonnull v2, case Value::Kind::FunctionType: case Value::Kind::PointerType: case Value::Kind::AutoType: - case Value::Kind::ClassType: + case Value::Kind::StructType: + case Value::Kind::NominalClassType: case Value::Kind::ChoiceType: case Value::Kind::ContinuationType: case Value::Kind::VariableType: case Value::Kind::StringType: return TypeEqual(v1, v2); - case Value::Kind::StructValue: + case Value::Kind::NominalClassValue: case Value::Kind::AlternativeValue: case Value::Kind::BindingPlaceholderValue: case Value::Kind::AlternativeConstructorValue: diff --git a/executable_semantics/interpreter/value.h b/executable_semantics/interpreter/value.h index 4c63aa62b838..c079406f1f52 100644 --- a/executable_semantics/interpreter/value.h +++ b/executable_semantics/interpreter/value.h @@ -37,6 +37,7 @@ class Value { PointerValue, BoolValue, StructValue, + NominalClassValue, AlternativeValue, TupleValue, IntType, @@ -45,7 +46,8 @@ class Value { FunctionType, PointerType, AutoType, - ClassType, + StructType, + NominalClassType, ChoiceType, ContinuationType, // The type of a continuation. VariableType, // e.g., generic type parameters. @@ -92,7 +94,11 @@ auto FindInVarValues(const std::string& field, const VarValues& inits) -> std::optional>; auto FieldsEqual(const VarValues& ts1, const VarValues& ts2) -> bool; -// A TupleElement represents the value of a single tuple field. +// A TupleElement represents the value of a single tuple or struct field. +// +// TODO(geoffromer): Rename this, and look for ways to eliminate duplication +// among TupleElement, VarValues::value_type, FieldInitializer, +// TuplePattern::Field, and any similar types. struct TupleElement { // The field name. std::string name; @@ -173,16 +179,48 @@ class BoolValue : public Value { bool val; }; -// A function value. +// A non-empty value of a struct type. +// +// It can't be empty because `{}` is a struct type as well as a value of that +// type, so for consistency we always represent it as a StructType rather than +// let it oscillate unpredictably between the two. However, this means code +// that handles StructValue instances may also need to be able to handle +// StructType instances. class StructValue : public Value { public: - StructValue(Nonnull type, Nonnull inits) - : Value(Kind::StructValue), type(type), inits(inits) {} + explicit StructValue(std::vector elements) + : Value(Kind::StructValue), elements_(std::move(elements)) { + CHECK(!elements_.empty()) + << "`{}` is represented as a StructType, not a StructValue."; + } static auto classof(const Value* value) -> bool { return value->Tag() == Kind::StructValue; } + auto elements() const -> const std::vector& { + return elements_; + } + + // Returns the value of the field named `name` in this struct, or + // nullopt if there is no such field. + auto FindField(const std::string& name) const + -> std::optional>; + + private: + std::vector elements_; +}; + +// A value of a nominal class type. +class NominalClassValue : public Value { + public: + NominalClassValue(Nonnull type, Nonnull inits) + : Value(Kind::NominalClassValue), type(type), inits(inits) {} + + static auto classof(const Value* value) -> bool { + return value->Tag() == Kind::NominalClassValue; + } + auto Type() const -> Nonnull { return type; } auto Inits() const -> Nonnull { return inits; } @@ -365,16 +403,37 @@ class AutoType : public Value { }; // A struct type. -class ClassType : public Value { +// +// Code that handles this type may sometimes need to have special-case handling +// for `{}`, which is a struct value in addition to being a struct type. +class StructType : public Value { public: - ClassType(std::string name, VarValues fields, VarValues methods) - : Value(Kind::ClassType), + StructType() : StructType(VarValues{}) {} + + explicit StructType(VarValues fields) + : Value(Kind::StructType), fields_(std::move(fields)) {} + + static auto classof(const Value* value) -> bool { + return value->Tag() == Kind::StructType; + } + + auto fields() const -> const VarValues& { return fields_; } + + private: + VarValues fields_; +}; + +// A class type. +class NominalClassType : public Value { + public: + NominalClassType(std::string name, VarValues fields, VarValues methods) + : Value(Kind::NominalClassType), name(std::move(name)), fields(std::move(fields)), methods(std::move(methods)) {} static auto classof(const Value* value) -> bool { - return value->Tag() == Kind::ClassType; + return value->Tag() == Kind::NominalClassType; } auto Name() const -> const std::string& { return name; } diff --git a/executable_semantics/syntax/parser.ypp b/executable_semantics/syntax/parser.ypp index 9ad33617daf3..7d2ed8804d53 100644 --- a/executable_semantics/syntax/parser.ypp +++ b/executable_semantics/syntax/parser.ypp @@ -106,7 +106,9 @@ %type > if_statement %type >> optional_else %type , bool>> return_expression +%type > nonempty_block %type > block +%type > nonempty_statement_list %type >> statement_list %type > expression %type generic_binding @@ -116,6 +118,10 @@ %type > non_expression_pattern %type , bool>> return_type %type > paren_expression +%type > struct_literal +%type > struct_literal_contents +%type > struct_type_literal +%type > struct_type_literal_contents %type > tuple %type > binding_lhs %type > variable_declaration @@ -299,6 +305,8 @@ expression: | CONTINUATION_TYPE { $$ = arena->New(context.SourceLoc()); } | paren_expression { $$ = $1; } +| struct_literal { $$ = $1; } +| struct_type_literal { $$ = $1; } | expression EQUAL_EQUAL expression { $$ = arena->New( @@ -415,6 +423,40 @@ paren_expression_contents: } ; +struct_literal: + LEFT_CURLY_BRACE struct_literal_contents RIGHT_CURLY_BRACE + { $$ = arena->New(context.SourceLoc(), $2); } +| LEFT_CURLY_BRACE struct_literal_contents COMMA RIGHT_CURLY_BRACE + { $$ = arena->New(context.SourceLoc(), $2); } +; +struct_literal_contents: + designator EQUAL expression + { $$ = {FieldInitializer($1, $3)}; } +| struct_literal_contents COMMA designator EQUAL expression + { + $$ = $1; + $$.push_back(FieldInitializer($3, $5)); + } +; + +struct_type_literal: + LEFT_CURLY_BRACE RIGHT_CURLY_BRACE + { $$ = arena->New(context.SourceLoc()); } +| LEFT_CURLY_BRACE struct_type_literal_contents RIGHT_CURLY_BRACE + { $$ = arena->New(context.SourceLoc(), $2); } +| LEFT_CURLY_BRACE struct_type_literal_contents COMMA RIGHT_CURLY_BRACE + { $$ = arena->New(context.SourceLoc(), $2); } +; +struct_type_literal_contents: + designator COLON expression + { $$ = {FieldInitializer($1, $3)}; } +| struct_type_literal_contents COMMA designator COLON expression + { + $$ = $1; + $$.push_back(FieldInitializer($3, $5)); + } +; + // In many cases, using `pattern` recursively will result in ambiguities. // When that happens, it's necessary to factor out two separate productions, // one for when the sub-pattern is an expression, and one for when it is not. @@ -499,6 +541,7 @@ maybe_empty_tuple_pattern: | tuple_pattern { $$ = $1; } ; + clause: CASE pattern DOUBLE_ARROW statement { $$ = Match::Clause($2, $4); } @@ -539,7 +582,14 @@ statement: auto [return_exp, is_omitted_exp] = $2; $$ = arena->New(context.SourceLoc(), return_exp, is_omitted_exp); } -| block +// We disallow empty blocks in places where an arbitrary statement can occur +// in order to avoid ambiguity with the empty struct literal `{}`. We can +// allow non-empty blocks because a non-empty struct literal always starts with +// a designator, and a block never does, so one token of lookahead suffices +// to disambiguate. As of this writing, the "official" resolution of this +// ambiguity is an open question (see +// https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/classes.md#literals) +| nonempty_block { $$ = $1; } | MATCH LEFT_PARENTHESIS expression RIGHT_PARENTHESIS LEFT_CURLY_BRACE clause_list RIGHT_CURLY_BRACE @@ -572,13 +622,20 @@ return_expression: statement_list: // Empty { $$ = std::nullopt; } -| statement statement_list +| nonempty_statement_list + { $$ = $1; } +; +nonempty_statement_list: + statement statement_list { $$ = arena->New(context.SourceLoc(), $1, $2); } ; block: LEFT_CURLY_BRACE statement_list RIGHT_CURLY_BRACE { $$ = arena->New(context.SourceLoc(), $2); } ; +nonempty_block: + LEFT_CURLY_BRACE nonempty_statement_list RIGHT_CURLY_BRACE + { $$ = arena->New(context.SourceLoc(), $2); } return_type: // Empty { $$ = {arena->New(context.SourceLoc()), true}; } diff --git a/executable_semantics/testdata/block/empty.carbon b/executable_semantics/testdata/block/empty.carbon index 5d4622d3f34a..6941a3c10e1b 100644 --- a/executable_semantics/testdata/block/empty.carbon +++ b/executable_semantics/testdata/block/empty.carbon @@ -11,10 +11,12 @@ package ExecutableSemanticsTest api; +fn DoNothing() { + // Empty block +} + fn main() -> i32 { var x: i32 = 0; - { - // empty block - } + DoNothing(); return x; } diff --git a/executable_semantics/testdata/struct/assign.carbon b/executable_semantics/testdata/struct/assign.carbon new file mode 100644 index 000000000000..f1ac06b96998 --- /dev/null +++ b/executable_semantics/testdata/struct/assign.carbon @@ -0,0 +1,18 @@ +// 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 +// +// RUN: executable_semantics %s 2>&1 | \ +// RUN: FileCheck --match-full-lines --allow-unused-prefixes=false %s +// RUN: executable_semantics --trace %s 2>&1 | \ +// RUN: FileCheck --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: executable_semantics %s +// CHECK: result: 0 + +package ExecutableSemanticsTest api; + +fn main() -> i32 { + var x: auto = {.x = 0, .y = 1}; + x = {.x = 5, .y = -5}; + return x.x + x.y; +} diff --git a/executable_semantics/testdata/struct/assign_member.carbon b/executable_semantics/testdata/struct/assign_member.carbon new file mode 100644 index 000000000000..50ad9822fbb8 --- /dev/null +++ b/executable_semantics/testdata/struct/assign_member.carbon @@ -0,0 +1,19 @@ +// 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 +// +// RUN: executable_semantics %s 2>&1 | \ +// RUN: FileCheck --match-full-lines --allow-unused-prefixes=false %s +// RUN: executable_semantics --trace %s 2>&1 | \ +// RUN: FileCheck --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: executable_semantics %s +// CHECK: result: 0 + +package ExecutableSemanticsTest api; + +fn main() -> i32 { + var p1: auto = {.x = 1, .y = 2}; + var p2: auto = p1; + p2.x = 3; + return p1.x - 1; +} diff --git a/executable_semantics/testdata/struct/empty.carbon b/executable_semantics/testdata/struct/empty.carbon new file mode 100644 index 000000000000..133f12273256 --- /dev/null +++ b/executable_semantics/testdata/struct/empty.carbon @@ -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 +// +// RUN: executable_semantics %s 2>&1 | \ +// RUN: FileCheck --match-full-lines --allow-unused-prefixes=false %s +// RUN: executable_semantics --trace %s 2>&1 | \ +// RUN: FileCheck --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: executable_semantics %s +// CHECK: result: 0 + +package ExecutableSemanticsTest api; + +fn main() -> i32 { + var empty: {} = {}; + empty = {}; + if (not (empty == {})) { + return 1; + } + match (empty) { + case {} => { + return 0; + } + } +} diff --git a/executable_semantics/testdata/struct/ending_comma.carbon b/executable_semantics/testdata/struct/ending_comma.carbon new file mode 100644 index 000000000000..fb75898bf17c --- /dev/null +++ b/executable_semantics/testdata/struct/ending_comma.carbon @@ -0,0 +1,18 @@ +// 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 +// +// RUN: executable_semantics %s 2>&1 | \ +// RUN: FileCheck --match-full-lines --allow-unused-prefixes=false %s +// RUN: executable_semantics --trace %s 2>&1 | \ +// RUN: FileCheck --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: executable_semantics %s +// CHECK: result: 0 + +package ExecutableSemanticsTest api; + +fn main() -> i32 { + var t1: {.x: i32,} = {.x = 5,}; + var t2: {.x: i32, .y: i32} = {.x = 2, .y = 3,}; + return t1.x - t2.x - t2.y; +} diff --git a/executable_semantics/testdata/struct/equality.carbon b/executable_semantics/testdata/struct/equality.carbon new file mode 100644 index 000000000000..73649afdc801 --- /dev/null +++ b/executable_semantics/testdata/struct/equality.carbon @@ -0,0 +1,22 @@ +// 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 +// +// RUN: executable_semantics %s 2>&1 | \ +// RUN: FileCheck --match-full-lines --allow-unused-prefixes=false %s +// RUN: executable_semantics --trace %s 2>&1 | \ +// RUN: FileCheck --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: executable_semantics %s +// CHECK: result: 0 + +package ExecutableSemanticsTest api; + +fn main() -> i32 { + var t1: {.x: i32, .y: i32} = {.x = 5, .y = 2}; + var t2: {.x: i32, .y: i32} = {.x = 5, .y = 2}; + if (t1 == t2) { + return 0; + } else { + return 1; + } +} diff --git a/executable_semantics/testdata/struct/equality_false.carbon b/executable_semantics/testdata/struct/equality_false.carbon new file mode 100644 index 000000000000..1aacd1c94196 --- /dev/null +++ b/executable_semantics/testdata/struct/equality_false.carbon @@ -0,0 +1,22 @@ +// 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 +// +// RUN: executable_semantics %s 2>&1 | \ +// RUN: FileCheck --match-full-lines --allow-unused-prefixes=false %s +// RUN: executable_semantics --trace %s 2>&1 | \ +// RUN: FileCheck --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: executable_semantics %s +// CHECK: result: 0 + +package ExecutableSemanticsTest api; + +fn main() -> i32 { + var t1: {.x: i32, .y: i32} = {.x = 5, .y = 2}; + var t2: {.x: i32, .y: i32} = {.x = 5, .y = 4}; + if (t1 == t2) { + return 1; + } else { + return 0; + } +} diff --git a/executable_semantics/testdata/struct/fail_equality_type.carbon b/executable_semantics/testdata/struct/fail_equality_type.carbon new file mode 100644 index 000000000000..6306e6b1a9d5 --- /dev/null +++ b/executable_semantics/testdata/struct/fail_equality_type.carbon @@ -0,0 +1,21 @@ +// 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 +// +// RUN: not executable_semantics %s 2>&1 2>&1 | FileCheck %s +// AUTOUPDATE: executable_semantics %s +// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/struct/fail_equality_type.carbon:16: type error in == +// CHECK: expected: {.x: i32, .y: i32} +// CHECK: actual: {.x: i32} + +package ExecutableSemanticsTest api; + +fn main() -> i32 { + var t1: {.x: i32, .y: i32} = {.x = 5, .y = 2}; + var t2: {.x: i32,} = {.x = 5,}; + if (t1 == t2) { + return 1; + } else { + return 0; + } +} diff --git a/executable_semantics/testdata/struct/fail_field_access_mismatch.carbon b/executable_semantics/testdata/struct/fail_field_access_mismatch.carbon new file mode 100644 index 000000000000..490c675738bd --- /dev/null +++ b/executable_semantics/testdata/struct/fail_field_access_mismatch.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 +// +// RUN: not executable_semantics %s 2>&1 | \ +// RUN: FileCheck --match-full-lines --allow-unused-prefixes=false %s +// RUN: not executable_semantics --trace %s 2>&1 | \ +// RUN: FileCheck --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: executable_semantics %s +// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/struct/fail_field_access_mismatch.carbon:15: struct {.x: i32, .y: i32} does not have a field named z + +package ExecutableSemanticsTest api; + +fn main() -> i32 { + return {.x = 1, .y = 2}.z - 1; +} diff --git a/executable_semantics/testdata/struct/fail_name_order.carbon b/executable_semantics/testdata/struct/fail_name_order.carbon new file mode 100644 index 000000000000..9d69962eff75 --- /dev/null +++ b/executable_semantics/testdata/struct/fail_name_order.carbon @@ -0,0 +1,19 @@ +// 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 +// +// RUN: not executable_semantics %s 2>&1 | \ +// RUN: FileCheck --match-full-lines --allow-unused-prefixes=false %s +// RUN: not executable_semantics --trace %s 2>&1 | \ +// RUN: FileCheck --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: executable_semantics %s +// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/struct/fail_name_order.carbon:17: Type pattern '{.x: i32, .y: i32}' does not match actual type '{.y: i32, .x: i32}' + +package ExecutableSemanticsTest api; + +// Test the that field order matters for structs. + +fn main() -> i32 { + var t: {.x: i32, .y: i32} = {.y = 2, .x = 3}; + return 0; +} diff --git a/executable_semantics/testdata/struct/temp.carbon b/executable_semantics/testdata/struct/temp.carbon new file mode 100644 index 000000000000..47e3ea2356e6 --- /dev/null +++ b/executable_semantics/testdata/struct/temp.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 +// +// RUN: executable_semantics %s 2>&1 | \ +// RUN: FileCheck --match-full-lines --allow-unused-prefixes=false %s +// RUN: executable_semantics --trace %s 2>&1 | \ +// RUN: FileCheck --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: executable_semantics %s +// CHECK: result: 0 + +package ExecutableSemanticsTest api; + +fn main() -> i32 { + return {.x = 1, .y = 2}.x - 1; +} diff --git a/executable_semantics/testdata/struct/var.carbon b/executable_semantics/testdata/struct/var.carbon new file mode 100644 index 000000000000..8a8727c64c35 --- /dev/null +++ b/executable_semantics/testdata/struct/var.carbon @@ -0,0 +1,17 @@ +// 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 +// +// RUN: executable_semantics %s 2>&1 | \ +// RUN: FileCheck --match-full-lines --allow-unused-prefixes=false %s +// RUN: executable_semantics --trace %s 2>&1 | \ +// RUN: FileCheck --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: executable_semantics %s +// CHECK: result: 0 + +package ExecutableSemanticsTest api; + +fn main() -> i32 { + var p: {.x: i32, .y: i32} = {.x = 1, .y = 2}; + return p.y - p.x - 1; +}