[torque] Generalize type argument inference for generic calls
With the arrival of generic structs (https://chromium-review.googlesource.com/c/v8/v8/+/1714868) the existing type inference procedure for generic calls became incomplete, since it could not infer types that were only constrained as part of generic types. For instance, given struct Box<T: Type> { ... } macro unbox<T: type>(box: Box<T>): T the type argument (Smi) at the following call site const box: Box<Smi> = ...; unbox(box); could not be inferred. This CL re-implements the inference procedure and documents the semantics of type argument inference in Torque a bit more clearly. R=tebbi@chromium.org Change-Id: I868f16afbd9864b9c810ac49bc1639b467df939c Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1720812 Commit-Queue: Georg Schmid <gsps@google.com> Reviewed-by: Tobias Tebbi <tebbi@chromium.org> Cr-Commit-Position: refs/heads/master@{#63005}
This commit is contained in:
parent
3a1bb751c9
commit
d4e6525849
2
BUILD.gn
2
BUILD.gn
@ -3364,6 +3364,8 @@ v8_source_set("torque_base") {
|
||||
"src/torque/torque-compiler.h",
|
||||
"src/torque/torque-parser.cc",
|
||||
"src/torque/torque-parser.h",
|
||||
"src/torque/type-inference.cc",
|
||||
"src/torque/type-inference.h",
|
||||
"src/torque/type-oracle.cc",
|
||||
"src/torque/type-oracle.h",
|
||||
"src/torque/type-visitor.cc",
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include "src/torque/declarable.h"
|
||||
#include "src/torque/global-context.h"
|
||||
#include "src/torque/type-inference.h"
|
||||
#include "src/torque/type-visitor.h"
|
||||
|
||||
namespace v8 {
|
||||
@ -65,56 +66,20 @@ std::ostream& operator<<(std::ostream& os, const Generic& g) {
|
||||
return os;
|
||||
}
|
||||
|
||||
namespace {
|
||||
base::Optional<const Type*> InferTypeArgument(const std::string& to_infer,
|
||||
TypeExpression* parameter,
|
||||
const Type* argument) {
|
||||
BasicTypeExpression* basic = BasicTypeExpression::DynamicCast(parameter);
|
||||
if (basic && basic->namespace_qualification.empty() && !basic->is_constexpr &&
|
||||
basic->name == to_infer) {
|
||||
return argument;
|
||||
}
|
||||
// TODO(gsps): Perform type inference for generic types
|
||||
return base::nullopt;
|
||||
}
|
||||
|
||||
base::Optional<const Type*> InferTypeArgument(
|
||||
const std::string& to_infer, const std::vector<TypeExpression*>& parameters,
|
||||
const TypeVector& arguments) {
|
||||
for (size_t i = 0; i < arguments.size() && i < parameters.size(); ++i) {
|
||||
if (base::Optional<const Type*> inferred =
|
||||
InferTypeArgument(to_infer, parameters[i], arguments[i])) {
|
||||
return *inferred;
|
||||
}
|
||||
}
|
||||
return base::nullopt;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
base::Optional<TypeVector> Generic::InferSpecializationTypes(
|
||||
TypeArgumentInference Generic::InferSpecializationTypes(
|
||||
const TypeVector& explicit_specialization_types,
|
||||
const TypeVector& arguments) {
|
||||
TypeVector result = explicit_specialization_types;
|
||||
size_t type_parameter_count = declaration()->generic_parameters.size();
|
||||
if (explicit_specialization_types.size() > type_parameter_count) {
|
||||
return base::nullopt;
|
||||
}
|
||||
for (size_t i = explicit_specialization_types.size();
|
||||
i < type_parameter_count; ++i) {
|
||||
const std::string type_name = declaration()->generic_parameters[i]->value;
|
||||
size_t implicit_count =
|
||||
declaration()->callable->signature->parameters.implicit_count;
|
||||
const std::vector<TypeExpression*>& parameters =
|
||||
declaration()->callable->signature->parameters.types;
|
||||
std::vector<TypeExpression*> explicit_parameters(
|
||||
parameters.begin() + implicit_count, parameters.end());
|
||||
base::Optional<const Type*> inferred =
|
||||
InferTypeArgument(type_name, explicit_parameters, arguments);
|
||||
if (!inferred) return base::nullopt;
|
||||
result.push_back(*inferred);
|
||||
}
|
||||
return result;
|
||||
size_t implicit_count =
|
||||
declaration()->callable->signature->parameters.implicit_count;
|
||||
const std::vector<TypeExpression*>& parameters =
|
||||
declaration()->callable->signature->parameters.types;
|
||||
std::vector<TypeExpression*> explicit_parameters(
|
||||
parameters.begin() + implicit_count, parameters.end());
|
||||
|
||||
TypeArgumentInference inference(declaration()->generic_parameters,
|
||||
explicit_specialization_types,
|
||||
explicit_parameters, arguments);
|
||||
return inference;
|
||||
}
|
||||
|
||||
bool Namespace::IsDefaultNamespace() const {
|
||||
|
@ -21,6 +21,7 @@ namespace torque {
|
||||
|
||||
class Scope;
|
||||
class Namespace;
|
||||
class TypeArgumentInference;
|
||||
|
||||
DECLARE_CONTEXTUAL_VARIABLE(CurrentScope, Scope*);
|
||||
|
||||
@ -489,7 +490,7 @@ class Generic : public Declarable {
|
||||
}
|
||||
SpecializationMap<Callable>& specializations() { return specializations_; }
|
||||
|
||||
base::Optional<TypeVector> InferSpecializationTypes(
|
||||
TypeArgumentInference InferSpecializationTypes(
|
||||
const TypeVector& explicit_specialization_types,
|
||||
const TypeVector& arguments);
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "src/torque/implementation-visitor.h"
|
||||
#include "src/torque/parameter-difference.h"
|
||||
#include "src/torque/server-data.h"
|
||||
#include "src/torque/type-inference.h"
|
||||
#include "src/torque/type-visitor.h"
|
||||
|
||||
namespace v8 {
|
||||
@ -1582,7 +1583,9 @@ namespace {
|
||||
void FailCallableLookup(const std::string& reason, const QualifiedName& name,
|
||||
const TypeVector& parameter_types,
|
||||
const std::vector<Binding<LocalLabel>*>& labels,
|
||||
const std::vector<Signature>& candidates) {
|
||||
const std::vector<Signature>& candidates,
|
||||
const std::vector<std::tuple<Generic*, const char*>>
|
||||
inapplicable_generics) {
|
||||
std::stringstream stream;
|
||||
stream << "\n" << reason << ": \n " << name << "(" << parameter_types << ")";
|
||||
if (labels.size() != 0) {
|
||||
@ -1596,6 +1599,16 @@ void FailCallableLookup(const std::string& reason, const QualifiedName& name,
|
||||
stream << "\n " << name;
|
||||
PrintSignature(stream, signature, false);
|
||||
}
|
||||
if (inapplicable_generics.size() != 0) {
|
||||
stream << "\nfailed to instantiate all of these generic declarations:";
|
||||
for (auto& failure : inapplicable_generics) {
|
||||
Generic* generic;
|
||||
const char* reason;
|
||||
std::tie(generic, reason) = failure;
|
||||
stream << "\n " << generic->name() << " defined at "
|
||||
<< generic->Position() << ":\n " << reason << "\n";
|
||||
}
|
||||
}
|
||||
ReportError(stream.str());
|
||||
}
|
||||
|
||||
@ -1655,17 +1668,20 @@ Callable* ImplementationVisitor::LookupCallable(
|
||||
|
||||
std::vector<Declarable*> overloads;
|
||||
std::vector<Signature> overload_signatures;
|
||||
std::vector<std::tuple<Generic*, const char*>> inapplicable_generics;
|
||||
for (auto* declarable : declaration_container) {
|
||||
if (Generic* generic = Generic::DynamicCast(declarable)) {
|
||||
base::Optional<TypeVector> inferred_specialization_types =
|
||||
generic->InferSpecializationTypes(specialization_types,
|
||||
parameter_types);
|
||||
if (!inferred_specialization_types) continue;
|
||||
TypeArgumentInference inference = generic->InferSpecializationTypes(
|
||||
specialization_types, parameter_types);
|
||||
if (inference.HasFailed()) {
|
||||
inapplicable_generics.push_back(
|
||||
std::make_tuple(generic, inference.GetFailureReason()));
|
||||
continue;
|
||||
}
|
||||
overloads.push_back(generic);
|
||||
overload_signatures.push_back(
|
||||
DeclarationVisitor::MakeSpecializedSignature(
|
||||
SpecializationKey<Generic>{generic,
|
||||
*inferred_specialization_types}));
|
||||
SpecializationKey<Generic>{generic, inference.GetResult()}));
|
||||
} else if (Callable* callable = Callable::DynamicCast(declarable)) {
|
||||
overloads.push_back(callable);
|
||||
overload_signatures.push_back(callable->signature());
|
||||
@ -1684,7 +1700,7 @@ Callable* ImplementationVisitor::LookupCallable(
|
||||
}
|
||||
}
|
||||
|
||||
if (overloads.empty()) {
|
||||
if (overloads.empty() && inapplicable_generics.empty()) {
|
||||
if (silence_errors) return nullptr;
|
||||
std::stringstream stream;
|
||||
stream << "no matching declaration found for " << name;
|
||||
@ -1692,7 +1708,8 @@ Callable* ImplementationVisitor::LookupCallable(
|
||||
} else if (candidates.empty()) {
|
||||
if (silence_errors) return nullptr;
|
||||
FailCallableLookup("cannot find suitable callable with name", name,
|
||||
parameter_types, labels, overload_signatures);
|
||||
parameter_types, labels, overload_signatures,
|
||||
inapplicable_generics);
|
||||
}
|
||||
|
||||
auto is_better_candidate = [&](size_t a, size_t b) {
|
||||
@ -1713,14 +1730,15 @@ Callable* ImplementationVisitor::LookupCallable(
|
||||
candidate_signatures.push_back(overload_signatures[i]);
|
||||
}
|
||||
FailCallableLookup("ambiguous callable ", name, parameter_types, labels,
|
||||
candidate_signatures);
|
||||
candidate_signatures, inapplicable_generics);
|
||||
}
|
||||
}
|
||||
|
||||
if (Generic* generic = Generic::DynamicCast(overloads[best])) {
|
||||
result = GetOrCreateSpecialization(SpecializationKey<Generic>{
|
||||
generic, *generic->InferSpecializationTypes(specialization_types,
|
||||
parameter_types)});
|
||||
TypeArgumentInference inference = generic->InferSpecializationTypes(
|
||||
specialization_types, parameter_types);
|
||||
result = GetOrCreateSpecialization(
|
||||
SpecializationKey<Generic>{generic, inference.GetResult()});
|
||||
} else {
|
||||
result = Callable::cast(overloads[best]);
|
||||
}
|
||||
|
118
src/torque/type-inference.cc
Normal file
118
src/torque/type-inference.cc
Normal file
@ -0,0 +1,118 @@
|
||||
// Copyright 2019 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "src/torque/type-inference.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
namespace torque {
|
||||
|
||||
TypeArgumentInference::TypeArgumentInference(
|
||||
const NameVector& type_parameters,
|
||||
const TypeVector& explicit_type_arguments,
|
||||
const std::vector<TypeExpression*> term_parameters,
|
||||
const TypeVector& term_argument_types)
|
||||
: num_explicit_(explicit_type_arguments.size()),
|
||||
type_parameter_from_name_(type_parameters.size()),
|
||||
inferred_(type_parameters.size()) {
|
||||
if (num_explicit_ > type_parameters.size()) {
|
||||
Fail("more explicit type arguments than expected");
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < type_parameters.size(); i++) {
|
||||
type_parameter_from_name_[type_parameters[i]->value] = i;
|
||||
}
|
||||
for (size_t i = 0; i < num_explicit_; i++) {
|
||||
inferred_[i] = {explicit_type_arguments[i]};
|
||||
}
|
||||
|
||||
DCHECK_EQ(term_parameters.size(), term_argument_types.size());
|
||||
for (size_t i = 0; i < term_parameters.size(); i++) {
|
||||
Match(term_parameters[i], term_argument_types[i]);
|
||||
if (HasFailed()) return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < type_parameters.size(); i++) {
|
||||
if (!inferred_[i]) {
|
||||
Fail("failed to infer arguments for all type parameters");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TypeVector TypeArgumentInference::GetResult() const {
|
||||
CHECK(!HasFailed());
|
||||
TypeVector result(inferred_.size());
|
||||
std::transform(
|
||||
inferred_.begin(), inferred_.end(), result.begin(),
|
||||
[](base::Optional<const Type*> maybe_type) { return *maybe_type; });
|
||||
return result;
|
||||
}
|
||||
|
||||
void TypeArgumentInference::Match(TypeExpression* parameter,
|
||||
const Type* argument_type) {
|
||||
if (BasicTypeExpression* basic =
|
||||
BasicTypeExpression::DynamicCast(parameter)) {
|
||||
// If the parameter is referring to one of the type parameters, substitute
|
||||
if (basic->namespace_qualification.empty() && !basic->is_constexpr) {
|
||||
auto result = type_parameter_from_name_.find(basic->name);
|
||||
if (result != type_parameter_from_name_.end()) {
|
||||
size_t type_parameter_index = result->second;
|
||||
if (type_parameter_index < num_explicit_) {
|
||||
return;
|
||||
}
|
||||
base::Optional<const Type*>& maybe_inferred =
|
||||
inferred_[type_parameter_index];
|
||||
if (maybe_inferred && *maybe_inferred != argument_type) {
|
||||
Fail("found conflicting types for generic parameter");
|
||||
} else {
|
||||
inferred_[type_parameter_index] = {argument_type};
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Try to recurse in case of generic types
|
||||
if (!basic->generic_arguments.empty()) {
|
||||
auto* argument_struct_type = StructType::DynamicCast(argument_type);
|
||||
if (argument_struct_type) {
|
||||
MatchGeneric(basic, argument_struct_type);
|
||||
}
|
||||
}
|
||||
// NOTE: We could also check whether ground parameter types match the
|
||||
// argument types, but we are only interested in inferring type arguments
|
||||
// here
|
||||
} else {
|
||||
Fail("unsupported parameter expression");
|
||||
}
|
||||
}
|
||||
|
||||
void TypeArgumentInference::MatchGeneric(BasicTypeExpression* parameter,
|
||||
const StructType* argument_type) {
|
||||
QualifiedName qualified_name{parameter->namespace_qualification,
|
||||
parameter->name};
|
||||
GenericStructType* generic_struct =
|
||||
Declarations::LookupUniqueGenericStructType(qualified_name);
|
||||
auto& specialized_from = argument_type->GetSpecializedFrom();
|
||||
if (!specialized_from || specialized_from->generic != generic_struct) {
|
||||
return Fail("found conflicting generic type constructors");
|
||||
}
|
||||
auto& parameters = parameter->generic_arguments;
|
||||
auto& argument_types = specialized_from->specialized_types;
|
||||
if (parameters.size() != argument_types.size()) {
|
||||
Error(
|
||||
"cannot infer types from generic-struct-typed parameter with "
|
||||
"incompatible number of arguments")
|
||||
.Position(parameter->pos)
|
||||
.Throw();
|
||||
}
|
||||
for (size_t i = 0; i < parameters.size(); i++) {
|
||||
Match(parameters[i], argument_types[i]);
|
||||
if (HasFailed()) return;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace torque
|
||||
} // namespace internal
|
||||
} // namespace v8
|
78
src/torque/type-inference.h
Normal file
78
src/torque/type-inference.h
Normal file
@ -0,0 +1,78 @@
|
||||
// Copyright 2019 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef V8_TORQUE_TYPE_INFERENCE_H_
|
||||
#define V8_TORQUE_TYPE_INFERENCE_H_
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "src/base/optional.h"
|
||||
#include "src/torque/ast.h"
|
||||
#include "src/torque/declarations.h"
|
||||
#include "src/torque/types.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
namespace torque {
|
||||
|
||||
// Type argument inference computes a potential instantiation of a generic
|
||||
// callable given some concrete argument types. As an example, consider the
|
||||
// generic macro
|
||||
//
|
||||
// macro Pick<T: type>(x: T, y: T): T
|
||||
//
|
||||
// along with a given call site, such as
|
||||
//
|
||||
// Pick(1, 2);
|
||||
//
|
||||
// The inference proceeds by matching the term argument types (`constexpr
|
||||
// int31`, in case of `1` and `2`) against the formal parameter types (`T` in
|
||||
// both cases). During this matching we discover that `T` must equal `constexpr
|
||||
// int31`.
|
||||
//
|
||||
// The inference will not perform any comprehensive type checking of its own,
|
||||
// but *does* fail if type parameters cannot be soundly instantiated given the
|
||||
// call site. For instance, for the following call site
|
||||
//
|
||||
// const aSmi: Smi = ...;
|
||||
// Pick(1, aSmi); // inference fails
|
||||
//
|
||||
// inference would fail, since `constexpr int31` is distinct from `Smi`. To
|
||||
// allow for implicit conversions to be tried in a separate step after type
|
||||
// argument inference, a number of type arguments may be given explicitly:
|
||||
//
|
||||
// Pick<Smi>(1, aSmi); // inference succeeds (doing nothing)
|
||||
//
|
||||
// In the above case the inference simply ignores inconsistent constraints on
|
||||
// `T`.
|
||||
class TypeArgumentInference {
|
||||
public:
|
||||
TypeArgumentInference(const NameVector& type_parameters,
|
||||
const TypeVector& explicit_type_arguments,
|
||||
const std::vector<TypeExpression*> term_parameters,
|
||||
const TypeVector& term_argument_types);
|
||||
|
||||
bool HasFailed() const { return failure_reason_.has_value(); }
|
||||
const char* GetFailureReason() { return *failure_reason_; }
|
||||
TypeVector GetResult() const;
|
||||
|
||||
private:
|
||||
void Fail(const char* reason) { failure_reason_ = {reason}; }
|
||||
|
||||
void Match(TypeExpression* parameter, const Type* argument_type);
|
||||
void MatchGeneric(BasicTypeExpression* parameter,
|
||||
const StructType* argument_type);
|
||||
|
||||
size_t num_explicit_;
|
||||
std::unordered_map<std::string, size_t> type_parameter_from_name_;
|
||||
std::vector<base::Optional<const Type*>> inferred_;
|
||||
base::Optional<const char*> failure_reason_;
|
||||
};
|
||||
|
||||
} // namespace torque
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
#endif // V8_TORQUE_TYPE_INFERENCE_H_
|
@ -475,6 +475,9 @@ class StructType final : public AggregateType {
|
||||
std::string ToExplicitString() const override;
|
||||
std::string GetGeneratedTypeNameImpl() const override;
|
||||
std::string MangledName() const override;
|
||||
const MaybeSpecializationKey& GetSpecializedFrom() const {
|
||||
return specialized_from_;
|
||||
}
|
||||
|
||||
static base::Optional<const Type*> MatchUnaryGeneric(
|
||||
const Type* type, GenericStructType* generic);
|
||||
|
@ -898,8 +898,7 @@ namespace test {
|
||||
const ref:&Smi = & array.a;
|
||||
* ref = 3 + * ref;
|
||||
-- * ref;
|
||||
// TODO(gsps): Remove explicit type arg once inference works
|
||||
Swap<Smi>(& array.b, array.GetA());
|
||||
Swap(& array.b, array.GetA());
|
||||
check(array.a == 2);
|
||||
check(array.b == 9);
|
||||
}
|
||||
@ -1002,7 +1001,7 @@ namespace test {
|
||||
@export
|
||||
macro TestGenericStruct2(): TestTuple<Smi, intptr> {
|
||||
const intptrAndSmi = TestTuple<intptr, Smi>{fst: 1, snd: 2};
|
||||
const smiAndIntptr = TupleSwap<intptr, Smi>(intptrAndSmi);
|
||||
const smiAndIntptr = TupleSwap(intptrAndSmi);
|
||||
check(intptrAndSmi.fst == smiAndIntptr.snd);
|
||||
check(intptrAndSmi.snd == smiAndIntptr.fst);
|
||||
return smiAndIntptr;
|
||||
|
Loading…
Reference in New Issue
Block a user