Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature generic choice #2042

Merged
merged 20 commits into from
Aug 24, 2022
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions explorer/ast/declaration.h
Original file line number Diff line number Diff line change
Expand Up @@ -262,16 +262,26 @@ class ChoiceDeclaration : public Declaration {
using ImplementsCarbonValueNode = void;

ChoiceDeclaration(SourceLocation source_loc, std::string name,
std::optional<Nonnull<TuplePattern*>> type_params,
std::vector<Nonnull<AlternativeSignature*>> alternatives)
: Declaration(AstNodeKind::ChoiceDeclaration, source_loc),
name_(std::move(name)),
type_params_(type_params),
alternatives_(std::move(alternatives)) {}

static auto classof(const AstNode* node) -> bool {
return InheritsFromChoiceDeclaration(node->kind());
}

auto name() const -> const std::string& { return name_; }

auto type_params() const -> std::optional<Nonnull<const TuplePattern*>> {
return type_params_;
}
auto type_params() -> std::optional<Nonnull<TuplePattern*>> {
return type_params_;
}

auto alternatives() const
-> llvm::ArrayRef<Nonnull<const AlternativeSignature*>> {
return alternatives_;
Expand All @@ -280,11 +290,18 @@ class ChoiceDeclaration : public Declaration {
return alternatives_;
}

void set_members(const std::vector<NamedValue>& members) {
members_ = members;
}
auto members() const -> std::vector<NamedValue> { return members_; }

auto value_category() const -> ValueCategory { return ValueCategory::Let; }

private:
std::string name_;
std::optional<Nonnull<TuplePattern*>> type_params_;
std::vector<Nonnull<AlternativeSignature*>> alternatives_;
std::vector<NamedValue> members_;
};

// Global variable definition implements the Declaration concept.
Expand Down
3 changes: 3 additions & 0 deletions explorer/interpreter/interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,9 @@ auto Interpreter::CallFunction(const CallExpression& call,
case DeclarationKind::InterfaceDeclaration:
return todo_.FinishAction(arena_->New<InterfaceType>(
&cast<InterfaceDeclaration>(decl), bindings));
case DeclarationKind::ChoiceDeclaration:
return todo_.FinishAction(arena_->New<ChoiceType>(
&cast<ChoiceDeclaration>(decl), bindings));
default:
CARBON_FATAL() << "unknown kind of ParameterizedEntityName " << decl;
}
Expand Down
9 changes: 8 additions & 1 deletion explorer/interpreter/resolve_names.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -562,14 +562,21 @@ static auto ResolveNames(Declaration& declaration, StaticScope& enclosing_scope,
}
case DeclarationKind::ChoiceDeclaration: {
auto& choice = cast<ChoiceDeclaration>(declaration);
StaticScope choice_scope;
choice_scope.AddParent(&enclosing_scope);
enclosing_scope.MarkDeclared(choice.name());
// Alternative names are never used unqualified, so we don't need to
// add the alternatives to a scope, or introduce a new scope; we only
// need to check for duplicates.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment seems to be referring to the below for loop, and probably shouldn't be separated from it, particularly with a blank line.

if (choice.type_params().has_value()) {
CARBON_RETURN_IF_ERROR(
ResolveNames(**choice.type_params(), choice_scope));
}

std::set<std::string_view> alternative_names;
for (Nonnull<AlternativeSignature*> alternative : choice.alternatives()) {
CARBON_RETURN_IF_ERROR(
ResolveNames(alternative->signature(), enclosing_scope));
ResolveNames(alternative->signature(), choice_scope));
if (!alternative_names.insert(alternative->name()).second) {
return CompilationError(alternative->source_loc())
<< "Duplicate name `" << alternative->name()
Expand Down
95 changes: 83 additions & 12 deletions explorer/interpreter/type_checker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,7 @@ auto TypeChecker::ArgumentDeduction(
}
**trace_stream_ << "\n";
}

// Handle the case where we can't perform deduction, either because the
// parameter is a primitive type or because the parameter and argument have
// different forms. In this case, we require an implicit conversion to exist,
Expand Down Expand Up @@ -1719,6 +1720,7 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
CARBON_ASSIGN_OR_RETURN(
Nonnull<const Value*> type,
InterpExp(&access.object(), arena_, trace_stream_));

switch (type->kind()) {
case Value::Kind::StructType: {
for (const auto& field : cast<StructType>(type)->fields()) {
Expand All @@ -1736,6 +1738,7 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
}
case Value::Kind::ChoiceType: {
const ChoiceType& choice = cast<ChoiceType>(*type);

std::optional<Nonnull<const Value*>> parameter_types =
choice.FindAlternative(access.member_name());
if (!parameter_types.has_value()) {
Expand All @@ -1744,9 +1747,15 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
<< " does not have an alternative named "
<< access.member_name();
}
Nonnull<const Value*> type =
arena_->New<FunctionType>(*parameter_types, llvm::None,
&choice, llvm::None, llvm::None);
Nonnull<const Value*> substituted_parameter_type =
*parameter_types;
if (choice.IsParameterized()) {
substituted_parameter_type =
Substitute(choice.type_args(), *parameter_types);
}
Nonnull<const Value*> type = arena_->New<FunctionType>(
substituted_parameter_type, llvm::None, &choice, llvm::None,
llvm::None);
// TODO: Should there be a Declaration corresponding to each
// choice type alternative?
access.set_member(Member(arena_->New<NamedValue>(
Expand All @@ -1771,6 +1780,7 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
}
Nonnull<const Value*> field_type = Substitute(
class_type.type_args(), &(*member)->static_type());

access.set_static_type(field_type);
access.set_value_category(ValueCategory::Let);
return Success();
Expand Down Expand Up @@ -2205,8 +2215,11 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
}
case ExpressionKind::CallExpression: {
auto& call = cast<CallExpression>(*e);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're adding a lot of blank lines in this file around code that you're not otherwise editing. The rule of thumb is to minimize vertical whitespace (https://google.github.io/styleguide/cppguide.html#Vertical_Whitespace), and while I think it can be useful to separate code blocks, I think separating out single lines is probably a little more than is needed. Can you please do a pass reducing the whitespace back down?

CARBON_RETURN_IF_ERROR(TypeCheckExp(&call.function(), impl_scope));

CARBON_RETURN_IF_ERROR(TypeCheckExp(&call.argument(), impl_scope));

switch (call.function().static_type().kind()) {
case Value::Kind::FunctionType: {
const auto& fun_t = cast<FunctionType>(call.function().static_type());
Expand All @@ -2216,6 +2229,7 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
<< "\nwith arguments of type: " << call.argument().static_type()
<< "\n";
}

CARBON_RETURN_IF_ERROR(DeduceCallBindings(
call, &fun_t.parameters(), fun_t.generic_parameters(),
fun_t.deduced_bindings(), fun_t.impl_bindings(), impl_scope));
Expand All @@ -2233,9 +2247,11 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
// This case handles the application of a parameterized class or
// interface to a set of arguments, such as Point(i32) or
// AddWith(i32).
const ParameterizedEntityName& param_name =
cast<TypeOfParameterizedEntityName>(call.function().static_type())
.name();
const TypeOfParameterizedEntityName& entity =
cast<TypeOfParameterizedEntityName>(
call.function().static_type());

const ParameterizedEntityName& param_name = entity.name();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like it's not necessary to change. I'm not seeing a particular style benefit to it, and you don't make use of entity. Can you please switch back so that we're not changing code when not necessary?


// Collect the top-level generic parameters and their constraints.
std::vector<FunctionType::GenericParameter> generic_parameters;
Expand Down Expand Up @@ -2278,12 +2294,28 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
call.set_value_category(ValueCategory::Let);
break;
}
case DeclarationKind::ChoiceDeclaration: {
Nonnull<ChoiceType*> ct = arena_->New<ChoiceType>(
cast<ChoiceDeclaration>(&decl), bindings);
Nonnull<TypeOfChoiceType*> inst_choice_type =
arena_->New<TypeOfChoiceType>(ct);
call.set_static_type(inst_choice_type);
call.set_value_category(ValueCategory::Let);

break;
}
default:
CARBON_FATAL()
<< "unknown type of ParameterizedEntityName for " << decl;
}
return Success();
}
case Value::Kind::TypeOfChoiceType: {
return CompilationError(e->source_loc())
<< "in call `" << *e
<< "`, expected callee to be a function, found `"
<< call.function().static_type() << "`";
}
default: {
return CompilationError(e->source_loc())
<< "in call `" << *e
Expand Down Expand Up @@ -2852,20 +2884,28 @@ auto TypeChecker::TypeCheckPattern(
<< "'" << alternative.alternative_name()
<< "' is not an alternative of " << choice_type;
}
CARBON_RETURN_IF_ERROR(TypeCheckPattern(&alternative.arguments(),
*parameter_types, impl_scope,
enclosing_value_category));

Nonnull<const Value*> substituted_parameter_type = *parameter_types;
if (choice_type.IsParameterized()) {
substituted_parameter_type =
Substitute(choice_type.type_args(), *parameter_types);
}
CARBON_RETURN_IF_ERROR(
TypeCheckPattern(&alternative.arguments(), substituted_parameter_type,
impl_scope, enclosing_value_category));
alternative.set_static_type(&choice_type);
CARBON_ASSIGN_OR_RETURN(
Nonnull<const Value*> alternative_value,
InterpPattern(&alternative, arena_, trace_stream_));

SetValue(&alternative, alternative_value);
return Success();
}
case PatternKind::ExpressionPattern: {
auto& expression = cast<ExpressionPattern>(*p).expression();
CARBON_RETURN_IF_ERROR(TypeCheckExp(&expression, impl_scope));
p->set_static_type(&expression.static_type());

CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> expr_value,
InterpPattern(p, arena_, trace_stream_));
SetValue(p, expr_value);
Expand Down Expand Up @@ -3802,21 +3842,51 @@ auto TypeChecker::TypeCheckImplDeclaration(Nonnull<ImplDeclaration*> impl_decl,
auto TypeChecker::DeclareChoiceDeclaration(Nonnull<ChoiceDeclaration*> choice,
const ScopeInfo& scope_info)
-> ErrorOr<Success> {
ImplScope choice_scope;
choice_scope.AddParent(scope_info.innermost_scope);
std::vector<Nonnull<const GenericBinding*>> bindings = scope_info.bindings;
if (choice->type_params().has_value()) {
Nonnull<TuplePattern*> type_params = *choice->type_params();
CARBON_RETURN_IF_ERROR(TypeCheckPattern(type_params, std::nullopt,
choice_scope, ValueCategory::Let));
CollectGenericBindingsInPattern(type_params, bindings);
if (trace_stream_) {
**trace_stream_ << choice_scope;
}
}
BindingMap generic_args;
for (auto* binding : bindings) {
generic_args[binding] = *binding->symbolic_identity();
}

std::vector<NamedValue> alternatives;
for (Nonnull<AlternativeSignature*> alternative : choice->alternatives()) {
CARBON_ASSIGN_OR_RETURN(auto signature,
TypeCheckTypeExp(&alternative->signature(),
*scope_info.innermost_scope));
alternatives.push_back({.name = alternative->name(), .value = signature});
}
auto ct = arena_->New<ChoiceType>(choice->name(), std::move(alternatives));
choice->set_members(alternatives);
if (choice->type_params().has_value()) {
Nonnull<ParameterizedEntityName*> param_name =
arena_->New<ParameterizedEntityName>(choice, *choice->type_params());
SetConstantValue(choice, param_name);
choice->set_static_type(
arena_->New<TypeOfParameterizedEntityName>(param_name));
return Success();
}

auto ct = arena_->New<ChoiceType>(
choice,
arena_->New<Bindings>(std::move(generic_args), Bindings::NoWitnesses));

SetConstantValue(choice, ct);
choice->set_static_type(arena_->New<TypeOfChoiceType>(ct));
return Success();
}

auto TypeChecker::TypeCheckChoiceDeclaration(
Nonnull<ChoiceDeclaration*> /*choice*/, const ImplScope& /*impl_scope*/)
auto TypeChecker::TypeCheckChoiceDeclaration(Nonnull<ChoiceDeclaration*> choice,
const ImplScope& impl_scope)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These were commented out to indicate they were unused. They probably should not be uncommented in this PR, because uses are not added.

-> ErrorOr<Success> {
// Nothing to do here, but perhaps that will change in the future?
return Success();
Expand Down Expand Up @@ -4009,6 +4079,7 @@ auto TypeChecker::DeclareDeclaration(Nonnull<Declaration*> d,

case DeclarationKind::VariableDeclaration: {
auto& var = cast<VariableDeclaration>(*d);

// Associate the variable name with it's declared type in the
// compile-time symbol table.
if (!llvm::isa<ExpressionPattern>(var.binding().type())) {
Expand Down
19 changes: 16 additions & 3 deletions explorer/interpreter/value.h
Original file line number Diff line number Diff line change
Expand Up @@ -835,10 +835,13 @@ class SymbolicWitness : public Witness {
// A choice type.
class ChoiceType : public Value {
public:
ChoiceType(std::string name, std::vector<NamedValue> alternatives)
ChoiceType(Nonnull<const ChoiceDeclaration*> declaration,
Nonnull<const Bindings*> bindings)
: Value(Kind::ChoiceType),
name_(std::move(name)),
alternatives_(std::move(alternatives)) {}
name_(declaration->name()),
alternatives_(declaration->members()),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these copies needed? Since you've started keeping a ChoiceDeclaration for IsParameterized, should name() and FindAlternative use it directly too?

bindings_(bindings),
declaration_(declaration) {}

static auto classof(const Value* value) -> bool {
return value->kind() == Kind::ChoiceType;
Expand All @@ -851,9 +854,19 @@ class ChoiceType : public Value {
auto FindAlternative(std::string_view name) const
-> std::optional<Nonnull<const Value*>>;

auto bindings() const -> const Bindings& { return *bindings_; }
auto type_args() const -> const BindingMap& { return bindings_->args(); }
auto declaration() const -> const ChoiceDeclaration& { return *declaration_; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like it's not safe to call these methods if the object was created using the first constructor.


auto IsParameterized() const -> bool {
return declaration_->type_params().has_value();
}

private:
std::string name_;
std::vector<NamedValue> alternatives_;
Nonnull<const Bindings*> bindings_;
Nonnull<const ChoiceDeclaration*> declaration_;
};

// A continuation type.
Expand Down
4 changes: 2 additions & 2 deletions explorer/syntax/parser.ypp
Original file line number Diff line number Diff line change
Expand Up @@ -1115,8 +1115,8 @@ declaration:
context.source_loc(), $3,
arena->New<SelfDeclaration>(context.source_loc()), $1, $4, $5, $7);
}
| CHOICE identifier LEFT_CURLY_BRACE alternative_list RIGHT_CURLY_BRACE
{ $$ = arena->New<ChoiceDeclaration>(context.source_loc(), $2, $4); }
| CHOICE identifier type_params LEFT_CURLY_BRACE alternative_list RIGHT_CURLY_BRACE
{ $$ = arena->New<ChoiceDeclaration>(context.source_loc(), $2, $3, $5); }
| VAR variable_declaration SEMICOLON
{
$$ = arena->New<VariableDeclaration>(context.source_loc(), $2,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// 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: %{explorer} %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s
// AUTOUPDATE: %{explorer} %s
// CHECK: result: 22

package ExplorerTest api;


choice MyOptionalElement(ZZ:! Type, YY:! Type) {
None(YY),
Element(ZZ)
}


fn Main() -> i32 {
var f: MyOptionalElement(String,i32);
f = MyOptionalElement(String,i32).None(22);
match(f) {
case MyOptionalElement(String,i32).None(var x: i32) => {
return x;
}
}
return 0;
}
Loading