mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-01 22:02:45 +01:00
Parameterized impl declarations (#1189)
* parameterized impls, first step * bug fixes, comments, etc. * added another test case, fix a bug in impl lookup * simplify tests, removing tuple stuff * don't create impl bindings for -bound implicit parameters * remove a redundant 'private' * CamelCase * add missing backtick * add a comment
This commit is contained in:
@@ -93,6 +93,15 @@ class Interpreter {
|
||||
SourceLocation source_loc) const
|
||||
-> ErrorOr<Nonnull<const Value*>>;
|
||||
|
||||
// Evaluate an impl expression to produce a witness, or signal an
|
||||
// error.
|
||||
//
|
||||
// An impl expression is either
|
||||
// 1) an IdentifierExpression whose value_node is an impl declaration, or
|
||||
// 2) an InstantiateImpl expression.
|
||||
auto EvalImplExp(Nonnull<const Expression*> exp) const
|
||||
-> ErrorOr<Nonnull<const Witness*>>;
|
||||
|
||||
// Instantiate a type by replacing all type variables that occur inside the
|
||||
// type by the current values of those variables.
|
||||
//
|
||||
@@ -104,6 +113,12 @@ class Interpreter {
|
||||
SourceLocation source_loc) const
|
||||
-> ErrorOr<Nonnull<const Value*>>;
|
||||
|
||||
// Call the function `fun` with the given `arg` and the `witnesses`
|
||||
// for the function's impl bindings.
|
||||
auto CallFunction(const CallExpression& call, Nonnull<const Value*> fun,
|
||||
Nonnull<const Value*> arg, const ImplWitnessMap& witnesses)
|
||||
-> ErrorOr<Success>;
|
||||
|
||||
void PrintState(llvm::raw_ostream& out);
|
||||
|
||||
Phase phase() const { return phase_; }
|
||||
@@ -365,23 +380,49 @@ auto Interpreter::StepLvalue() -> ErrorOr<Success> {
|
||||
case ExpressionKind::IntrinsicExpression:
|
||||
case ExpressionKind::IfExpression:
|
||||
case ExpressionKind::ArrayTypeLiteral:
|
||||
case ExpressionKind::InstantiateImpl:
|
||||
FATAL() << "Can't treat expression as lvalue: " << exp;
|
||||
case ExpressionKind::UnimplementedExpression:
|
||||
FATAL() << "Unimplemented: " << exp;
|
||||
}
|
||||
}
|
||||
|
||||
auto Interpreter::EvalImplExp(Nonnull<const Expression*> exp) const
|
||||
-> ErrorOr<Nonnull<const Witness*>> {
|
||||
switch (exp->kind()) {
|
||||
case ExpressionKind::InstantiateImpl: {
|
||||
const InstantiateImpl& inst_impl = cast<InstantiateImpl>(*exp);
|
||||
ASSIGN_OR_RETURN(Nonnull<const Witness*> gen_impl,
|
||||
EvalImplExp(inst_impl.generic_impl()));
|
||||
ImplWitnessMap witnesses;
|
||||
for (auto& [bind, impl_exp] : inst_impl.impls()) {
|
||||
ASSIGN_OR_RETURN(witnesses[bind], EvalImplExp(impl_exp));
|
||||
}
|
||||
return arena_->New<Witness>(&gen_impl->declaration(),
|
||||
inst_impl.type_args(), witnesses);
|
||||
}
|
||||
case ExpressionKind::IdentifierExpression: {
|
||||
const auto& ident = cast<IdentifierExpression>(*exp);
|
||||
ASSIGN_OR_RETURN(
|
||||
Nonnull<const Value*> value,
|
||||
todo_.ValueOfNode(ident.value_node(), ident.source_loc()));
|
||||
if (const auto* lvalue = dyn_cast<LValue>(value)) {
|
||||
ASSIGN_OR_RETURN(value,
|
||||
heap_.Read(lvalue->address(), exp->source_loc()));
|
||||
}
|
||||
return cast<Witness>(value);
|
||||
}
|
||||
default: {
|
||||
FATAL() << "EvalImplExp, unexpected expression: " << *exp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Interpreter::InstantiateType(Nonnull<const Value*> type,
|
||||
SourceLocation source_loc) const
|
||||
-> ErrorOr<Nonnull<const Value*>> {
|
||||
if (trace_stream_) {
|
||||
**trace_stream_ << "instantiating: " << *type << "\n";
|
||||
}
|
||||
switch (type->kind()) {
|
||||
case Value::Kind::VariableType: {
|
||||
if (trace_stream_) {
|
||||
**trace_stream_ << "case VariableType\n";
|
||||
}
|
||||
ASSIGN_OR_RETURN(
|
||||
Nonnull<const Value*> value,
|
||||
todo_.ValueOfNode(&cast<VariableType>(*type).binding(), source_loc));
|
||||
@@ -391,46 +432,15 @@ auto Interpreter::InstantiateType(Nonnull<const Value*> type,
|
||||
return value;
|
||||
}
|
||||
case Value::Kind::NominalClassType: {
|
||||
if (trace_stream_) {
|
||||
**trace_stream_ << "case NominalClassType\n";
|
||||
}
|
||||
const auto& class_type = cast<NominalClassType>(*type);
|
||||
BindingMap inst_type_args;
|
||||
for (const auto& [ty_var, ty_arg] : class_type.type_args()) {
|
||||
ASSIGN_OR_RETURN(inst_type_args[ty_var],
|
||||
InstantiateType(ty_arg, source_loc));
|
||||
}
|
||||
if (trace_stream_) {
|
||||
**trace_stream_ << "finished instantiating ty_arg\n";
|
||||
}
|
||||
std::map<Nonnull<const ImplBinding*>, Nonnull<const Witness*>> witnesses;
|
||||
for (const auto& [bind, impl] : class_type.impls()) {
|
||||
ASSIGN_OR_RETURN(Nonnull<const Value*> witness_addr,
|
||||
todo_.ValueOfNode(impl, source_loc));
|
||||
if (trace_stream_) {
|
||||
**trace_stream_ << "witness_addr: " << *witness_addr << "\n";
|
||||
}
|
||||
// If the witness came directly from an `impl` declaration (via
|
||||
// `constant_value`), then it is a `Witness`. If the witness
|
||||
// came from the runtime scope, then the `Witness` got wrapped
|
||||
// in an `LValue` because that's what
|
||||
// `RuntimeScope::Initialize` does.
|
||||
Nonnull<const Witness*> witness;
|
||||
if (llvm::isa<Witness>(witness_addr)) {
|
||||
witness = cast<Witness>(witness_addr);
|
||||
} else if (llvm::isa<LValue>(witness_addr)) {
|
||||
ASSIGN_OR_RETURN(
|
||||
Nonnull<const Value*> witness_value,
|
||||
heap_.Read(llvm::cast<LValue>(witness_addr)->address(),
|
||||
source_loc));
|
||||
witness = cast<Witness>(witness_value);
|
||||
} else {
|
||||
FATAL() << "expected a witness or LValue of a witness";
|
||||
}
|
||||
witnesses[bind] = witness;
|
||||
}
|
||||
if (trace_stream_) {
|
||||
**trace_stream_ << "finished finding witnesses\n";
|
||||
for (const auto& [bind, impl_exp] : class_type.impls()) {
|
||||
ASSIGN_OR_RETURN(witnesses[bind], EvalImplExp(impl_exp));
|
||||
}
|
||||
return arena_->New<NominalClassType>(&class_type.declaration(),
|
||||
inst_type_args, witnesses);
|
||||
@@ -538,6 +548,104 @@ auto Interpreter::Convert(Nonnull<const Value*> value,
|
||||
}
|
||||
}
|
||||
|
||||
auto Interpreter::CallFunction(const CallExpression& call,
|
||||
Nonnull<const Value*> fun,
|
||||
Nonnull<const Value*> arg,
|
||||
const ImplWitnessMap& witnesses)
|
||||
-> ErrorOr<Success> {
|
||||
if (trace_stream_) {
|
||||
**trace_stream_ << "calling function: " << *fun << "\n";
|
||||
}
|
||||
switch (fun->kind()) {
|
||||
case Value::Kind::AlternativeConstructorValue: {
|
||||
const auto& alt = cast<AlternativeConstructorValue>(*fun);
|
||||
return todo_.FinishAction(arena_->New<AlternativeValue>(
|
||||
alt.alt_name(), alt.choice_name(), arg));
|
||||
}
|
||||
case Value::Kind::FunctionValue: {
|
||||
const FunctionValue& fun_val = cast<FunctionValue>(*fun);
|
||||
const FunctionDeclaration& function = fun_val.declaration();
|
||||
ASSIGN_OR_RETURN(Nonnull<const Value*> converted_args,
|
||||
Convert(arg, &function.param_pattern().static_type(),
|
||||
call.source_loc()));
|
||||
RuntimeScope function_scope(&heap_);
|
||||
// Bring the class type arguments into scope.
|
||||
for (const auto& [bind, val] : fun_val.type_args()) {
|
||||
function_scope.Initialize(bind, val);
|
||||
}
|
||||
// Bring the deduced type arguments into scope.
|
||||
for (const auto& [bind, val] : call.deduced_args()) {
|
||||
function_scope.Initialize(bind, val);
|
||||
}
|
||||
// Bring the impl witness tables into scope.
|
||||
for (const auto& [impl_bind, witness] : witnesses) {
|
||||
function_scope.Initialize(impl_bind, witness);
|
||||
}
|
||||
for (const auto& [impl_bind, witness] : fun_val.witnesses()) {
|
||||
function_scope.Initialize(impl_bind, witness);
|
||||
}
|
||||
BindingMap generic_args;
|
||||
CHECK(PatternMatch(&function.param_pattern().value(), converted_args,
|
||||
call.source_loc(), &function_scope, generic_args));
|
||||
CHECK(function.body().has_value())
|
||||
<< "Calling a function that's missing a body";
|
||||
return todo_.Spawn(std::make_unique<StatementAction>(*function.body()),
|
||||
std::move(function_scope));
|
||||
}
|
||||
case Value::Kind::BoundMethodValue: {
|
||||
const auto& m = cast<BoundMethodValue>(*fun);
|
||||
const FunctionDeclaration& method = m.declaration();
|
||||
CHECK(method.is_method());
|
||||
ASSIGN_OR_RETURN(Nonnull<const Value*> converted_args,
|
||||
Convert(arg, &method.param_pattern().static_type(),
|
||||
call.source_loc()));
|
||||
RuntimeScope method_scope(&heap_);
|
||||
BindingMap generic_args;
|
||||
CHECK(PatternMatch(&method.me_pattern().value(), m.receiver(),
|
||||
call.source_loc(), &method_scope, generic_args));
|
||||
CHECK(PatternMatch(&method.param_pattern().value(), converted_args,
|
||||
call.source_loc(), &method_scope, generic_args));
|
||||
// Bring the class type arguments into scope.
|
||||
for (const auto& [bind, val] : m.type_args()) {
|
||||
method_scope.Initialize(bind, val);
|
||||
}
|
||||
|
||||
// Bring the impl witness tables into scope.
|
||||
for (const auto& [impl_bind, witness] : m.witnesses()) {
|
||||
method_scope.Initialize(impl_bind, witness);
|
||||
}
|
||||
CHECK(method.body().has_value())
|
||||
<< "Calling a method that's missing a body";
|
||||
return todo_.Spawn(std::make_unique<StatementAction>(*method.body()),
|
||||
std::move(method_scope));
|
||||
}
|
||||
case Value::Kind::NominalClassType: {
|
||||
const NominalClassType& class_type = cast<NominalClassType>(*fun);
|
||||
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));
|
||||
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;
|
||||
}
|
||||
}
|
||||
default:
|
||||
return RuntimeError(call.source_loc())
|
||||
<< "in call, expected a function, not " << *fun;
|
||||
}
|
||||
}
|
||||
|
||||
auto Interpreter::StepExp() -> ErrorOr<Success> {
|
||||
Action& act = todo_.CurrentAction();
|
||||
const Expression& exp = cast<ExpressionAction>(act).expression();
|
||||
@@ -546,6 +654,28 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
|
||||
<< ") --->\n";
|
||||
}
|
||||
switch (exp.kind()) {
|
||||
case ExpressionKind::InstantiateImpl: {
|
||||
const InstantiateImpl& inst_impl = cast<InstantiateImpl>(exp);
|
||||
if (act.pos() == 0) {
|
||||
return todo_.Spawn(
|
||||
std::make_unique<ExpressionAction>(inst_impl.generic_impl()));
|
||||
} else if (act.pos() - 1 < int(inst_impl.impls().size())) {
|
||||
auto iter = inst_impl.impls().begin();
|
||||
std::advance(iter, act.pos() - 1);
|
||||
return todo_.Spawn(std::make_unique<ExpressionAction>(iter->second));
|
||||
} else {
|
||||
Nonnull<const Witness*> generic_witness =
|
||||
cast<Witness>(act.results()[0]);
|
||||
ImplWitnessMap witnesses;
|
||||
int i = 0;
|
||||
for (const auto& [impl_bind, impl_exp] : inst_impl.impls()) {
|
||||
witnesses[impl_bind] = cast<Witness>(act.results()[i + 1]);
|
||||
++i;
|
||||
}
|
||||
return todo_.FinishAction(arena_->New<Witness>(
|
||||
&generic_witness->declaration(), inst_impl.type_args(), witnesses));
|
||||
}
|
||||
}
|
||||
case ExpressionKind::IndexExpression: {
|
||||
if (act.pos() == 0) {
|
||||
// { { e[i] :: C, E, F} :: S, H}
|
||||
@@ -673,161 +803,49 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
|
||||
return todo_.FinishAction(value);
|
||||
}
|
||||
}
|
||||
case ExpressionKind::CallExpression:
|
||||
case ExpressionKind::CallExpression: {
|
||||
const CallExpression& call = cast<CallExpression>(exp);
|
||||
// Don't evaluate the impls at compile time?
|
||||
unsigned int num_impls =
|
||||
phase() == Phase::CompileTime ? 0 : call.impls().size();
|
||||
if (act.pos() == 0) {
|
||||
// { {e1(e2) :: C, E, F} :: S, H}
|
||||
// -> { {e1 :: [](e2) :: C, E, F} :: S, H}
|
||||
return todo_.Spawn(std::make_unique<ExpressionAction>(
|
||||
&cast<CallExpression>(exp).function()));
|
||||
return todo_.Spawn(
|
||||
std::make_unique<ExpressionAction>(&call.function()));
|
||||
} else if (act.pos() == 1) {
|
||||
// { { v :: [](e) :: C, E, F} :: S, H}
|
||||
// -> { { e :: v([]) :: C, E, F} :: S, H}
|
||||
return todo_.Spawn(std::make_unique<ExpressionAction>(
|
||||
&cast<CallExpression>(exp).argument()));
|
||||
} else if (act.pos() == 2) {
|
||||
return todo_.Spawn(
|
||||
std::make_unique<ExpressionAction>(&call.argument()));
|
||||
} else if (num_impls > 0 && act.pos() < 2 + int(num_impls)) {
|
||||
auto iter = call.impls().begin();
|
||||
std::advance(iter, act.pos() - 2);
|
||||
return todo_.Spawn(std::make_unique<ExpressionAction>(iter->second));
|
||||
} else if (act.pos() == 2 + int(num_impls)) {
|
||||
// { { v2 :: v1([]) :: C, E, F} :: S, H}
|
||||
// -> { {C',E',F'} :: {C, E, F} :: S, H}
|
||||
switch (act.results()[0]->kind()) {
|
||||
case Value::Kind::AlternativeConstructorValue: {
|
||||
const auto& alt =
|
||||
cast<AlternativeConstructorValue>(*act.results()[0]);
|
||||
return todo_.FinishAction(arena_->New<AlternativeValue>(
|
||||
alt.alt_name(), alt.choice_name(), act.results()[1]));
|
||||
ImplWitnessMap witnesses;
|
||||
if (num_impls > 0) {
|
||||
int i = 2;
|
||||
for (const auto& [impl_bind, impl_exp] : call.impls()) {
|
||||
witnesses[impl_bind] = cast<Witness>(act.results()[i]);
|
||||
++i;
|
||||
}
|
||||
case Value::Kind::FunctionValue: {
|
||||
const FunctionValue& fun_val =
|
||||
cast<FunctionValue>(*act.results()[0]);
|
||||
const FunctionDeclaration& function = fun_val.declaration();
|
||||
if (trace_stream_) {
|
||||
**trace_stream_ << "*** call function " << function.name()
|
||||
<< "\n";
|
||||
}
|
||||
ASSIGN_OR_RETURN(Nonnull<const Value*> converted_args,
|
||||
Convert(act.results()[1],
|
||||
&function.param_pattern().static_type(),
|
||||
exp.source_loc()));
|
||||
RuntimeScope function_scope(&heap_);
|
||||
// Bring the class type arguments into scope.
|
||||
for (const auto& [bind, val] : fun_val.type_args()) {
|
||||
function_scope.Initialize(bind, val);
|
||||
}
|
||||
// Bring the deduced type arguments into scope.
|
||||
for (const auto& [bind, val] :
|
||||
cast<CallExpression>(exp).deduced_args()) {
|
||||
function_scope.Initialize(bind, val);
|
||||
}
|
||||
|
||||
// Bring the impl witness tables into scope.
|
||||
for (const auto& [impl_bind, impl_node] :
|
||||
cast<CallExpression>(exp).impls()) {
|
||||
ASSIGN_OR_RETURN(Nonnull<const Value*> witness,
|
||||
todo_.ValueOfNode(impl_node, exp.source_loc()));
|
||||
if (witness->kind() == Value::Kind::LValue) {
|
||||
const auto& lval = cast<LValue>(*witness);
|
||||
ASSIGN_OR_RETURN(witness,
|
||||
heap_.Read(lval.address(), exp.source_loc()));
|
||||
}
|
||||
function_scope.Initialize(impl_bind, witness);
|
||||
}
|
||||
for (const auto& [impl_bind, witness] : fun_val.witnesses()) {
|
||||
function_scope.Initialize(impl_bind, witness);
|
||||
}
|
||||
BindingMap generic_args;
|
||||
CHECK(PatternMatch(&function.param_pattern().value(),
|
||||
converted_args, exp.source_loc(),
|
||||
&function_scope, generic_args));
|
||||
CHECK(function.body().has_value())
|
||||
<< "Calling a function that's missing a body";
|
||||
return todo_.Spawn(
|
||||
std::make_unique<StatementAction>(*function.body()),
|
||||
std::move(function_scope));
|
||||
}
|
||||
case Value::Kind::BoundMethodValue: {
|
||||
const auto& m = cast<BoundMethodValue>(*act.results()[0]);
|
||||
const FunctionDeclaration& method = m.declaration();
|
||||
CHECK(method.is_method());
|
||||
ASSIGN_OR_RETURN(
|
||||
Nonnull<const Value*> converted_args,
|
||||
Convert(act.results()[1], &method.param_pattern().static_type(),
|
||||
exp.source_loc()));
|
||||
RuntimeScope method_scope(&heap_);
|
||||
BindingMap generic_args;
|
||||
CHECK(PatternMatch(&method.me_pattern().value(), m.receiver(),
|
||||
exp.source_loc(), &method_scope, generic_args));
|
||||
CHECK(PatternMatch(&method.param_pattern().value(), converted_args,
|
||||
exp.source_loc(), &method_scope, generic_args));
|
||||
// Bring the class type arguments into scope.
|
||||
for (const auto& [bind, val] : m.type_args()) {
|
||||
method_scope.Initialize(bind, val);
|
||||
}
|
||||
|
||||
// Bring the impl witness tables into scope.
|
||||
for (const auto& [impl_bind, witness] : m.witnesses()) {
|
||||
method_scope.Initialize(impl_bind, witness);
|
||||
}
|
||||
CHECK(method.body().has_value())
|
||||
<< "Calling a method that's missing a body";
|
||||
return todo_.Spawn(
|
||||
std::make_unique<StatementAction>(*method.body()),
|
||||
std::move(method_scope));
|
||||
}
|
||||
case Value::Kind::NominalClassType: {
|
||||
const NominalClassType& class_type =
|
||||
cast<NominalClassType>(*act.results()[0]);
|
||||
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(),
|
||||
act.results()[1], exp.source_loc(),
|
||||
&type_params_scope, generic_args));
|
||||
switch (phase()) {
|
||||
case Phase::RunTime: {
|
||||
std::map<Nonnull<const ImplBinding*>, const Witness*>
|
||||
witnesses;
|
||||
for (const auto& [impl_bind, impl_node] :
|
||||
cast<CallExpression>(exp).impls()) {
|
||||
ASSIGN_OR_RETURN(
|
||||
Nonnull<const Value*> witness,
|
||||
todo_.ValueOfNode(impl_node, exp.source_loc()));
|
||||
if (witness->kind() == Value::Kind::LValue) {
|
||||
const LValue& lval = cast<LValue>(*witness);
|
||||
ASSIGN_OR_RETURN(witness, heap_.Read(lval.address(),
|
||||
exp.source_loc()));
|
||||
}
|
||||
witnesses[impl_bind] = &cast<Witness>(*witness);
|
||||
}
|
||||
Nonnull<NominalClassType*> inst_class =
|
||||
arena_->New<NominalClassType>(&class_type.declaration(),
|
||||
generic_args, witnesses);
|
||||
return todo_.FinishAction(inst_class);
|
||||
}
|
||||
case Phase::CompileTime: {
|
||||
Nonnull<NominalClassType*> inst_class =
|
||||
arena_->New<NominalClassType>(
|
||||
&class_type.declaration(), generic_args,
|
||||
cast<CallExpression>(exp).impls());
|
||||
return todo_.FinishAction(inst_class);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
FATAL() << "instantiation of non-generic class " << class_type;
|
||||
}
|
||||
}
|
||||
default:
|
||||
return RuntimeError(exp.source_loc())
|
||||
<< "in call, expected a function, not " << *act.results()[0];
|
||||
}
|
||||
} else if (act.pos() == 3) {
|
||||
if (act.results().size() < 3) {
|
||||
return CallFunction(call, act.results()[0], act.results()[1],
|
||||
witnesses);
|
||||
} else if (act.pos() == 3 + int(num_impls)) {
|
||||
if (act.results().size() < 3 + num_impls) {
|
||||
// Control fell through without explicit return.
|
||||
return todo_.FinishAction(TupleValue::Empty());
|
||||
} else {
|
||||
return todo_.FinishAction(act.results()[2]);
|
||||
return todo_.FinishAction(act.results()[2 + int(num_impls)]);
|
||||
}
|
||||
} else {
|
||||
FATAL() << "in handle_value with Call pos " << act.pos();
|
||||
FATAL() << "in StepExp with Call pos " << act.pos();
|
||||
}
|
||||
}
|
||||
case ExpressionKind::IntrinsicExpression: {
|
||||
const auto& intrinsic = cast<IntrinsicExpression>(exp);
|
||||
if (act.pos() == 0) {
|
||||
|
||||
Reference in New Issue
Block a user