diff --git a/explorer/ast/declaration.h b/explorer/ast/declaration.h index d2ef58231cf4..1a1a32f9316b 100644 --- a/explorer/ast/declaration.h +++ b/explorer/ast/declaration.h @@ -262,9 +262,11 @@ class ChoiceDeclaration : public Declaration { using ImplementsCarbonValueNode = void; ChoiceDeclaration(SourceLocation source_loc, std::string name, + std::optional> type_params, std::vector> 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 { @@ -272,6 +274,14 @@ class ChoiceDeclaration : public Declaration { } auto name() const -> const std::string& { return name_; } + + auto type_params() const -> std::optional> { + return type_params_; + } + auto type_params() -> std::optional> { + return type_params_; + } + auto alternatives() const -> llvm::ArrayRef> { return alternatives_; @@ -280,11 +290,18 @@ class ChoiceDeclaration : public Declaration { return alternatives_; } + void set_members(const std::vector& members) { + members_ = members; + } + auto members() const -> std::vector { return members_; } + auto value_category() const -> ValueCategory { return ValueCategory::Let; } private: std::string name_; + std::optional> type_params_; std::vector> alternatives_; + std::vector members_; }; // Global variable definition implements the Declaration concept. diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index 75e07af2a1b7..2c646ecdfbf8 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -838,6 +838,9 @@ auto Interpreter::CallFunction(const CallExpression& call, case DeclarationKind::InterfaceDeclaration: return todo_.FinishAction(arena_->New( &cast(decl), bindings)); + case DeclarationKind::ChoiceDeclaration: + return todo_.FinishAction(arena_->New( + &cast(decl), bindings)); default: CARBON_FATAL() << "unknown kind of ParameterizedEntityName " << decl; } diff --git a/explorer/interpreter/resolve_names.cpp b/explorer/interpreter/resolve_names.cpp index 94f3ad028d47..00a1f47d5bec 100644 --- a/explorer/interpreter/resolve_names.cpp +++ b/explorer/interpreter/resolve_names.cpp @@ -562,14 +562,20 @@ static auto ResolveNames(Declaration& declaration, StaticScope& enclosing_scope, } case DeclarationKind::ChoiceDeclaration: { auto& choice = cast(declaration); + StaticScope choice_scope; + choice_scope.AddParent(&enclosing_scope); enclosing_scope.MarkDeclared(choice.name()); + if (choice.type_params().has_value()) { + CARBON_RETURN_IF_ERROR( + ResolveNames(**choice.type_params(), choice_scope)); + } // 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. std::set alternative_names; for (Nonnull 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() diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index cfdec2d2d7cd..3416eb406ad6 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -1744,9 +1744,15 @@ auto TypeChecker::TypeCheckExp(Nonnull e, << " does not have an alternative named " << access.member_name(); } - Nonnull type = - arena_->New(*parameter_types, llvm::None, - &choice, llvm::None, llvm::None); + Nonnull substituted_parameter_type = + *parameter_types; + if (choice.IsParameterized()) { + substituted_parameter_type = + Substitute(choice.type_args(), *parameter_types); + } + Nonnull type = arena_->New( + 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( @@ -2278,12 +2284,22 @@ auto TypeChecker::TypeCheckExp(Nonnull e, call.set_value_category(ValueCategory::Let); break; } + case DeclarationKind::ChoiceDeclaration: { + Nonnull ct = arena_->New( + cast(&decl), bindings); + Nonnull inst_choice_type = + arena_->New(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: default: { return CompilationError(e->source_loc()) << "in call `" << *e @@ -2852,9 +2868,15 @@ 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 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 alternative_value, @@ -3802,6 +3824,23 @@ auto TypeChecker::TypeCheckImplDeclaration(Nonnull impl_decl, auto TypeChecker::DeclareChoiceDeclaration(Nonnull choice, const ScopeInfo& scope_info) -> ErrorOr { + ImplScope choice_scope; + choice_scope.AddParent(scope_info.innermost_scope); + std::vector> bindings = scope_info.bindings; + if (choice->type_params().has_value()) { + Nonnull 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 alternatives; for (Nonnull alternative : choice->alternatives()) { CARBON_ASSIGN_OR_RETURN(auto signature, @@ -3809,7 +3848,20 @@ auto TypeChecker::DeclareChoiceDeclaration(Nonnull choice, *scope_info.innermost_scope)); alternatives.push_back({.name = alternative->name(), .value = signature}); } - auto ct = arena_->New(choice->name(), std::move(alternatives)); + choice->set_members(alternatives); + if (choice->type_params().has_value()) { + Nonnull param_name = + arena_->New(choice, *choice->type_params()); + SetConstantValue(choice, param_name); + choice->set_static_type( + arena_->New(param_name)); + return Success(); + } + + auto ct = arena_->New( + choice, + arena_->New(std::move(generic_args), Bindings::NoWitnesses)); + SetConstantValue(choice, ct); choice->set_static_type(arena_->New(ct)); return Success(); diff --git a/explorer/interpreter/value.cpp b/explorer/interpreter/value.cpp index bafa78ad06df..398f0631322d 100644 --- a/explorer/interpreter/value.cpp +++ b/explorer/interpreter/value.cpp @@ -937,7 +937,8 @@ auto ConstraintType::VisitEqualValues( auto ChoiceType::FindAlternative(std::string_view name) const -> std::optional> { - for (const NamedValue& alternative : alternatives_) { + std::vector alternatives = declaration_->members(); + for (const NamedValue& alternative : alternatives) { if (alternative.name == name) { return alternative.value; } diff --git a/explorer/interpreter/value.h b/explorer/interpreter/value.h index 2f626589e38d..5d221f07bd49 100644 --- a/explorer/interpreter/value.h +++ b/explorer/interpreter/value.h @@ -835,25 +835,36 @@ class SymbolicWitness : public Witness { // A choice type. class ChoiceType : public Value { public: - ChoiceType(std::string name, std::vector alternatives) + ChoiceType(Nonnull declaration, + Nonnull bindings) : Value(Kind::ChoiceType), - name_(std::move(name)), - alternatives_(std::move(alternatives)) {} + declaration_(declaration), + bindings_(bindings) {} static auto classof(const Value* value) -> bool { return value->kind() == Kind::ChoiceType; } - auto name() const -> const std::string& { return name_; } + auto name() const -> const std::string& { return declaration_->name(); } // Returns the parameter types of the alternative with the given name, // or nullopt if no such alternative is present. auto FindAlternative(std::string_view name) const -> std::optional>; + auto bindings() const -> const Bindings& { return *bindings_; } + + auto type_args() const -> const BindingMap& { return bindings_->args(); } + + auto declaration() const -> const ChoiceDeclaration& { return *declaration_; } + + auto IsParameterized() const -> bool { + return declaration_->type_params().has_value(); + } + private: - std::string name_; - std::vector alternatives_; + Nonnull declaration_; + Nonnull bindings_; }; // A continuation type. diff --git a/explorer/syntax/parser.ypp b/explorer/syntax/parser.ypp index fe8a808b5000..e01472abf95f 100644 --- a/explorer/syntax/parser.ypp +++ b/explorer/syntax/parser.ypp @@ -1115,8 +1115,8 @@ declaration: context.source_loc(), $3, arena->New(context.source_loc()), $1, $4, $5, $7); } -| CHOICE identifier LEFT_CURLY_BRACE alternative_list RIGHT_CURLY_BRACE - { $$ = arena->New(context.source_loc(), $2, $4); } +| CHOICE identifier type_params LEFT_CURLY_BRACE alternative_list RIGHT_CURLY_BRACE + { $$ = arena->New(context.source_loc(), $2, $3, $5); } | VAR variable_declaration SEMICOLON { $$ = arena->New(context.source_loc(), $2, diff --git a/explorer/testdata/choice/generic_choice_multiple_template_arguments.carbon b/explorer/testdata/choice/generic_choice_multiple_template_arguments.carbon new file mode 100644 index 000000000000..02948a04988f --- /dev/null +++ b/explorer/testdata/choice/generic_choice_multiple_template_arguments.carbon @@ -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; +} diff --git a/explorer/testdata/choice/generic_choice_nested_in_template_class.carbon b/explorer/testdata/choice/generic_choice_nested_in_template_class.carbon new file mode 100644 index 000000000000..93a0a9fe5d25 --- /dev/null +++ b/explorer/testdata/choice/generic_choice_nested_in_template_class.carbon @@ -0,0 +1,58 @@ +// 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: H 22 +// CHECK: result: 0 + +package ExplorerTest api; + + +choice MyOptionalElement(T:! Type) { + None(), + Element(T) +} + +class MyOptional(T:! Type){ + fn CreateEmpty() -> MyOptional(T){ + return { .element = MyOptionalElement(T).None() }; + } + fn Create ( value: T ) -> MyOptional(T){ + return { .element = MyOptionalElement(T).Element(value) }; + } + + fn has_value[me: Self] () -> bool{ + var x: MyOptionalElement(T) = me.element; + match(x){ + case MyOptionalElement(T).None() => { return false; } + } + return false; + } + + fn get[me: Self] () -> T { + var y: T; + var x: MyOptionalElement(T) = me.element; + match(x){ + case MyOptionalElement(T).Element( var x: T ) =>{ + return x; + } + } + return y; + } + + var element: MyOptionalElement(T); +} + + +fn Main() -> i32 { + var f: MyOptional(i32) = MyOptional(i32).Create(22); + var x: i32 = f.get(); + Print("H {0}",x); + + return 0; +} diff --git a/explorer/testdata/choice/generic_choice_simple_assignment.carbon b/explorer/testdata/choice/generic_choice_simple_assignment.carbon new file mode 100644 index 000000000000..6b9f00fc2f13 --- /dev/null +++ b/explorer/testdata/choice/generic_choice_simple_assignment.carbon @@ -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) { + None(ZZ), + Element(ZZ) +} + + +fn Main() -> i32 { + var f: MyOptionalElement(i32); + f = MyOptionalElement(i32).None(22); + match(f) { + case MyOptionalElement(i32).None(var x: i32) => { + return x; + } + } + return 0; +}