Skip to content

Commit

Permalink
Dyno: implement "is default initializable" (#26679)
Browse files Browse the repository at this point in the history
This PR implements the `type has default value` primitive. For the most
part, I attempted to mimic the approach used by production. This for the
most part boils down to checking fields then invoking `init()`, except
for some special cases like array types (which are default-initializable
if their elements are) and classes (which are always
default-initializable if they are nilable, and never otherwise).

There's some trickiness involved: "default initialization" sometimes
involves invoking `init` with some actuals. Specifically, in the case of
generic types, each `type` and `param` substitution gets turned into an
actual to resolve the corresponding initializer. This enables resolving
initializers that only require types / params that are already provided
as substitutions for the type. To implement this, extract existing logic
for adding substitutions as actuals from `Resolver.cpp` (for its use in
`new` resolution) and share it with the new initializer call builder and
existing logic in `call-init-deinit.cpp`

While this PR generally follows the production compiler's behavior,
following a comparison with #26678 I noticed some dd behaviors. Most
notably, default generic types such as `numeric` are treated as having a
default initializer. these cases likely don't occur in practice because
they are wrapped in function calls (and thus, only things that
instantiate these types are actually fed to the primitive). Thus, I
chose to match Anna's approach and mark those as generic.

Reviewed by @riftEmber -- thanks!

## Testing
- [x] dyno tests
- [x] paratest
  • Loading branch information
DanilaFe authored Feb 10, 2025
2 parents deec65f + 25cb9e5 commit 0dfb840
Show file tree
Hide file tree
Showing 10 changed files with 326 additions and 82 deletions.
15 changes: 15 additions & 0 deletions frontend/include/chpl/resolution/resolution-queries.h
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,21 @@ const TypedFnSignature* tryResolveInitEq(Context* context,
const types::Type* rhsType,
const PoiScope* poiScope = nullptr);

// helper for tryResolveZeroArgInit: add substitutions from a type to a list
// of actuals. In practice, "zero-arg" init calls are really init calls where
// arguments are given by the field substitutions etc.
bool addExistingSubstitutionsAsActuals(Context* context,
const types::Type* type,
std::vector<CallInfoActual>& outActuals,
std::vector<const uast::AstNode*>& outActualAsts);


// tries to resolve an (unambiguous) init()
const TypedFnSignature* tryResolveZeroArgInit(Context* context,
const uast::AstNode* astForScopeOrErr,
const types::Type* toInit,
const PoiScope* poiScope = nullptr);

// tries to resolve an (unambiguous) assign
const TypedFnSignature* tryResolveAssign(Context* context,
const uast::AstNode* astForScopeOrErr,
Expand Down
2 changes: 2 additions & 0 deletions frontend/include/chpl/types/Type.h
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,8 @@ class Type {
*/
static bool isPod(Context* context, const Type* t);

static bool isDefaultInitializable(Context* context, const Type* t);

static bool needsInitDeinitCall(const Type* t);

/// \cond DO_NOT_DOCUMENT
Expand Down
25 changes: 0 additions & 25 deletions frontend/lib/resolution/Resolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2490,31 +2490,6 @@ void Resolver::resolveTupleDecl(const TupleDecl* td,
resolveTupleUnpackDecl(td, useT);
}

static bool addExistingSubstitutionsAsActuals(Context* context,
const Type* type,
std::vector<CallInfoActual>& outActuals,
std::vector<const AstNode*>& outActualAsts) {
bool addedSubs = false;
while (auto ct = type->getCompositeType()) {
if (!ct->instantiatedFromCompositeType()) break;

for (auto& [id, qt] : ct->substitutions()) {
auto fieldName = parsing::fieldIdToName(context, id);
addedSubs = true;
outActuals.emplace_back(qt, fieldName);
outActualAsts.push_back(nullptr);
}

if (auto clt = ct->toBasicClassType()) {
type = clt->parentClassType();
} else {
break;
}
}

return addedSubs;
}

static void findMismatchedInstantiations(Context* context,
const CompositeType* originalCT,
const CompositeType* finalCT,
Expand Down
15 changes: 4 additions & 11 deletions frontend/lib/resolution/call-init-deinit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "chpl/parsing/parsing-queries.h"
#include "chpl/resolution/can-pass.h"
#include "chpl/resolution/ResolvedVisitor.h"
#include "chpl/resolution/resolution-queries.h"
#include "chpl/resolution/resolution-types.h"
#include "chpl/resolution/scope-queries.h"
#include "chpl/resolution/copy-elision.h"
Expand Down Expand Up @@ -427,6 +428,7 @@ void CallInitDeinit::resolveDefaultInit(const VarLikeDecl* ast, RV& rv) {
// try to resolve 'init'
// TODO: handle instantiations passing field types
std::vector<CallInfoActual> actuals;
std::vector<const AstNode*> ignoredActualAsts;
actuals.push_back(CallInfoActual(varType, USTR("this")));
if (classType != nullptr && classType->manager() != nullptr) {
// when default-initializing a shared C? or owned C?,
Expand All @@ -444,17 +446,8 @@ void CallInitDeinit::resolveDefaultInit(const VarLikeDecl* ast, RV& rv) {
CallInfoActual(QualifiedType(QualifiedType::TYPE, t), chpl_t));
} else if (compositeType != nullptr &&
compositeType->instantiatedFromCompositeType() != nullptr) {
// pass generic type and param fields by the name
auto subs = compositeType->sortedSubstitutions();
for (const auto& pair : subs) {
const ID& id = pair.first;
const QualifiedType& qt = pair.second;
auto fieldAst = parsing::idToAst(context, id)->toVarLikeDecl();
if (fieldAst->storageKind() == QualifiedType::TYPE ||
fieldAst->storageKind() == QualifiedType::PARAM) {
actuals.push_back(CallInfoActual(qt, fieldAst->name()));
}
}
addExistingSubstitutionsAsActuals(context, compositeType, actuals,
ignoredActualAsts);
}

// Get the 'root' instantiation
Expand Down
8 changes: 7 additions & 1 deletion frontend/lib/resolution/prims.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1059,6 +1059,12 @@ static QualifiedType primIsPod(Context* context, const CallInfo& ci) {
});
}

static QualifiedType primIsDefaultInitializable(Context* context, const CallInfo& ci) {
return actualTypeHasProperty(context, ci, [=](auto t) {
return Type::isDefaultInitializable(context, t);
});
}

static QualifiedType primNeedsAutoDestroy(Context* context, const CallInfo& ci) {
return actualTypeHasProperty(context, ci, [=](auto t) {
return Type::needsInitDeinitCall(t) && !Type::isPod(context, t);
Expand Down Expand Up @@ -1323,7 +1329,7 @@ CallResolutionResult resolvePrimCall(ResolutionContext* rc,
break;

case PRIM_HAS_DEFAULT_VALUE:
CHPL_UNIMPL("various primitives");
type = primIsDefaultInitializable(context, ci);
break;

case PRIM_NEEDS_AUTO_DESTROY:
Expand Down
54 changes: 54 additions & 0 deletions frontend/lib/resolution/resolution-queries.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5498,6 +5498,60 @@ const TypedFnSignature* tryResolveInitEq(Context* context,
return c.mostSpecific().only().fn();
}

bool addExistingSubstitutionsAsActuals(Context* context,
const types::Type* type,
std::vector<CallInfoActual>& outActuals,
std::vector<const uast::AstNode*>& outActualAsts) {
bool addedSubs = false;
while (auto ct = type->getCompositeType()) {
if (!ct->instantiatedFromCompositeType()) break;

for (auto& [id, qt] : ct->substitutions()) {
auto fieldAst = parsing::idToAst(context, id)->toVarLikeDecl();
if (fieldAst->storageKind() == QualifiedType::TYPE ||
fieldAst->storageKind() == QualifiedType::PARAM) {
addedSubs = true;
outActuals.emplace_back(qt, fieldAst->name());
outActualAsts.push_back(nullptr);
}
}

if (auto clt = ct->toBasicClassType()) {
type = clt->parentClassType();
} else {
break;
}
}

return addedSubs;
}

const TypedFnSignature* tryResolveZeroArgInit(Context* context,
const AstNode* astForScopeOrErr,
const types::Type* toInit,
const PoiScope* poiScope) {
if (!toInit->getCompositeType()) return nullptr;

QualifiedType toInitQt(QualifiedType::INIT_RECEIVER, toInit);

std::vector<CallInfoActual> actuals;
std::vector<const uast::AstNode*> ignoredActualAsts;
actuals.push_back(CallInfoActual(toInitQt, USTR("this")));
addExistingSubstitutionsAsActuals(context, toInit, actuals, ignoredActualAsts);
auto ci = CallInfo(/* name */ USTR("init"),
/* calledType */ toInitQt,
/* isMethodCall */ true,
/* hasQuestionArg */ false,
/* isParenless */ false, actuals);

const Scope* scope = nullptr;
if (astForScopeOrErr) scope = scopeForId(context, astForScopeOrErr->id());

auto c = resolveGeneratedCall(context, astForScopeOrErr, ci,
CallScopeInfo::forNormalCall(scope, poiScope));
return c.mostSpecific().only().fn();
}

const TypedFnSignature* tryResolveDeinit(Context* context,
const AstNode* astForScopeOrErr,
const types::Type* t,
Expand Down
99 changes: 86 additions & 13 deletions frontend/lib/types/Type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "chpl/framework/query-impl.h"
#include "chpl/types/AnyClassType.h"
#include "chpl/types/AnyType.h"
#include "chpl/types/ArrayType.h"
#include "chpl/types/BoolType.h"
#include "chpl/types/BuiltinType.h"
#include "chpl/types/CStringType.h"
Expand All @@ -44,6 +45,7 @@
#include "chpl/types/VoidType.h"
#include "chpl/parsing/parsing-queries.h"
#include "chpl/resolution/resolution-queries.h"
#include "../resolution/default-functions.h"

namespace chpl {
namespace types {
Expand Down Expand Up @@ -258,43 +260,59 @@ const CompositeType* Type::getCompositeType() const {
return nullptr;
}

static bool
compositeTypeIsPod(Context* context, const Type* t) {
template <typename F>
static bool checkFieldsWithPredicate(Context* context, const Type* t, F&& pred) {
using namespace resolution;

if (auto cls = t->toClassType()) {
return !cls->decorator().isManaged();
}

auto ct = t->getCompositeType();
if (!ct) return false;
CHPL_ASSERT(ct);

if (auto tt = t->toTupleType()) {
// A tuple is plain old data if all of its components are plain old data.
for (int i = 0; i < tt->numElements(); i++) {
auto& eltType = tt->elementType(i);
if (!eltType.type()) return false;
if (!Type::isPod(context, eltType.type())) return false;
if (!pred(context, eltType.type())) return false;
}

return true;
}

const uast::AstNode* ast = nullptr;
if (auto id = ct->id()) ast = parsing::idToAst(context, std::move(id));

auto& rf = fieldsForTypeDecl(context, ct, DefaultsPolicy::USE_DEFAULTS);
for (int i = 0; i < rf.numFields(); i++) {
auto qt = rf.fieldType(i);
if (auto ft = qt.type()) {
if (qt.kind() == QualifiedType::PARAM ||
qt.kind() == QualifiedType::TYPE) continue;
if (!Type::isPod(context, ft)) return false;
if (!pred(context, ft)) return false;
} else {
return false;
}
}

return true;
}

static bool
compositeTypeIsPod(Context* context, const Type* t) {
using namespace resolution;

if (auto cls = t->toClassType()) {
return !cls->decorator().isManaged();
}

auto ct = t->getCompositeType();
if (!ct) return false;

bool fieldsArePod = checkFieldsWithPredicate(context, t, Type::isPod);
if (!fieldsArePod) return false;

// for tuple, this is enough; for other composite types, see if any user-defined
// methods are present.
if (t->isTupleType()) return true;

const uast::AstNode* ast = nullptr;
if (auto id = ct->id()) ast = parsing::idToAst(context, std::move(id));

if (auto tfs = tryResolveDeinit(context, ast, t)) {
if (!tfs->isCompilerGenerated()) return false;
}
Expand Down Expand Up @@ -337,6 +355,61 @@ bool Type::isPod(Context* context, const Type* t) {
return true;
}

static bool const& isDefaultInitializableQuery(Context* context, const Type* t) {
QUERY_BEGIN(isDefaultInitializableQuery, context, t);

bool result = true;
if (!t || t->isUnknownType() || t->isErroneousType()) {
result = false;
} else if (t->isBuiltinType()) {
result = t->genericity() == Type::CONCRETE;
} else if (auto at = t->toArrayType()) {
result = isDefaultInitializableQuery(context, at->eltType().type());
} else if (t->isDomainType()) {
result = true; // production always returns true for domains.
} else if (t->isExternType()) {
// Currently extern records aren't initialized at all by default.
// But it's not necessarily reasonable to expect them to have
// initializers. See issue #7992 and preFold.cpp's setRecordDefaultValueFlags
// for FLAG_EXTERN.
result = true;
} else if (auto ct = t->toClassType()) {
result = ct->decorator().isNilable();
} else if (t->isTupleType()) {
result = checkFieldsWithPredicate(context, t, Type::isDefaultInitializable);
} else if (t->isRecordLike()) {
// If the type doesn't have a user-defined initializer or is a tuple, check
// its fields.
auto fieldsDefaultInitializable = true;
if (resolution::needCompilerGeneratedMethod(context, t, USTR("init"), /* parenless */ false)) {
fieldsDefaultInitializable = checkFieldsWithPredicate(context, t, Type::isDefaultInitializable);
}

if (!fieldsDefaultInitializable) {
result = false;
} else {
const uast::AstNode* ast = nullptr;
if (auto ct = t->getCompositeType()) {
if (auto id = ct->id()) {
ast = parsing::idToAst(context, std::move(id));
}
}

// note: production disallows default-init for generic fields like `var x;`,
// even if they are instantiated with a type that is default-initializable.
// But why? Seems like this is an implementation detail. Allow it in Dyno.

result = resolution::tryResolveZeroArgInit(context, ast, t) != nullptr;
}
}

return QUERY_END(result);
}

bool Type::isDefaultInitializable(Context* context, const Type* t) {
return isDefaultInitializableQuery(context, t);
}

bool Type::needsInitDeinitCall(const Type* t) {
if (t == nullptr || t->isUnknownType() || t->isErroneousType()) {
// can't do anything with these
Expand Down
1 change: 1 addition & 0 deletions frontend/test/resolution/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ comp_unit_test(testParamFolding)
comp_unit_test(testParamIf)
comp_unit_test(testParams)
comp_unit_test(testPoi)
comp_unit_test(testPrimHasDefaultValue)
comp_unit_test(testProcThis)
comp_unit_test(testPromotion)
comp_unit_test(testRanges)
Expand Down
Loading

0 comments on commit 0dfb840

Please sign in to comment.