Basic support for parameterized interfaces. (#1220)

To make this work, introduce impl bindings into scope immediately when type-checking. We may rely on their existence before we finish type-checking the enclosing pattern.
This commit is contained in:
Richard Smith
2022-04-29 12:42:15 -07:00
committed by GitHub
parent b9538129a2
commit 3b58a1bf48
15 changed files with 513 additions and 197 deletions
+1 -1
View File
@@ -80,7 +80,7 @@ void Declaration::PrintID(llvm::raw_ostream& out) const {
switch (kind()) {
case DeclarationKind::InterfaceDeclaration: {
const auto& iface_decl = cast<InterfaceDeclaration>(*this);
out << "interface" << iface_decl.name();
out << "interface " << iface_decl.name();
break;
}
case DeclarationKind::ImplDeclaration: {
+12 -5
View File
@@ -289,30 +289,37 @@ class InterfaceDeclaration : public Declaration {
using ImplementsCarbonValueNode = void;
InterfaceDeclaration(SourceLocation source_loc, std::string name,
std::optional<Nonnull<TuplePattern*>> params,
Nonnull<GenericBinding*> self,
std::vector<Nonnull<Declaration*>> members)
: Declaration(AstNodeKind::InterfaceDeclaration, source_loc),
name_(std::move(name)),
members_(std::move(members)),
self_(self) {}
params_(std::move(params)),
self_(self),
members_(std::move(members)) {}
static auto classof(const AstNode* node) -> bool {
return InheritsFromInterfaceDeclaration(node->kind());
}
auto name() const -> const std::string& { return name_; }
auto params() const -> std::optional<Nonnull<const TuplePattern*>> {
return params_;
}
auto params() -> std::optional<Nonnull<TuplePattern*>> { return params_; }
auto self() const -> Nonnull<const GenericBinding*> { return self_; }
auto self() -> Nonnull<GenericBinding*> { return self_; }
auto members() const -> llvm::ArrayRef<Nonnull<Declaration*>> {
return members_;
}
auto self() const -> Nonnull<const GenericBinding*> { return self_; }
auto self() -> Nonnull<GenericBinding*> { return self_; }
auto value_category() const -> ValueCategory { return ValueCategory::Let; }
private:
std::string name_;
std::vector<Nonnull<Declaration*>> members_;
std::optional<Nonnull<TuplePattern*>> params_;
Nonnull<GenericBinding*> self_;
std::vector<Nonnull<Declaration*>> members_;
};
enum class ImplKind { InternalImpl, ExternalImpl };
@@ -633,20 +633,37 @@ auto Interpreter::CallFunction(const CallExpression& call,
const ClassDeclaration& class_decl = class_type.declaration();
RuntimeScope type_params_scope(&heap_);
BindingMap generic_args;
if (class_decl.type_params().has_value()) {
CHECK(PatternMatch(&(*class_decl.type_params())->value(), arg,
call.source_loc(), &type_params_scope, generic_args,
trace_stream_));
switch (phase()) {
case Phase::RunTime:
return todo_.FinishAction(arena_->New<NominalClassType>(
&class_type.declaration(), generic_args, witnesses));
case Phase::CompileTime:
return todo_.FinishAction(arena_->New<NominalClassType>(
&class_type.declaration(), generic_args, call.impls()));
}
} else {
FATAL() << "instantiation of non-generic class " << class_type;
CHECK(class_decl.type_params().has_value())
<< "instantiation of non-generic class " << class_type;
CHECK(PatternMatch(&(*class_decl.type_params())->value(), arg,
call.source_loc(), &type_params_scope, generic_args,
trace_stream_));
switch (phase()) {
case Phase::RunTime:
return todo_.FinishAction(arena_->New<NominalClassType>(
&class_decl, generic_args, witnesses));
case Phase::CompileTime:
return todo_.FinishAction(arena_->New<NominalClassType>(
&class_decl, generic_args, call.impls()));
}
}
case Value::Kind::InterfaceType: {
const InterfaceType& iface_type = cast<InterfaceType>(*fun);
const InterfaceDeclaration& iface_decl = iface_type.declaration();
RuntimeScope params_scope(&heap_);
BindingMap generic_args;
CHECK(iface_decl.params().has_value())
<< "call of unparameterized interface " << iface_type;
CHECK(PatternMatch(&(*iface_decl.params())->value(), arg,
call.source_loc(), &params_scope, generic_args,
trace_stream_));
switch (phase()) {
case Phase::RunTime:
return todo_.FinishAction(
arena_->New<InterfaceType>(&iface_decl, generic_args, witnesses));
case Phase::CompileTime:
return todo_.FinishAction(arena_->New<InterfaceType>(
&iface_decl, generic_args, call.impls()));
}
}
default:
@@ -303,6 +303,9 @@ static auto ResolveNames(Declaration& declaration, StaticScope& enclosing_scope)
auto& iface = cast<InterfaceDeclaration>(declaration);
StaticScope iface_scope;
iface_scope.AddParent(&enclosing_scope);
if (iface.params().has_value()) {
RETURN_IF_ERROR(ResolveNames(**iface.params(), iface_scope));
}
RETURN_IF_ERROR(iface_scope.Add("Self", iface.self()));
for (Nonnull<Declaration*> member : iface.members()) {
RETURN_IF_ERROR(AddExposedNames(*member, iface_scope));
+225 -153
View File
@@ -86,7 +86,6 @@ static auto IsType(Nonnull<const Value*> value) -> bool {
case Value::Kind::FunctionType:
case Value::Kind::PointerType:
case Value::Kind::StructType:
case Value::Kind::InterfaceType:
case Value::Kind::ChoiceType:
case Value::Kind::ContinuationType:
case Value::Kind::VariableType:
@@ -105,14 +104,10 @@ static auto IsType(Nonnull<const Value*> value) -> bool {
}
return true;
}
case Value::Kind::NominalClassType: {
const NominalClassType& class_type = cast<NominalClassType>(*value);
// A NominalClassType is concrete if
// 1) it is not a generic class (has no type parameters), or
// 2) it is a generic class applied to some type arguments.
return !class_type.declaration().type_params().has_value() ||
!class_type.type_args().empty();
}
case Value::Kind::NominalClassType:
return !cast<NominalClassType>(*value).IsParameterized();
case Value::Kind::InterfaceType:
return !cast<InterfaceType>(*value).IsParameterized();
}
}
@@ -152,7 +147,6 @@ static auto IsConcreteType(Nonnull<const Value*> value) -> bool {
case Value::Kind::FunctionType:
case Value::Kind::PointerType:
case Value::Kind::StructType:
case Value::Kind::InterfaceType:
case Value::Kind::ChoiceType:
case Value::Kind::ContinuationType:
case Value::Kind::VariableType:
@@ -173,14 +167,10 @@ static auto IsConcreteType(Nonnull<const Value*> value) -> bool {
}
return true;
}
case Value::Kind::NominalClassType: {
const NominalClassType& class_type = cast<NominalClassType>(*value);
// A NominalClassType is concrete if
// 1) it is not a generic class (has no type parameters), or
// 2) it is a generic class applied to some type arguments.
return !class_type.declaration().type_params().has_value() ||
!class_type.type_args().empty();
}
case Value::Kind::NominalClassType:
return !cast<NominalClassType>(*value).IsParameterized();
case Value::Kind::InterfaceType:
return !cast<InterfaceType>(*value).IsParameterized();
}
}
@@ -303,9 +293,7 @@ auto TypeChecker::IsImplicitlyConvertible(
case Value::Kind::InterfaceType:
return destination->kind() == Value::Kind::TypeType;
case Value::Kind::TypeOfClassType: {
const auto& class_type = cast<TypeOfClassType>(*source).class_type();
return ((!class_type.declaration().type_params().has_value()) ||
(!class_type.type_args().empty())) &&
return !cast<TypeOfClassType>(*source).class_type().IsParameterized() &&
destination->kind() == Value::Kind::TypeType;
}
default:
@@ -538,12 +526,25 @@ auto TypeChecker::Substitute(
}
return new_class_type;
}
case Value::Kind::InterfaceType: {
const auto& iface_type = cast<InterfaceType>(*type);
BindingMap args;
for (const auto& [name, value] : iface_type.args()) {
args[name] = Substitute(dict, value);
}
Nonnull<const InterfaceType*> new_iface_type =
arena_->New<InterfaceType>(&iface_type.declaration(), args);
if (trace_stream_) {
**trace_stream_ << "substitution: " << iface_type << " => "
<< *new_iface_type << "\n";
}
return new_iface_type;
}
case Value::Kind::StaticArrayType:
case Value::Kind::AutoType:
case Value::Kind::IntType:
case Value::Kind::BoolType:
case Value::Kind::TypeType:
case Value::Kind::InterfaceType:
case Value::Kind::ChoiceType:
case Value::Kind::ContinuationType:
case Value::Kind::StringType:
@@ -634,11 +635,12 @@ auto TypeChecker::SatisfyImpls(
BindingMap& deduced_type_args, ImplExpMap& impls) const
-> ErrorOr<Success> {
for (Nonnull<const ImplBinding*> impl_binding : impl_bindings) {
ASSIGN_OR_RETURN(
Nonnull<Expression*> impl,
impl_scope.Resolve(impl_binding->interface(),
deduced_type_args[impl_binding->type_var()],
source_loc, *this));
Nonnull<const Value*> interface =
Substitute(deduced_type_args, impl_binding->interface());
ASSIGN_OR_RETURN(Nonnull<Expression*> impl,
impl_scope.Resolve(
interface, deduced_type_args[impl_binding->type_var()],
source_loc, *this));
impls.emplace(impl_binding, impl);
}
return Success();
@@ -839,11 +841,10 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
FindMember(access.field(), iface_decl.members());
member.has_value()) {
const Value& member_type = (*member)->static_type();
std::map<Nonnull<const GenericBinding*>, Nonnull<const Value*>>
self_map;
self_map[iface_decl.self()] = &var_type;
BindingMap binding_map = iface_type.args();
binding_map[iface_decl.self()] = &var_type;
Nonnull<const Value*> inst_member_type =
Substitute(self_map, &member_type);
Substitute(binding_map, &member_type);
access.set_static_type(inst_member_type);
CHECK(var_type.binding().impl_binding().has_value());
access.set_impl(*var_type.binding().impl_binding());
@@ -877,8 +878,10 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
FindMember(access.field(), iface_decl.members());
member.has_value()) {
const Value& member_type = (*member)->static_type();
BindingMap binding_map = iface_type.args();
binding_map[iface_decl.self()] = &var_type;
Nonnull<const Value*> inst_member_type =
Substitute({{iface_decl.self(), &var_type}}, &member_type);
Substitute(binding_map, &member_type);
access.set_static_type(inst_member_type);
CHECK(var_type.binding().impl_binding().has_value());
access.set_impl(*var_type.binding().impl_binding());
@@ -1060,8 +1063,7 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
cast<TypeOfClassType>(call.function().static_type()).class_type();
const ClassDeclaration& class_decl = class_type.declaration();
BindingMap generic_args;
if (class_decl.type_params().has_value() &&
class_type.type_args().empty()) {
if (class_type.IsParameterized()) {
if (trace_stream_) {
**trace_stream_ << "pattern matching type params and args\n";
}
@@ -1089,23 +1091,11 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
if (binding->impl_binding().has_value()) {
Nonnull<const ImplBinding*> impl_binding =
*binding->impl_binding();
switch (impl_binding->interface()->kind()) {
case Value::Kind::InterfaceType: {
ASSIGN_OR_RETURN(
Nonnull<Expression*> impl,
impl_scope.Resolve(impl_binding->interface(),
generic_args[binding],
call.source_loc(), *this));
impls.emplace(impl_binding, impl);
break;
}
case Value::Kind::TypeType:
break;
default:
return CompilationError(e->source_loc())
<< "unexpected type of deduced parameter "
<< *impl_binding->interface();
}
ASSIGN_OR_RETURN(Nonnull<Expression*> impl,
impl_scope.Resolve(impl_binding->interface(),
generic_args[binding],
call.source_loc(), *this));
impls.emplace(impl_binding, impl);
}
}
Nonnull<NominalClassType*> inst_class_type =
@@ -1115,6 +1105,59 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
call.set_value_category(ValueCategory::Let);
return Success();
}
case Value::Kind::TypeOfInterfaceType: {
// This case handles the application of a parameterized class to
// its arguments, such as OrderedWith(i32).
// FIXME: This is very repetitive with the corresponding case for
// parameterized classes.
const InterfaceType& iface_type =
cast<TypeOfInterfaceType>(call.function().static_type())
.interface_type();
const InterfaceDeclaration& iface_decl = iface_type.declaration();
BindingMap generic_args;
if (iface_type.IsParameterized()) {
if (trace_stream_) {
**trace_stream_ << "pattern matching interface params and args\n";
}
RETURN_IF_ERROR(ExpectType(call.source_loc(), "call",
&(*iface_decl.params())->static_type(),
&call.argument().static_type()));
ASSIGN_OR_RETURN(
Nonnull<const Value*> arg,
InterpExp(&call.argument(), arena_, trace_stream_));
CHECK(PatternMatch(&(*iface_decl.params())->value(), arg,
call.source_loc(), std::nullopt, generic_args,
trace_stream_));
} else if (!iface_type.args().empty()) {
return CompilationError(call.source_loc())
<< "attempt to provide arguments to parameterized interface "
<< "twice: " << *e;
} else {
return CompilationError(call.source_loc())
<< "attempt to provide arguments to non-parameterized "
<< "interface: " << *e;
}
// Find impls for all the impl bindings of the interface.
ImplExpMap impls;
for (const auto& [binding, val] : generic_args) {
if (binding->impl_binding().has_value()) {
Nonnull<const ImplBinding*> impl_binding =
*binding->impl_binding();
ASSIGN_OR_RETURN(Nonnull<Expression*> impl,
impl_scope.Resolve(impl_binding->interface(),
generic_args[binding],
call.source_loc(), *this));
impls.emplace(impl_binding, impl);
}
}
Nonnull<InterfaceType*> inst_iface_type =
arena_->New<InterfaceType>(&iface_decl, generic_args, impls);
call.set_impls(impls);
call.set_static_type(
arena_->New<TypeOfInterfaceType>(inst_iface_type));
call.set_value_category(ValueCategory::Let);
return Success();
}
default: {
return CompilationError(e->source_loc())
<< "in call, expected a function\n"
@@ -1218,33 +1261,32 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
}
}
void TypeChecker::AddPatternImpls(Nonnull<Pattern*> p, ImplScope& impl_scope) {
void TypeChecker::CollectImplBindingsInPattern(
Nonnull<const Pattern*> p,
std::vector<Nonnull<const ImplBinding*>>& impl_bindings) {
switch (p->kind()) {
case PatternKind::GenericBinding: {
auto& binding = cast<GenericBinding>(*p);
CHECK(binding.impl_binding().has_value());
Nonnull<const ImplBinding*> impl_binding = *binding.impl_binding();
auto impl_id = arena_->New<IdentifierExpression>(p->source_loc(), "impl");
impl_id->set_value_node(impl_binding);
impl_scope.Add(impl_binding->interface(),
*impl_binding->type_var()->symbolic_identity(), impl_id);
if (binding.impl_binding().has_value()) {
impl_bindings.push_back(*binding.impl_binding());
}
return;
}
case PatternKind::TuplePattern: {
auto& tuple = cast<TuplePattern>(*p);
for (Nonnull<Pattern*> field : tuple.fields()) {
AddPatternImpls(field, impl_scope);
for (Nonnull<const Pattern*> field : tuple.fields()) {
CollectImplBindingsInPattern(field, impl_bindings);
}
return;
}
case PatternKind::AlternativePattern: {
auto& alternative = cast<AlternativePattern>(*p);
AddPatternImpls(&alternative.arguments(), impl_scope);
CollectImplBindingsInPattern(&alternative.arguments(), impl_bindings);
return;
}
case PatternKind::VarPattern: {
auto& var_pattern = cast<VarPattern>(*p);
AddPatternImpls(&var_pattern.pattern(), impl_scope);
CollectImplBindingsInPattern(&var_pattern.pattern(), impl_bindings);
return;
}
case PatternKind::ExpressionPattern:
@@ -1254,9 +1296,34 @@ void TypeChecker::AddPatternImpls(Nonnull<Pattern*> p, ImplScope& impl_scope) {
}
}
void TypeChecker::BringPatternImplsIntoScope(Nonnull<const Pattern*> p,
ImplScope& impl_scope) {
std::vector<Nonnull<const ImplBinding*>> impl_bindings;
CollectImplBindingsInPattern(p, impl_bindings);
BringImplsIntoScope(impl_bindings, impl_scope);
}
void TypeChecker::BringImplsIntoScope(
llvm::ArrayRef<Nonnull<const ImplBinding*>> impl_bindings,
ImplScope& impl_scope) {
for (Nonnull<const ImplBinding*> impl_binding : impl_bindings) {
BringImplIntoScope(impl_binding, impl_scope);
}
}
void TypeChecker::BringImplIntoScope(Nonnull<const ImplBinding*> impl_binding,
ImplScope& impl_scope) {
CHECK(impl_binding->type_var()->symbolic_identity().has_value());
auto impl_id =
arena_->New<IdentifierExpression>(impl_binding->source_loc(), "impl");
impl_id->set_value_node(impl_binding);
impl_scope.Add(impl_binding->interface(),
*impl_binding->type_var()->symbolic_identity(), impl_id);
}
auto TypeChecker::TypeCheckPattern(
Nonnull<Pattern*> p, std::optional<Nonnull<const Value*>> expected,
const ImplScope& impl_scope, ValueCategory enclosing_value_category)
ImplScope& impl_scope, ValueCategory enclosing_value_category)
-> ErrorOr<Success> {
if (trace_stream_) {
**trace_stream_ << "checking pattern " << *p;
@@ -1324,10 +1391,14 @@ auto TypeChecker::TypeCheckPattern(
ASSIGN_OR_RETURN(Nonnull<const Value*> val,
InterpPattern(&binding, arena_, trace_stream_));
binding.set_symbolic_identity(val);
Nonnull<ImplBinding*> impl_binding = arena_->New<ImplBinding>(
binding.source_loc(), &binding, &binding.static_type());
binding.set_impl_binding(impl_binding);
SetValue(&binding, val);
if (isa<InterfaceType>(type)) {
Nonnull<ImplBinding*> impl_binding =
arena_->New<ImplBinding>(binding.source_loc(), &binding, type);
binding.set_impl_binding(impl_binding);
BringImplIntoScope(impl_binding, impl_scope);
}
return Success();
}
case PatternKind::TuplePattern: {
@@ -1428,10 +1499,12 @@ auto TypeChecker::TypeCheckStmt(Nonnull<Statement*> s,
RETURN_IF_ERROR(TypeCheckExp(&match.expression(), impl_scope));
std::vector<Match::Clause> new_clauses;
for (auto& clause : match.clauses()) {
ImplScope clause_scope;
clause_scope.AddParent(&impl_scope);
RETURN_IF_ERROR(TypeCheckPattern(&clause.pattern(),
&match.expression().static_type(),
impl_scope, ValueCategory::Let));
RETURN_IF_ERROR(TypeCheckStmt(&clause.statement(), impl_scope));
clause_scope, ValueCategory::Let));
RETURN_IF_ERROR(TypeCheckStmt(&clause.statement(), clause_scope));
}
return Success();
}
@@ -1458,7 +1531,16 @@ auto TypeChecker::TypeCheckStmt(Nonnull<Statement*> s,
auto& var = cast<VariableDefinition>(*s);
RETURN_IF_ERROR(TypeCheckExp(&var.init(), impl_scope));
const Value& rhs_ty = var.init().static_type();
RETURN_IF_ERROR(TypeCheckPattern(&var.pattern(), &rhs_ty, impl_scope,
// FIXME: If the pattern contains a binding that implies a new impl is
// available, should that remain in scope for as long as its binding?
// ```
// var a: (T:! Widget) = ...;
// // Is the `impl T as Widget` in scope here?
// a.(Widget.F)();
// ```
ImplScope var_scope;
var_scope.AddParent(&impl_scope);
RETURN_IF_ERROR(TypeCheckPattern(&var.pattern(), &rhs_ty, var_scope,
var.value_category()));
return Success();
}
@@ -1604,45 +1686,6 @@ auto TypeChecker::ExpectReturnOnAllPaths(
}
}
auto TypeChecker::CreateImplBindings(
llvm::ArrayRef<Nonnull<GenericBinding*>> deduced_parameters,
SourceLocation source_loc,
std::vector<Nonnull<const ImplBinding*>>& impl_bindings)
-> ErrorOr<Success> {
for (Nonnull<GenericBinding*> deduced : deduced_parameters) {
switch (deduced->static_type().kind()) {
case Value::Kind::InterfaceType: {
Nonnull<ImplBinding*> impl_binding = arena_->New<ImplBinding>(
deduced->source_loc(), deduced, &deduced->static_type());
deduced->set_impl_binding(impl_binding);
impl_binding->set_static_type(&deduced->static_type());
impl_bindings.push_back(impl_binding);
break;
}
case Value::Kind::TypeType:
// No `impl` binding needed for type parameter with bound `Type`.
break;
default:
return CompilationError(source_loc)
<< "unexpected type of deduced parameter "
<< deduced->static_type();
}
}
return Success();
}
void TypeChecker::BringImplsIntoScope(
llvm::ArrayRef<Nonnull<const ImplBinding*>> impl_bindings, ImplScope& scope,
SourceLocation source_loc) {
for (Nonnull<const ImplBinding*> impl_binding : impl_bindings) {
CHECK(impl_binding->type_var()->symbolic_identity().has_value());
auto impl_id = arena_->New<IdentifierExpression>(source_loc, "impl");
impl_id->set_value_node(impl_binding);
scope.Add(impl_binding->interface(),
*impl_binding->type_var()->symbolic_identity(), impl_id);
}
}
// TODO: Add checking to function definitions to ensure that
// all deduced type parameters will be deduced.
auto TypeChecker::DeclareFunctionDeclaration(Nonnull<FunctionDeclaration*> f,
@@ -1651,30 +1694,25 @@ auto TypeChecker::DeclareFunctionDeclaration(Nonnull<FunctionDeclaration*> f,
if (trace_stream_) {
**trace_stream_ << "** declaring function " << f->name() << "\n";
}
// Bring the deduced parameters into scope.
for (Nonnull<GenericBinding*> deduced : f->deduced_parameters()) {
RETURN_IF_ERROR(TypeCheckExp(&deduced->type(), enclosing_scope));
deduced->set_symbolic_identity(arena_->New<VariableType>(deduced));
ASSIGN_OR_RETURN(Nonnull<const Value*> type_of_type,
InterpExp(&deduced->type(), arena_, trace_stream_));
deduced->set_static_type(type_of_type);
}
// Create the impl_bindings.
std::vector<Nonnull<const ImplBinding*>> impl_bindings;
RETURN_IF_ERROR(CreateImplBindings(f->deduced_parameters(), f->source_loc(),
impl_bindings));
// Bring the impl bindings into scope.
ImplScope function_scope;
function_scope.AddParent(&enclosing_scope);
BringImplsIntoScope(impl_bindings, function_scope, f->source_loc());
std::vector<Nonnull<const ImplBinding*>> impl_bindings;
// Bring the deduced parameters into scope.
for (Nonnull<GenericBinding*> deduced : f->deduced_parameters()) {
RETURN_IF_ERROR(TypeCheckPattern(deduced, std::nullopt, function_scope,
ValueCategory::Let));
CollectImplBindingsInPattern(deduced, impl_bindings);
}
// Type check the receiver pattern.
if (f->is_method()) {
RETURN_IF_ERROR(TypeCheckPattern(&f->me_pattern(), std::nullopt,
function_scope, ValueCategory::Let));
CollectImplBindingsInPattern(&f->me_pattern(), impl_bindings);
}
// Type check the parameter pattern.
RETURN_IF_ERROR(TypeCheckPattern(&f->param_pattern(), std::nullopt,
function_scope, ValueCategory::Let));
CollectImplBindingsInPattern(&f->param_pattern(), impl_bindings);
// Evaluate the return type, if we can do so without examining the body.
if (std::optional<Nonnull<Expression*>> return_expression =
@@ -1737,11 +1775,11 @@ auto TypeChecker::TypeCheckFunctionDeclaration(Nonnull<FunctionDeclaration*> f,
// if f->return_term().is_auto(), the function body was already
// type checked in DeclareFunctionDeclaration.
if (f->body().has_value() && !f->return_term().is_auto()) {
// Bring the impl's into scope.
// Bring the impls into scope.
ImplScope function_scope;
function_scope.AddParent(&impl_scope);
BringImplsIntoScope(cast<FunctionType>(f->static_type()).impl_bindings(),
function_scope, f->source_loc());
function_scope);
if (trace_stream_)
**trace_stream_ << function_scope;
RETURN_IF_ERROR(TypeCheckStmt(*f->body(), function_scope));
@@ -1766,7 +1804,6 @@ auto TypeChecker::DeclareClassDeclaration(Nonnull<ClassDeclaration*> class_decl,
class_scope.AddParent(&enclosing_scope);
RETURN_IF_ERROR(TypeCheckPattern(*class_decl->type_params(), std::nullopt,
class_scope, ValueCategory::Let));
AddPatternImpls(*class_decl->type_params(), class_scope);
if (trace_stream_) {
**trace_stream_ << class_scope;
}
@@ -1779,8 +1816,6 @@ auto TypeChecker::DeclareClassDeclaration(Nonnull<ClassDeclaration*> class_decl,
for (Nonnull<Declaration*> m : class_decl->members()) {
RETURN_IF_ERROR(DeclareDeclaration(m, class_scope));
}
// TODO: when/how to bring impls in generic class into scope?
} else {
// The declarations of the members may refer to the class, so we
// must set the constant value of the class and its static type
@@ -1810,7 +1845,7 @@ auto TypeChecker::TypeCheckClassDeclaration(
ImplScope class_scope;
class_scope.AddParent(&impl_scope);
if (class_decl->type_params().has_value()) {
AddPatternImpls(*class_decl->type_params(), class_scope);
BringPatternImplsIntoScope(*class_decl->type_params(), class_scope);
}
if (trace_stream_) {
**trace_stream_ << class_scope;
@@ -1828,15 +1863,33 @@ auto TypeChecker::TypeCheckClassDeclaration(
auto TypeChecker::DeclareInterfaceDeclaration(
Nonnull<InterfaceDeclaration*> iface_decl, ImplScope& enclosing_scope)
-> ErrorOr<Success> {
if (trace_stream_) {
**trace_stream_ << "** declaring interface " << iface_decl->name() << "\n";
}
ImplScope iface_scope;
iface_scope.AddParent(&enclosing_scope);
if (iface_decl->params().has_value()) {
RETURN_IF_ERROR(TypeCheckPattern(*iface_decl->params(), std::nullopt,
iface_scope, ValueCategory::Let));
if (trace_stream_) {
**trace_stream_ << iface_scope;
}
}
Nonnull<InterfaceType*> iface_type = arena_->New<InterfaceType>(iface_decl);
SetConstantValue(iface_decl, iface_type);
iface_decl->set_static_type(arena_->New<TypeOfInterfaceType>(iface_type));
// Process the Self parameter.
RETURN_IF_ERROR(TypeCheckPattern(iface_decl->self(), std::nullopt,
enclosing_scope, ValueCategory::Let));
iface_scope, ValueCategory::Let));
for (Nonnull<Declaration*> m : iface_decl->members()) {
RETURN_IF_ERROR(DeclareDeclaration(m, enclosing_scope));
RETURN_IF_ERROR(DeclareDeclaration(m, iface_scope));
}
if (trace_stream_) {
**trace_stream_ << "** finished declaring interface " << iface_decl->name()
<< "\n";
}
return Success();
}
@@ -1844,8 +1897,23 @@ auto TypeChecker::DeclareInterfaceDeclaration(
auto TypeChecker::TypeCheckInterfaceDeclaration(
Nonnull<InterfaceDeclaration*> iface_decl, const ImplScope& impl_scope)
-> ErrorOr<Success> {
if (trace_stream_) {
**trace_stream_ << "** checking interface " << iface_decl->name() << "\n";
}
ImplScope iface_scope;
iface_scope.AddParent(&impl_scope);
if (iface_decl->params().has_value()) {
BringPatternImplsIntoScope(*iface_decl->params(), iface_scope);
}
if (trace_stream_) {
**trace_stream_ << iface_scope;
}
for (Nonnull<Declaration*> m : iface_decl->members()) {
RETURN_IF_ERROR(TypeCheckDeclaration(m, impl_scope));
RETURN_IF_ERROR(TypeCheckDeclaration(m, iface_scope));
}
if (trace_stream_) {
**trace_stream_ << "** finished checking interface " << iface_decl->name()
<< "\n";
}
return Success();
}
@@ -1857,29 +1925,35 @@ auto TypeChecker::DeclareImplDeclaration(Nonnull<ImplDeclaration*> impl_decl,
**trace_stream_ << "declaring " << *impl_decl << "\n";
}
RETURN_IF_ERROR(TypeCheckExp(&impl_decl->interface(), enclosing_scope));
ASSIGN_OR_RETURN(Nonnull<const Value*> iface_type,
ASSIGN_OR_RETURN(Nonnull<const Value*> written_iface_type,
InterpExp(&impl_decl->interface(), arena_, trace_stream_));
const auto& iface_decl = cast<InterfaceType>(*iface_type).declaration();
const auto* iface_type = dyn_cast<InterfaceType>(written_iface_type);
if (!iface_type) {
return CompilationError(impl_decl->interface().source_loc())
<< "expected constraint after `as`, found value of type "
<< *written_iface_type;
}
if (iface_type->IsParameterized()) {
return CompilationError(impl_decl->interface().source_loc())
<< "missing arguments for parameterized interface";
}
const auto& iface_decl = iface_type->declaration();
impl_decl->set_interface_type(iface_type);
ImplScope impl_scope;
impl_scope.AddParent(&enclosing_scope);
std::vector<Nonnull<const ImplBinding*>> impl_bindings;
// Bring the deduced parameters into scope.
for (Nonnull<GenericBinding*> deduced : impl_decl->deduced_parameters()) {
RETURN_IF_ERROR(TypeCheckExp(&deduced->type(), enclosing_scope));
deduced->set_symbolic_identity(arena_->New<VariableType>(deduced));
ASSIGN_OR_RETURN(Nonnull<const Value*> type_of_type,
InterpExp(&deduced->type(), arena_, trace_stream_));
deduced->set_static_type(type_of_type);
RETURN_IF_ERROR(TypeCheckPattern(deduced, std::nullopt, impl_scope,
ValueCategory::Let));
CollectImplBindingsInPattern(deduced, impl_bindings);
}
// Create the impl_bindings.
std::vector<Nonnull<const ImplBinding*>> impl_bindings;
RETURN_IF_ERROR(CreateImplBindings(impl_decl->deduced_parameters(),
impl_decl->source_loc(), impl_bindings));
impl_decl->set_impl_bindings(impl_bindings);
// Bring the impl bindings into scope for the impl body.
ImplScope impl_scope;
impl_scope.AddParent(&enclosing_scope);
BringImplsIntoScope(impl_bindings, impl_scope, impl_decl->source_loc());
// Check and interpret the impl_type
RETURN_IF_ERROR(TypeCheckExp(impl_decl->impl_type(), impl_scope));
ASSIGN_OR_RETURN(Nonnull<const Value*> impl_type_value,
@@ -1902,11 +1976,10 @@ auto TypeChecker::DeclareImplDeclaration(Nonnull<ImplDeclaration*> impl_decl,
if (std::optional<Nonnull<const Declaration*>> mem =
FindMember(*mem_name, impl_decl->members());
mem.has_value()) {
std::map<Nonnull<const GenericBinding*>, Nonnull<const Value*>>
self_map;
self_map[iface_decl.self()] = impl_type_value;
BindingMap binding_map = iface_type->args();
binding_map[iface_decl.self()] = impl_type_value;
Nonnull<const Value*> iface_mem_type =
Substitute(self_map, &m->static_type());
Substitute(binding_map, &m->static_type());
RETURN_IF_ERROR(ExpectType((*mem)->source_loc(),
"member of implementation", iface_mem_type,
&(*mem)->static_type()));
@@ -1930,11 +2003,10 @@ auto TypeChecker::TypeCheckImplDeclaration(Nonnull<ImplDeclaration*> impl_decl,
if (trace_stream_) {
**trace_stream_ << "checking " << *impl_decl << "\n";
}
// Bring the impl's from the parameters into scope.
// Bring the impls from the parameters into scope.
ImplScope impl_scope;
impl_scope.AddParent(&enclosing_scope);
BringImplsIntoScope(impl_decl->impl_bindings(), impl_scope,
impl_decl->source_loc());
BringImplsIntoScope(impl_decl->impl_bindings(), impl_scope);
for (Nonnull<Declaration*> m : impl_decl->members()) {
RETURN_IF_ERROR(TypeCheckDeclaration(m, impl_scope));
}
+20 -17
View File
@@ -64,9 +64,11 @@ class TypeChecker {
// `expected` is the type that this pattern is expected to have, if the
// surrounding context gives us that information. Otherwise, it is
// nullopt.
//
// `impl_scope` is extended with all impls implied by the pattern.
auto TypeCheckPattern(Nonnull<Pattern*> p,
std::optional<Nonnull<const Value*>> expected,
const ImplScope& impl_scope,
ImplScope& impl_scope,
ValueCategory enclosing_value_category)
-> ErrorOr<Success>;
@@ -104,8 +106,24 @@ class TypeChecker {
const ImplScope& enclosing_scope)
-> ErrorOr<Success>;
// Find all of the ImplBindings in the given pattern. The pattern is required
// to have already been type-checked.
void CollectImplBindingsInPattern(
Nonnull<const Pattern*> p,
std::vector<Nonnull<const ImplBinding*>>& impl_bindings);
// Add the impls from the pattern into the given `impl_scope`.
void AddPatternImpls(Nonnull<Pattern*> p, ImplScope& impl_scope);
void BringPatternImplsIntoScope(Nonnull<const Pattern*> p,
ImplScope& impl_scope);
// Add the given ImplBinding to the given `impl_scope`.
void BringImplIntoScope(Nonnull<const ImplBinding*> impl_binding,
ImplScope& impl_scope);
// Add all of the `impl_bindings` into the `scope`.
void BringImplsIntoScope(
llvm::ArrayRef<Nonnull<const ImplBinding*>> impl_bindings,
ImplScope& scope);
// Checks the statements and (runtime) expressions within the
// declaration, such as the body of a function.
@@ -184,21 +202,6 @@ class TypeChecker {
Nonnull<const Value*>>& dict,
Nonnull<const Value*> type) const -> Nonnull<const Value*>;
// For each deduced type parameter of a generic that has a
// non-trivial type (such as an interface), create an impl binding
// to serve as the parameter for passing a witness at runtime for
// the required impl.
auto CreateImplBindings(
llvm::ArrayRef<Nonnull<GenericBinding*>> deduced_parameters,
SourceLocation source_loc,
std::vector<Nonnull<const ImplBinding*>>& impl_bindings)
-> ErrorOr<Success>;
// Add all of the `impl_bindings` into the `scope`.
void BringImplsIntoScope(
llvm::ArrayRef<Nonnull<const ImplBinding*>> impl_bindings,
ImplScope& scope, SourceLocation source_loc);
// Find impls that satisfy all of the `impl_bindings`, but with the
// type variables in the `impl_bindings` replaced by the argument
// type in `deduced_type_args`. The results are placed in the
+20 -4
View File
@@ -334,6 +334,14 @@ void Value::Print(llvm::raw_ostream& out) const {
case Value::Kind::InterfaceType: {
const auto& iface_type = cast<InterfaceType>(*this);
out << "interface " << iface_type.declaration().name();
if (!iface_type.args().empty()) {
out << "(";
llvm::ListSeparator sep;
for (const auto& [bind, val] : iface_type.args()) {
out << sep << bind->name() << " = " << *val;
}
out << ")";
}
break;
}
case Value::Kind::Witness: {
@@ -456,15 +464,23 @@ auto TypeEqual(Nonnull<const Value*> t1, Nonnull<const Value*> t2) -> bool {
}
for (const auto& [ty_var1, ty1] :
cast<NominalClassType>(*t1).type_args()) {
if (!TypeEqual(ty1,
cast<NominalClassType>(*t2).type_args().at(ty_var1))) {
if (!ValueEqual(ty1,
cast<NominalClassType>(*t2).type_args().at(ty_var1))) {
return false;
}
}
return true;
case Value::Kind::InterfaceType:
return cast<InterfaceType>(*t1).declaration().name() ==
cast<InterfaceType>(*t2).declaration().name();
if (cast<InterfaceType>(*t1).declaration().name() !=
cast<InterfaceType>(*t2).declaration().name()) {
return false;
}
for (const auto& [ty_var1, ty1] : cast<InterfaceType>(*t1).args()) {
if (!ValueEqual(ty1, cast<InterfaceType>(*t2).args().at(ty_var1))) {
return false;
}
}
return true;
case Value::Kind::ChoiceType:
return cast<ChoiceType>(*t1).name() == cast<ChoiceType>(*t2).name();
case Value::Kind::TupleValue: {
+33
View File
@@ -560,6 +560,12 @@ class NominalClassType : public Value {
// instantiated runtime type of a generic class.
auto witnesses() const -> const ImplWitnessMap& { return witnesses_; }
// Returns whether this a parameterized class. That is, a class with
// parameters and no corresponding arguments.
auto IsParameterized() const -> bool {
return declaration_->type_params().has_value() && type_args_.empty();
}
// Returns the value of the function named `name` in this class, or
// nullopt if there is no such function.
auto FindFunction(const std::string& name) const
@@ -582,6 +588,21 @@ class InterfaceType : public Value {
public:
explicit InterfaceType(Nonnull<const InterfaceDeclaration*> declaration)
: Value(Kind::InterfaceType), declaration_(declaration) {}
explicit InterfaceType(Nonnull<const InterfaceDeclaration*> declaration,
const BindingMap& args)
: Value(Kind::InterfaceType), declaration_(declaration), args_(args) {}
explicit InterfaceType(Nonnull<const InterfaceDeclaration*> declaration,
const BindingMap& args, const ImplExpMap& impls)
: Value(Kind::InterfaceType),
declaration_(declaration),
args_(args),
impls_(impls) {}
explicit InterfaceType(Nonnull<const InterfaceDeclaration*> declaration,
const BindingMap& args, const ImplWitnessMap& wits)
: Value(Kind::InterfaceType),
declaration_(declaration),
args_(args),
witnesses_(wits) {}
static auto classof(const Value* value) -> bool {
return value->kind() == Kind::InterfaceType;
@@ -590,9 +611,21 @@ class InterfaceType : public Value {
auto declaration() const -> const InterfaceDeclaration& {
return *declaration_;
}
auto args() const -> const BindingMap& { return args_; }
// FIXME: These aren't used for anything yet.
auto impls() const -> const ImplExpMap& { return impls_; }
auto witnesses() const -> const ImplWitnessMap& { return witnesses_; }
auto IsParameterized() const -> bool {
return declaration_->params().has_value() && args_.empty();
}
private:
Nonnull<const InterfaceDeclaration*> declaration_;
BindingMap args_;
ImplExpMap impls_;
ImplWitnessMap witnesses_;
};
// The witness table for an impl.
+3 -2
View File
@@ -909,12 +909,13 @@ declaration:
$$ = arena->New<VariableDeclaration>(context.source_loc(), $2, $4,
ValueCategory::Let);
}
| INTERFACE identifier LEFT_CURLY_BRACE declaration_list RIGHT_CURLY_BRACE
| INTERFACE identifier type_params LEFT_CURLY_BRACE declaration_list RIGHT_CURLY_BRACE
{
auto ty_ty = arena -> New<TypeTypeLiteral>(context.source_loc());
auto self =
arena -> New<GenericBinding>(context.source_loc(), "Self", ty_ty);
$$ = arena->New<InterfaceDeclaration>(context.source_loc(), $2, self, $4);
$$ = arena->New<InterfaceDeclaration>(context.source_loc(), $2, $3, self,
$5);
}
| impl_kind IMPL impl_deduced_params expression AS expression LEFT_CURLY_BRACE declaration_list RIGHT_CURLY_BRACE
{
@@ -7,7 +7,7 @@
// RUN: %{not} %{executable_semantics} --parser_debug --trace_file=- %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s
// AUTOUPDATE: %{executable_semantics} %s
// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/generic_class/fail_bad_parameter_type.carbon:15: unexpected type of deduced parameter i32
// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/generic_class/fail_bad_parameter_type.carbon:28: type error in call: '(Type)' is not implicitly convertible to '(i32)'
package ExecutableSemanticsTest api;
@@ -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} --parser_debug --trace_file=- %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s
// AUTOUPDATE: %{executable_semantics} %s
// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/impl/fail_impl_as_not_constraint.carbon:14: expected constraint after `as`, found value of type i32
package ExecutableSemanticsTest api;
external impl i32 as i32 {}
fn Main() -> i32 {
}
@@ -0,0 +1,20 @@
// 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} --parser_debug --trace_file=- %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s
// AUTOUPDATE: %{executable_semantics} %s
// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/impl/fail_impl_as_parameterized.carbon:17: missing arguments for parameterized interface
package ExecutableSemanticsTest api;
interface Vector(Scalar:! Type) {
}
external impl i32 as Vector {}
fn Main() -> i32 {
}
+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} --parser_debug --trace_file=- %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s
// AUTOUPDATE: %{executable_semantics} %s
// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/impl/impl_as.carbon:14: expected constraint after `as`, found value of type i32
package ExecutableSemanticsTest api;
external impl i32 as i32 {}
fn Main() -> i32 {
}
@@ -0,0 +1,66 @@
// 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} --parser_debug --trace_file=- %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s
// AUTOUPDATE: %{executable_semantics} %s
// CHECK: result: 0
package ExecutableSemanticsTest api;
interface AddMul {
fn Add[me: Self](o: Self) -> Self;
fn Mul[me: Self](o: Self) -> Self;
}
external impl i32 as AddMul {
fn Add[me: i32](o: i32) -> i32 {
return me + o;
}
fn Mul[me: i32](o: i32) -> i32 {
return me * o;
}
}
class Holder(T:! AddMul) {
var v: T;
}
interface Vector(Scalar:! AddMul) {
fn Zero() -> Self;
fn Add[me: Self](b: Self) -> Self;
fn Scale[me: Self](v: Scalar) -> Self;
fn Hold[me: Self](v: Scalar) -> Holder(Scalar);
}
class Point {
var x: i32;
var y: i32;
impl Point as Vector(i32) {
fn Zero() -> Point {
return {.x = 0, .y = 0};
}
fn Add[me: Point](b: Point) -> Point {
return {.x = me.x + b.x, .y = me.y + b.y};
}
fn Scale[me: Point](v: i32) -> Point {
return {.x = me.x * v, .y = me.y * v};
}
fn Hold[me: Point](v: i32) -> Holder(i32) {
return {.v = v};
}
}
}
fn AddAndScaleGeneric[T:! AddMul, U:! Vector(T)](a: U, s: T) -> U {
return a.Add(U.Zero()).Scale(a.Hold(s).v);
}
fn Main() -> i32 {
var a: Point = {.x = 2, .y = 1};
var p: Point = AddAndScaleGeneric(a, 5);
return p.x - 10;
}
@@ -0,0 +1,44 @@
// 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} --parser_debug --trace_file=- %s 2>&1 | \
// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s
// AUTOUPDATE: %{executable_semantics} %s
// CHECK: result: 0
package ExecutableSemanticsTest api;
interface Vector(Scalar:! Type) {
fn Zero() -> Self;
fn Add[me: Self](b: Self) -> Self;
fn Scale[me: Self](v: Scalar) -> Self;
}
class Point {
var x: i32;
var y: i32;
impl Point as Vector(i32) {
fn Zero() -> Point {
return {.x = 0, .y = 0};
}
fn Add[me: Point](b: Point) -> Point {
return {.x = me.x + b.x, .y = me.y + b.y};
}
fn Scale[me: Point](v: i32) -> Point {
return {.x = me.x * v, .y = me.y * v};
}
}
}
fn AddAndScaleGeneric[T:! Type, U:! Vector(T)](a: U, s: T) -> U {
return a.Add(U.Zero()).Scale(s);
}
fn Main() -> i32 {
var a: Point = {.x = 2, .y = 1};
var p: Point = AddAndScaleGeneric(a, 5);
return p.x - 10;
}