diff --git a/common/fuzzing/carbon.proto b/common/fuzzing/carbon.proto index cb645f9a7fdf..56f7a16299ac 100644 --- a/common/fuzzing/carbon.proto +++ b/common/fuzzing/carbon.proto @@ -110,6 +110,11 @@ message TypeTypeLiteral {} message UnimplementedExpression {} +message ArrayTypeLiteral { + optional Expression element_type = 1; + optional Expression size = 2; +} + message Expression { oneof kind { CallExpression call = 1; @@ -132,6 +137,7 @@ message Expression { StringTypeLiteral string_type_literal = 18; TypeTypeLiteral type_type_literal = 19; UnimplementedExpression unimplemented_expression = 20; + ArrayTypeLiteral array_type_literal = 21; } } diff --git a/executable_semantics/ast/ast_rtti.txt b/executable_semantics/ast/ast_rtti.txt index 54bce5fd1ebe..c3735a4ad2c4 100644 --- a/executable_semantics/ast/ast_rtti.txt +++ b/executable_semantics/ast/ast_rtti.txt @@ -55,3 +55,4 @@ abstract class Expression : AstNode; class IntrinsicExpression : Expression; class IfExpression : Expression; class UnimplementedExpression : Expression; + class ArrayTypeLiteral : Expression; diff --git a/executable_semantics/ast/expression.cpp b/executable_semantics/ast/expression.cpp index 8c2ae1c613e8..209581f6be72 100644 --- a/executable_semantics/ast/expression.cpp +++ b/executable_semantics/ast/expression.cpp @@ -176,6 +176,12 @@ void Expression::Print(llvm::raw_ostream& out) const { out << ")"; break; } + case ExpressionKind::ArrayTypeLiteral: { + const auto& array_literal = cast(*this); + out << "[" << array_literal.element_type_expression() << "; " + << array_literal.size_expression() << "]"; + break; + } case ExpressionKind::IdentifierExpression: case ExpressionKind::IntLiteral: case ExpressionKind::BoolLiteral: @@ -232,6 +238,7 @@ void Expression::PrintID(llvm::raw_ostream& out) const { case ExpressionKind::IntrinsicExpression: case ExpressionKind::UnimplementedExpression: case ExpressionKind::FunctionTypeLiteral: + case ExpressionKind::ArrayTypeLiteral: out << "..."; break; } diff --git a/executable_semantics/ast/expression.h b/executable_semantics/ast/expression.h index c47e68da4fa1..8d64ab16a48e 100644 --- a/executable_semantics/ast/expression.h +++ b/executable_semantics/ast/expression.h @@ -572,6 +572,39 @@ class UnimplementedExpression : public Expression { std::vector> children_; }; +// A literal representing a statically-sized array type. +class ArrayTypeLiteral : public Expression { + public: + // Constructs an array type literal which uses the given expressions to + // represent the element type and size. + ArrayTypeLiteral(SourceLocation source_loc, + Nonnull element_type_expression, + Nonnull size_expression) + : Expression(AstNodeKind::ArrayTypeLiteral, source_loc), + element_type_expression_(element_type_expression), + size_expression_(size_expression) {} + + static auto classof(const AstNode* node) -> bool { + return InheritsFromArrayTypeLiteral(node->kind()); + } + + auto element_type_expression() const -> const Expression& { + return *element_type_expression_; + } + auto element_type_expression() -> Expression& { + return *element_type_expression_; + } + + auto size_expression() const -> const Expression& { + return *size_expression_; + } + auto size_expression() -> Expression& { return *size_expression_; } + + private: + Nonnull element_type_expression_; + Nonnull size_expression_; +}; + // Converts paren_contents to an Expression, interpreting the parentheses as // grouping if their contents permit that interpretation, or as forming a // tuple otherwise. diff --git a/executable_semantics/fuzzing/ast_to_proto.cpp b/executable_semantics/fuzzing/ast_to_proto.cpp index 837cd4465bda..9cc96969eb4c 100644 --- a/executable_semantics/fuzzing/ast_to_proto.cpp +++ b/executable_semantics/fuzzing/ast_to_proto.cpp @@ -227,6 +227,17 @@ static auto ExpressionToProto(const Expression& expression) case ExpressionKind::UnimplementedExpression: expression_proto.mutable_unimplemented_expression(); break; + + case ExpressionKind::ArrayTypeLiteral: { + const auto& array_literal = cast(expression); + Fuzzing::ArrayTypeLiteral* array_literal_proto = + expression_proto.mutable_array_type_literal(); + *array_literal_proto->mutable_element_type() = + ExpressionToProto(array_literal.element_type_expression()); + *array_literal_proto->mutable_size() = + ExpressionToProto(array_literal.size_expression()); + break; + } } return expression_proto; } diff --git a/executable_semantics/interpreter/interpreter.cpp b/executable_semantics/interpreter/interpreter.cpp index d7ac7992dbf2..ac46c3ed8680 100644 --- a/executable_semantics/interpreter/interpreter.cpp +++ b/executable_semantics/interpreter/interpreter.cpp @@ -363,6 +363,7 @@ auto Interpreter::StepLvalue() -> ErrorOr { case ExpressionKind::StringTypeLiteral: case ExpressionKind::IntrinsicExpression: case ExpressionKind::IfExpression: + case ExpressionKind::ArrayTypeLiteral: FATAL() << "Can't treat expression as lvalue: " << exp; case ExpressionKind::UnimplementedExpression: FATAL() << "Unimplemented: " << exp; @@ -472,6 +473,7 @@ auto Interpreter::Convert(Nonnull value, case Value::Kind::TypeOfClassType: case Value::Kind::TypeOfInterfaceType: case Value::Kind::TypeOfChoiceType: + case Value::Kind::StaticArrayType: // TODO: add `CHECK(TypeEqual(type, value->dynamic_type()))`, once we // have Value::dynamic_type. return value; @@ -506,15 +508,28 @@ auto Interpreter::Convert(Nonnull value, } case Value::Kind::TupleValue: { const auto& tuple = cast(value); - const auto& destination_tuple_type = cast(destination_type); - CHECK(tuple->elements().size() == - destination_tuple_type->elements().size()); + std::vector> destination_element_types; + switch (destination_type->kind()) { + case Value::Kind::TupleValue: + destination_element_types = + cast(destination_type)->elements(); + break; + case Value::Kind::StaticArrayType: { + const auto& array_type = cast(*destination_type); + destination_element_types.resize(array_type.size(), + &array_type.element_type()); + break; + } + default: + FATAL() << "Can't convert value " << *value << " to type " + << *destination_type; + } + CHECK(tuple->elements().size() == destination_element_types.size()); std::vector> new_elements; for (size_t i = 0; i < tuple->elements().size(); ++i) { - ASSIGN_OR_RETURN( - Nonnull val, - Convert(tuple->elements()[i], destination_tuple_type->elements()[i], - source_loc)); + ASSIGN_OR_RETURN(Nonnull val, + Convert(tuple->elements()[i], + destination_element_types[i], source_loc)); new_elements.push_back(val); } return arena_->New(std::move(new_elements)); @@ -886,6 +901,19 @@ auto Interpreter::StepExp() -> ErrorOr { } case ExpressionKind::UnimplementedExpression: FATAL() << "Unimplemented: " << exp; + case ExpressionKind::ArrayTypeLiteral: { + const auto& array_literal = cast(exp); + if (act.pos() == 0) { + return todo_.Spawn(std::make_unique( + &array_literal.element_type_expression())); + } else if (act.pos() == 1) { + return todo_.Spawn(std::make_unique( + &array_literal.size_expression())); + } else { + return todo_.FinishAction(arena_->New( + act.results()[0], cast(act.results()[1])->value())); + } + } } // switch (exp->kind) } diff --git a/executable_semantics/interpreter/resolve_names.cpp b/executable_semantics/interpreter/resolve_names.cpp index 95f3402f4773..416507ad5c44 100644 --- a/executable_semantics/interpreter/resolve_names.cpp +++ b/executable_semantics/interpreter/resolve_names.cpp @@ -151,6 +151,14 @@ static auto ResolveNames(Expression& expression, ResolveNames(*if_expr.else_expression(), enclosing_scope)); break; } + case ExpressionKind::ArrayTypeLiteral: { + auto& array_literal = cast(expression); + RETURN_IF_ERROR(ResolveNames(array_literal.element_type_expression(), + enclosing_scope)); + RETURN_IF_ERROR( + ResolveNames(array_literal.size_expression(), enclosing_scope)); + break; + } case ExpressionKind::BoolTypeLiteral: case ExpressionKind::BoolLiteral: case ExpressionKind::IntTypeLiteral: diff --git a/executable_semantics/interpreter/type_checker.cpp b/executable_semantics/interpreter/type_checker.cpp index 96d466517158..6299d4819447 100644 --- a/executable_semantics/interpreter/type_checker.cpp +++ b/executable_semantics/interpreter/type_checker.cpp @@ -97,6 +97,7 @@ static auto IsConcreteType(Nonnull value) -> bool { case Value::Kind::TypeOfClassType: case Value::Kind::TypeOfInterfaceType: case Value::Kind::TypeOfChoiceType: + case Value::Kind::StaticArrayType: return true; case Value::Kind::AutoType: // `auto` isn't a concrete type, it's a pattern that matches types. @@ -183,25 +184,40 @@ auto TypeChecker::IsImplicitlyConvertible(Nonnull source, default: return false; } - case Value::Kind::TupleValue: - if (destination->kind() == Value::Kind::TupleValue) { - const std::vector>& source_elements = - cast(*source).elements(); - const std::vector>& destination_elements = - cast(*destination).elements(); - if (source_elements.size() != destination_elements.size()) { - return false; - } - for (size_t i = 0; i < source_elements.size(); ++i) { - if (!IsImplicitlyConvertible(source_elements[i], - destination_elements[i])) { + case Value::Kind::TupleValue: { + const auto& source_tuple = cast(*source); + switch (destination->kind()) { + case Value::Kind::TupleValue: { + const auto& destination_tuple = cast(*destination); + if (source_tuple.elements().size() != + destination_tuple.elements().size()) { return false; } + for (size_t i = 0; i < source_tuple.elements().size(); ++i) { + if (!IsImplicitlyConvertible(source_tuple.elements()[i], + destination_tuple.elements()[i])) { + return false; + } + } + return true; } - return true; - } else { - return false; + case Value::Kind::StaticArrayType: { + const auto& destination_array = cast(*destination); + if (destination_array.size() != source_tuple.elements().size()) { + return false; + } + for (Nonnull source_element : source_tuple.elements()) { + if (!IsImplicitlyConvertible(source_element, + &destination_array.element_type())) { + return false; + } + } + return true; + } + default: + return false; } + } case Value::Kind::TypeType: return destination->kind() == Value::Kind::InterfaceType; default: @@ -339,6 +355,7 @@ auto TypeChecker::ArgumentDeduction(SourceLocation source_loc, << "actual: " << *arg_type; } // For the following cases, we check for type convertability. + case Value::Kind::StaticArrayType: case Value::Kind::ContinuationType: case Value::Kind::InterfaceType: case Value::Kind::ChoiceType: @@ -423,6 +440,7 @@ auto TypeChecker::Substitute( } return new_class_type; } + case Value::Kind::StaticArrayType: case Value::Kind::AutoType: case Value::Kind::IntType: case Value::Kind::BoolType: @@ -467,10 +485,14 @@ auto TypeChecker::TypeCheckExp(Nonnull e, case ExpressionKind::IndexExpression: { auto& index = cast(*e); RETURN_IF_ERROR(TypeCheckExp(&index.aggregate(), impl_scope)); + RETURN_IF_ERROR(TypeCheckExp(&index.offset(), impl_scope)); const Value& aggregate_type = index.aggregate().static_type(); switch (aggregate_type.kind()) { case Value::Kind::TupleValue: { const auto& tuple_type = cast(aggregate_type); + RETURN_IF_ERROR(ExpectExactType(index.offset().source_loc(), + "tuple index", arena_->New(), + &index.offset().static_type())); ASSIGN_OR_RETURN(auto offset_value, InterpExp(&index.offset(), arena_, trace_)); int i = cast(*offset_value).value(); @@ -483,6 +505,15 @@ auto TypeChecker::TypeCheckExp(Nonnull e, index.set_value_category(index.aggregate().value_category()); return Success(); } + case Value::Kind::StaticArrayType: { + RETURN_IF_ERROR(ExpectExactType(index.offset().source_loc(), + "array index", arena_->New(), + &index.offset().static_type())); + index.set_static_type( + &cast(aggregate_type).element_type()); + index.set_value_category(index.aggregate().value_category()); + return Success(); + } default: return FATAL_COMPILATION_ERROR(e->source_loc()) << "expected a tuple"; } @@ -989,6 +1020,34 @@ auto TypeChecker::TypeCheckExp(Nonnull e, } case ExpressionKind::UnimplementedExpression: FATAL() << "Unimplemented: " << *e; + case ExpressionKind::ArrayTypeLiteral: { + auto& array_literal = cast(*e); + RETURN_IF_ERROR( + TypeCheckExp(&array_literal.element_type_expression(), impl_scope)); + ASSIGN_OR_RETURN( + Nonnull element_type, + InterpExp(&array_literal.element_type_expression(), arena_, trace_)); + RETURN_IF_ERROR(ExpectIsConcreteType( + array_literal.element_type_expression().source_loc(), element_type)); + + RETURN_IF_ERROR( + TypeCheckExp(&array_literal.size_expression(), impl_scope)); + RETURN_IF_ERROR( + ExpectExactType(array_literal.size_expression().source_loc(), + "array size", arena_->New(), + &array_literal.size_expression().static_type())); + ASSIGN_OR_RETURN( + Nonnull size_value, + InterpExp(&array_literal.size_expression(), arena_, trace_)); + if (cast(size_value)->value() < 0) { + return FATAL_COMPILATION_ERROR( + array_literal.size_expression().source_loc()) + << "Array size cannot be negative"; + } + array_literal.set_static_type(arena_->New()); + array_literal.set_value_category(ValueCategory::Let); + return Success(); + } } } diff --git a/executable_semantics/interpreter/value.cpp b/executable_semantics/interpreter/value.cpp index 45ff9e2d8b23..3cd3cff96c78 100644 --- a/executable_semantics/interpreter/value.cpp +++ b/executable_semantics/interpreter/value.cpp @@ -377,6 +377,12 @@ void Value::Print(llvm::raw_ostream& out) const { out << "typeof(" << cast(*this).choice_type().name() << ")"; break; + case Value::Kind::StaticArrayType: { + const auto& array_type = cast(*this); + out << "[" << array_type.element_type() << "; " << array_type.size() + << "]"; + break; + } } } @@ -494,6 +500,12 @@ auto TypeEqual(Nonnull t1, Nonnull t2) -> bool { case Value::Kind::TypeOfChoiceType: return TypeEqual(&cast(*t1).choice_type(), &cast(*t2).choice_type()); + case Value::Kind::StaticArrayType: { + const auto& array1 = cast(*t1); + const auto& array2 = cast(*t2); + return TypeEqual(&array1.element_type(), &array2.element_type()) && + array1.size() == array2.size(); + } case Value::Kind::IntValue: case Value::Kind::BoolValue: case Value::Kind::FunctionValue: @@ -595,6 +607,7 @@ auto ValueEqual(Nonnull v1, Nonnull v2) -> bool { case Value::Kind::TypeOfClassType: case Value::Kind::TypeOfInterfaceType: case Value::Kind::TypeOfChoiceType: + case Value::Kind::StaticArrayType: return TypeEqual(v1, v2); case Value::Kind::NominalClassValue: case Value::Kind::AlternativeValue: diff --git a/executable_semantics/interpreter/value.h b/executable_semantics/interpreter/value.h index c53f72871389..bf64b5620a9c 100644 --- a/executable_semantics/interpreter/value.h +++ b/executable_semantics/interpreter/value.h @@ -65,6 +65,7 @@ class Value { TypeOfClassType, TypeOfInterfaceType, TypeOfChoiceType, + StaticArrayType, }; Value(const Value&) = delete; @@ -800,6 +801,30 @@ class TypeOfChoiceType : public Value { Nonnull choice_type_; }; +// The type of a statically-sized array. +// +// Note that values of this type are represented as tuples. +class StaticArrayType : public Value { + public: + // Constructs a statically-sized array type with the given element type and + // size. + StaticArrayType(Nonnull element_type, size_t size) + : Value(Kind::StaticArrayType), + element_type_(element_type), + size_(size) {} + + static auto classof(const Value* value) -> bool { + return value->kind() == Kind::StaticArrayType; + } + + auto element_type() const -> const Value& { return *element_type_; } + auto size() const -> size_t { return size_; } + + private: + Nonnull element_type_; + size_t size_; +}; + auto TypeEqual(Nonnull t1, Nonnull t2) -> bool; auto ValueEqual(Nonnull v1, Nonnull v2) -> bool; diff --git a/executable_semantics/syntax/parser.ypp b/executable_semantics/syntax/parser.ypp index e8443b9fede2..c0a6c8d51d84 100644 --- a/executable_semantics/syntax/parser.ypp +++ b/executable_semantics/syntax/parser.ypp @@ -312,6 +312,8 @@ primary_expression: | paren_expression { $$ = $1; } | struct_literal { $$ = $1; } | struct_type_literal { $$ = $1; } +| LEFT_SQUARE_BRACKET expression SEMICOLON expression RIGHT_SQUARE_BRACKET + { $$ = arena->New(context.source_loc(), $2, $4); } ; postfix_expression: primary_expression diff --git a/executable_semantics/testdata/array/fail_index.carbon b/executable_semantics/testdata/array/fail_index.carbon new file mode 100644 index 000000000000..3baf5f62769a --- /dev/null +++ b/executable_semantics/testdata/array/fail_index.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: %{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: RUNTIME ERROR: index 2 out of range in (0, 1) + +package ExecutableSemanticsTest api; + +fn Main() -> i32 { + var x: [i32; 2] = (0, 1); + return x[2]; +} diff --git a/executable_semantics/testdata/array/fail_negative_size.carbon b/executable_semantics/testdata/array/fail_negative_size.carbon new file mode 100644 index 000000000000..a73335f4b0c6 --- /dev/null +++ b/executable_semantics/testdata/array/fail_negative_size.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: %{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/array/fail_negative_size.carbon:15: Array size cannot be negative + +package ExecutableSemanticsTest api; + +fn Main() -> i32 { + var x: [i32; -1] = (); + return x[0]; +} diff --git a/executable_semantics/testdata/array/fail_size_mismatch.carbon b/executable_semantics/testdata/array/fail_size_mismatch.carbon new file mode 100644 index 000000000000..8d54b9732766 --- /dev/null +++ b/executable_semantics/testdata/array/fail_size_mismatch.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: %{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/array/fail_size_mismatch.carbon:15: type error in name binding: '(i32, i32, i32)' is not implicitly convertible to '[i32; 2]' + +package ExecutableSemanticsTest api; + +fn Main() -> i32 { + var x: [i32; 2] = (0, 1, 2); + return x[0]; +} diff --git a/executable_semantics/testdata/array/index.carbon b/executable_semantics/testdata/array/index.carbon new file mode 100644 index 000000000000..753809a3db02 --- /dev/null +++ b/executable_semantics/testdata/array/index.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 x: [i32; 2] = (0, 1); + var index: i32 = 1; + x[index] = 0; + return x[0] + x[1]; +} diff --git a/executable_semantics/testdata/array/nested.carbon b/executable_semantics/testdata/array/nested.carbon new file mode 100644 index 000000000000..339426a8da42 --- /dev/null +++ b/executable_semantics/testdata/array/nested.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 x: [[i32; 3]; 2] = ((0, 1, 2), (3, 4, 5)); + return x[1][2] - 5; +}