Initial support for statically-sized arrays (#1158)

This commit is contained in:
Geoff Romer
2022-03-31 10:55:34 -07:00
committed by GitHub
parent 0b8a96af6d
commit 8f9638d759
16 changed files with 302 additions and 22 deletions
+1
View File
@@ -55,3 +55,4 @@ abstract class Expression : AstNode;
class IntrinsicExpression : Expression;
class IfExpression : Expression;
class UnimplementedExpression : Expression;
class ArrayTypeLiteral : Expression;
+7
View File
@@ -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
View File
@@ -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.
@@ -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;
}
@@ -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)
}
@@ -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:
@@ -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;
}
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<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;
}
return true;
} else {
return false;
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;
}
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();
}
}
}
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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];
}
@@ -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];
}
@@ -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
View File
@@ -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
View File
@@ -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;
}