Просмотр исходного кода

Initial support for statically-sized arrays (#1158)

Geoff Romer 4 лет назад
Родитель
Сommit
8f9638d759

+ 6 - 0
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;
   }
 }
 

+ 1 - 0
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;

+ 7 - 0
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<ArrayTypeLiteral>(*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;
   }

+ 33 - 0
executable_semantics/ast/expression.h

@@ -572,6 +572,39 @@ class UnimplementedExpression : public Expression {
   std::vector<Nonnull<AstNode*>> 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<Expression*> element_type_expression,
+                   Nonnull<Expression*> 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<Expression*> element_type_expression_;
+  Nonnull<Expression*> 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.

+ 11 - 0
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<ArrayTypeLiteral>(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;
 }

+ 35 - 7
executable_semantics/interpreter/interpreter.cpp

@@ -363,6 +363,7 @@ auto Interpreter::StepLvalue() -> ErrorOr<Success> {
     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<const Value*> 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<const Value*> value,
     }
     case Value::Kind::TupleValue: {
       const auto& tuple = cast<TupleValue>(value);
-      const auto& destination_tuple_type = cast<TupleValue>(destination_type);
-      CHECK(tuple->elements().size() ==
-            destination_tuple_type->elements().size());
+      std::vector<Nonnull<const Value*>> destination_element_types;
+      switch (destination_type->kind()) {
+        case Value::Kind::TupleValue:
+          destination_element_types =
+              cast<TupleValue>(destination_type)->elements();
+          break;
+        case Value::Kind::StaticArrayType: {
+          const auto& array_type = cast<StaticArrayType>(*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<Nonnull<const Value*>> new_elements;
       for (size_t i = 0; i < tuple->elements().size(); ++i) {
-        ASSIGN_OR_RETURN(
-            Nonnull<const Value*> val,
-            Convert(tuple->elements()[i], destination_tuple_type->elements()[i],
-                    source_loc));
+        ASSIGN_OR_RETURN(Nonnull<const Value*> val,
+                         Convert(tuple->elements()[i],
+                                 destination_element_types[i], source_loc));
         new_elements.push_back(val);
       }
       return arena_->New<TupleValue>(std::move(new_elements));
@@ -886,6 +901,19 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
     }
     case ExpressionKind::UnimplementedExpression:
       FATAL() << "Unimplemented: " << exp;
+    case ExpressionKind::ArrayTypeLiteral: {
+      const auto& array_literal = cast<ArrayTypeLiteral>(exp);
+      if (act.pos() == 0) {
+        return todo_.Spawn(std::make_unique<ExpressionAction>(
+            &array_literal.element_type_expression()));
+      } else if (act.pos() == 1) {
+        return todo_.Spawn(std::make_unique<ExpressionAction>(
+            &array_literal.size_expression()));
+      } else {
+        return todo_.FinishAction(arena_->New<StaticArrayType>(
+            act.results()[0], cast<IntValue>(act.results()[1])->value()));
+      }
+    }
   }  // switch (exp->kind)
 }
 

+ 8 - 0
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<ArrayTypeLiteral>(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:

+ 73 - 14
executable_semantics/interpreter/type_checker.cpp

@@ -97,6 +97,7 @@ static auto IsConcreteType(Nonnull<const Value*> 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<const Value*> source,
         default:
           return false;
       }
-    case Value::Kind::TupleValue:
-      if (destination->kind() == Value::Kind::TupleValue) {
-        const std::vector<Nonnull<const Value*>>& source_elements =
-            cast<TupleValue>(*source).elements();
-        const std::vector<Nonnull<const Value*>>& destination_elements =
-            cast<TupleValue>(*destination).elements();
-        if (source_elements.size() != destination_elements.size()) {
-          return false;
+    case Value::Kind::TupleValue: {
+      const auto& source_tuple = cast<TupleValue>(*source);
+      switch (destination->kind()) {
+        case Value::Kind::TupleValue: {
+          const auto& destination_tuple = cast<TupleValue>(*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;
         }
-        for (size_t i = 0; i < source_elements.size(); ++i) {
-          if (!IsImplicitlyConvertible(source_elements[i],
-                                       destination_elements[i])) {
+        case Value::Kind::StaticArrayType: {
+          const auto& destination_array = cast<StaticArrayType>(*destination);
+          if (destination_array.size() != source_tuple.elements().size()) {
             return false;
           }
+          for (Nonnull<const Value*> source_element : source_tuple.elements()) {
+            if (!IsImplicitlyConvertible(source_element,
+                                         &destination_array.element_type())) {
+              return false;
+            }
+          }
+          return true;
         }
-        return true;
-      } else {
-        return false;
+        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<Expression*> e,
     case ExpressionKind::IndexExpression: {
       auto& index = cast<IndexExpression>(*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<TupleValue>(aggregate_type);
+          RETURN_IF_ERROR(ExpectExactType(index.offset().source_loc(),
+                                          "tuple index", arena_->New<IntType>(),
+                                          &index.offset().static_type()));
           ASSIGN_OR_RETURN(auto offset_value,
                            InterpExp(&index.offset(), arena_, trace_));
           int i = cast<IntValue>(*offset_value).value();
@@ -483,6 +505,15 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> 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<IntType>(),
+                                          &index.offset().static_type()));
+          index.set_static_type(
+              &cast<StaticArrayType>(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<Expression*> e,
     }
     case ExpressionKind::UnimplementedExpression:
       FATAL() << "Unimplemented: " << *e;
+    case ExpressionKind::ArrayTypeLiteral: {
+      auto& array_literal = cast<ArrayTypeLiteral>(*e);
+      RETURN_IF_ERROR(
+          TypeCheckExp(&array_literal.element_type_expression(), impl_scope));
+      ASSIGN_OR_RETURN(
+          Nonnull<const Value*> 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<IntType>(),
+                          &array_literal.size_expression().static_type()));
+      ASSIGN_OR_RETURN(
+          Nonnull<const Value*> size_value,
+          InterpExp(&array_literal.size_expression(), arena_, trace_));
+      if (cast<IntValue>(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<TypeType>());
+      array_literal.set_value_category(ValueCategory::Let);
+      return Success();
+    }
   }
 }
 

+ 13 - 0
executable_semantics/interpreter/value.cpp

@@ -377,6 +377,12 @@ void Value::Print(llvm::raw_ostream& out) const {
       out << "typeof(" << cast<TypeOfChoiceType>(*this).choice_type().name()
           << ")";
       break;
+    case Value::Kind::StaticArrayType: {
+      const auto& array_type = cast<StaticArrayType>(*this);
+      out << "[" << array_type.element_type() << "; " << array_type.size()
+          << "]";
+      break;
+    }
   }
 }
 
@@ -494,6 +500,12 @@ auto TypeEqual(Nonnull<const Value*> t1, Nonnull<const Value*> t2) -> bool {
     case Value::Kind::TypeOfChoiceType:
       return TypeEqual(&cast<TypeOfChoiceType>(*t1).choice_type(),
                        &cast<TypeOfChoiceType>(*t2).choice_type());
+    case Value::Kind::StaticArrayType: {
+      const auto& array1 = cast<StaticArrayType>(*t1);
+      const auto& array2 = cast<StaticArrayType>(*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<const Value*> v1, Nonnull<const Value*> 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:

+ 25 - 0
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<const ChoiceType*> 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<const Value*> 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<const Value*> element_type_;
+  size_t size_;
+};
+
 auto TypeEqual(Nonnull<const Value*> t1, Nonnull<const Value*> t2) -> bool;
 auto ValueEqual(Nonnull<const Value*> v1, Nonnull<const Value*> v2) -> bool;
 

+ 2 - 0
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<ArrayTypeLiteral>(context.source_loc(), $2, $4); }
 ;
 postfix_expression:
   primary_expression

+ 17 - 0
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];
+}

+ 17 - 0
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];
+}

+ 17 - 0
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];
+}

+ 19 - 0
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];
+}

+ 17 - 0
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;
+}