Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dyno: implement "is default initializable" #26679

Merged
merged 12 commits into from
Feb 10, 2025
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