diff --git a/BUILD.gn b/BUILD.gn index 2c57a30ab9..d6c1934973 100644 --- a/BUILD.gn +++ b/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", diff --git a/src/torque/declarable.cc b/src/torque/declarable.cc index 2ea6b6d65c..127a91cf5d 100644 --- a/src/torque/declarable.cc +++ b/src/torque/declarable.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 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 InferTypeArgument( - const std::string& to_infer, const std::vector& parameters, - const TypeVector& arguments) { - for (size_t i = 0; i < arguments.size() && i < parameters.size(); ++i) { - if (base::Optional inferred = - InferTypeArgument(to_infer, parameters[i], arguments[i])) { - return *inferred; - } - } - return base::nullopt; -} - -} // namespace - -base::Optional 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& parameters = - declaration()->callable->signature->parameters.types; - std::vector explicit_parameters( - parameters.begin() + implicit_count, parameters.end()); - base::Optional 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& parameters = + declaration()->callable->signature->parameters.types; + std::vector 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 { diff --git a/src/torque/declarable.h b/src/torque/declarable.h index dc515b50c9..23ab1dcb28 100644 --- a/src/torque/declarable.h +++ b/src/torque/declarable.h @@ -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& specializations() { return specializations_; } - base::Optional InferSpecializationTypes( + TypeArgumentInference InferSpecializationTypes( const TypeVector& explicit_specialization_types, const TypeVector& arguments); diff --git a/src/torque/implementation-visitor.cc b/src/torque/implementation-visitor.cc index 3a7105d744..a8dc1e6120 100644 --- a/src/torque/implementation-visitor.cc +++ b/src/torque/implementation-visitor.cc @@ -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*>& labels, - const std::vector& candidates) { + const std::vector& candidates, + const std::vector> + 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 overloads; std::vector overload_signatures; + std::vector> inapplicable_generics; for (auto* declarable : declaration_container) { if (Generic* generic = Generic::DynamicCast(declarable)) { - base::Optional 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, - *inferred_specialization_types})); + SpecializationKey{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->InferSpecializationTypes(specialization_types, - parameter_types)}); + TypeArgumentInference inference = generic->InferSpecializationTypes( + specialization_types, parameter_types); + result = GetOrCreateSpecialization( + SpecializationKey{generic, inference.GetResult()}); } else { result = Callable::cast(overloads[best]); } diff --git a/src/torque/type-inference.cc b/src/torque/type-inference.cc new file mode 100644 index 0000000000..4a320cdfe8 --- /dev/null +++ b/src/torque/type-inference.cc @@ -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 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 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& 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 diff --git a/src/torque/type-inference.h b/src/torque/type-inference.h new file mode 100644 index 0000000000..1a89462581 --- /dev/null +++ b/src/torque/type-inference.h @@ -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 +#include + +#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(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(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 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 type_parameter_from_name_; + std::vector> inferred_; + base::Optional failure_reason_; +}; + +} // namespace torque +} // namespace internal +} // namespace v8 + +#endif // V8_TORQUE_TYPE_INFERENCE_H_ diff --git a/src/torque/types.h b/src/torque/types.h index 77da82bea8..8f153bef42 100644 --- a/src/torque/types.h +++ b/src/torque/types.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 MatchUnaryGeneric( const Type* type, GenericStructType* generic); diff --git a/test/torque/test-torque.tq b/test/torque/test-torque.tq index 87a2521674..150942dd8a 100644 --- a/test/torque/test-torque.tq +++ b/test/torque/test-torque.tq @@ -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(& 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 { const intptrAndSmi = TestTuple{fst: 1, snd: 2}; - const smiAndIntptr = TupleSwap(intptrAndSmi); + const smiAndIntptr = TupleSwap(intptrAndSmi); check(intptrAndSmi.fst == smiAndIntptr.snd); check(intptrAndSmi.snd == smiAndIntptr.fst); return smiAndIntptr;