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: Improve initializers on classes that inherit #25965

Merged
merged 5 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 96 additions & 109 deletions frontend/lib/resolution/InitResolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
// "or this.init()"
// - [ ] "cannot pass a record to a function before 'init this'"
//
// Refactoring:
// Try to move init-specific logic in Resolver into InitResolver
//
namespace chpl {
namespace resolution {

Expand Down Expand Up @@ -112,6 +115,25 @@ bool InitResolver::setupFromType(const Type* type) {
return anyAffectsResultingType;
}

void InitResolver::resolveImplicitSuperInit() {
// TODO: better error when arg-less super.init() does not exist
// -- e.g. class Parent { type T; var x : T; }

if (phase_ == PHASE_NEED_SUPER_INIT &&
superType_->isObjectType() == false) {
std::vector<CallInfoActual> actuals;
auto superCT = ClassType::get(ctx_, superType_, nullptr,
ClassTypeDecorator(ClassTypeDecorator::BORROWED_NONNIL));
auto superQT = QualifiedType(QualifiedType::INIT_RECEIVER, superCT);
actuals.push_back(CallInfoActual(superQT, USTR("this")));
auto ci = CallInfo(USTR("init"), superQT, true, false, false, actuals);
auto inScopes = CallScopeInfo::forNormalCall(initResolver_.scopeStack.back(), initResolver_.poiScope);
auto c = resolveGeneratedCall(ctx_, fn_->body()->child(0), ci, inScopes);

updateSuperType(&c);
}
}

void InitResolver::doSetupInitialState(void) {
// Determine the initial phase.
phase_ = isCallToSuperInitRequired() ? PHASE_NEED_SUPER_INIT
Expand Down Expand Up @@ -450,6 +472,10 @@ void InitResolver::updateResolverVisibleReceiverType(void) {
auto tfs = computeTypedSignature(updated);
initResolver_.typedSignature = tfs;
currentRecvType_ = updated;

// Update type of 'this'
auto& thisRE = initResolver_.byPostorder.byAst(fn_->thisFormal());
thisRE.setType(tfs->formalType(0));
}
}

Expand Down Expand Up @@ -496,6 +522,9 @@ bool InitResolver::isMentionOfNodeInLhsOfAssign(const AstNode* node) {
const AstNode* prior = node;

for (; parent != nullptr; parent = parentOf(parent)) {
// Stop searching upwards once we hit the initializer symbol
if (parent->id().isSymbolDefiningScope()) break;

if (auto opCall = parent->toOpCall())
if (opCall->op() == USTR("="))
if (prior && opCall->actual(0) == prior)
Expand All @@ -506,39 +535,31 @@ bool InitResolver::isMentionOfNodeInLhsOfAssign(const AstNode* node) {
return false;
}

ID InitResolver::fieldIdFromName(UniqueString name) {
if (!isNameOfField(ctx_, name, initialRecvType_)) return ID();
// TODO: Need to replace this as we continue to build it up?
auto ct = initialRecvType_->getCompositeType();
auto ret = parsing::fieldIdWithName(ctx_, ct->id(), name);
return ret;
}

ID InitResolver::fieldIdFromPossibleMentionOfField(const AstNode* node) {
UniqueString name;
std::pair<ID,bool> InitResolver::fieldIdFromPossibleMentionOfField(const AstNode* node) {
if (node->isDot()) {
auto dot = node->toDot();
if (auto ident = dot->receiver()->toIdentifier()) {
if (dot->field() != "init" &&
(ident->name() == "this" || ident->name() == "super")) {
auto ct = currentRecvType_->getCompositeType();

// TODO: Just remove this and check the set ID in all cases?
if (auto ident = node->toIdentifier()) {
name = ident->name();
} else if (auto dot = node->toDot()) {
if (auto lhs = dot->receiver()->toIdentifier()) {
if (lhs->name() == USTR("this")) name = dot->field();
if (auto decl = findFieldByName(ctx_, aggregateDecl_, ct, dot->field())) {
return {decl->id(), !aggregateDecl_->id().contains(decl->id())};
}
}
}
} else if (auto ident = node->toIdentifier()) {
riftEmber marked this conversation as resolved.
Show resolved Hide resolved
auto& re = initResolver_.byPostorder.byAst(ident);
auto fieldID = re.toId();

// Note: Assumes an Identifier resolving to a field is a reliable
// indication that the field belongs to the type being initialized.
if (!fieldID.isEmpty() && parsing::idIsField(ctx_, fieldID)) {
return {fieldID, !aggregateDecl_->id().contains(fieldID)};
}
}

if (name.isEmpty()) return ID();

auto ret = ID();
auto fieldId = fieldIdFromName(name);
auto& re = initResolver_.byPostorder.byAst(node);

if (fieldId.isEmpty()) return ret;

// TODO: Handle case where "this.foo" resolve to a paren-less method
auto id = re.toId();
if (!id.isEmpty() && parsing::idIsField(ctx_, id)) return id;

return ret;
return {ID(), false};
}

bool InitResolver::isFieldInitialized(ID fieldId) {
Expand Down Expand Up @@ -581,24 +602,16 @@ bool InitResolver::handleCallToSuperInit(const FnCall* node,
if (auto dot = node->calledExpression()->toDot()) {
if (auto ident = dot->receiver()->toIdentifier()) {
if (ident->name() == "super" && dot->field() == "init") {
auto& msc = c->mostSpecific().only();
auto superThis = msc.formalActualMap().byFormalIdx(0).formalType();

const BasicClassType* superType = nullptr;
if (auto ct = superThis.type()->toClassType()) {
superType = ct->basicClassType();
} else {
superType = superThis.type()->toBasicClassType();
if (useOfSuperFields_.size() > 0) {
for (auto [fieldID, useID] : useOfSuperFields_) {
auto fieldNode = parsing::idToAst(ctx_, fieldID)->toNamedDecl();
ctx_->error(useID, "Cannot access parent field \"%s\" before super.init() or this.init()", fieldNode->name().c_str());
}
}

this->superType_ = superType;

// Only update the current receiver if the parent was generic.
if (superType->instantiatedFromCompositeType() != nullptr) {
updateResolverVisibleReceiverType();
}
updateSuperType(c);

phase_ = PHASE_NEED_COMPLETE;
this->explicitSuperInit = true;

return true;
}
Expand All @@ -608,6 +621,20 @@ bool InitResolver::handleCallToSuperInit(const FnCall* node,
return false;
}

void InitResolver::updateSuperType(const CallResolutionResult* c) {
auto& msc = c->mostSpecific().only();
auto superThis = msc.formalActualMap().byFormalIdx(0).formalType().type();

this->superType_ = superThis->getCompositeType()->toBasicClassType();

// Only update the current receiver if the parent was generic.
if (superType_->instantiatedFromCompositeType() != nullptr) {
updateResolverVisibleReceiverType();
}

phase_ = PHASE_NEED_COMPLETE;
}

bool InitResolver::applyResolvedInitCallToState(const FnCall* node,
const CallResolutionResult* c) {
if (!c || !c->mostSpecific().only()) return false;
Expand Down Expand Up @@ -706,8 +733,10 @@ bool InitResolver::handleAssignmentToField(const OpCall* node) {
auto rhs = node->actual(1);

// TODO: Is 'field' or 'this.field' too strict of a pattern?
auto fieldId = fieldIdFromPossibleMentionOfField(lhs);
if (fieldId.isEmpty()) return false;
auto [fieldId, isSuperField] = fieldIdFromPossibleMentionOfField(lhs);

// parent fields already evaluated by 'handleUseOfField'
if (fieldId.isEmpty() || isSuperField) return false;

auto state = fieldStateFromId(fieldId);
CHPL_ASSERT(state);
Expand Down Expand Up @@ -799,18 +828,36 @@ bool InitResolver::handleInitStatement(const uast::Init* node) {
}

bool InitResolver::handleUseOfField(const AstNode* node) {
auto id = fieldIdFromPossibleMentionOfField(node);
auto [id, isSuperField] = fieldIdFromPossibleMentionOfField(node);
if (id.isEmpty()) return false;

if (isFieldInitialized(id)) return false;
if (!isSuperField && isFieldInitialized(id)) return false;

auto state = fieldStateFromId(id);
bool isValidPreInitMention = false;

if (isDescendingIntoAssignment_)
if (isDescendingIntoAssignment_ && isSuperField == false)
if (isMentionOfNodeInLhsOfAssign(node))
isValidPreInitMention = true;

// uses of parent fields might be valid during PHASE_NEED_COMPLETE if we
// implicitly resolved super.init(). We still need to keep track of parent
// field usage in case an explicit super.init() comes up later.
if (isSuperField) {
if (explicitSuperInit == false && phase_ != PHASE_COMPLETE) {
// Upon first use of parent field, need to insert a super.init() call
// TODO: store this as an associated action
if (useOfSuperFields_.empty()) {
this->resolveImplicitSuperInit();
}

useOfSuperFields_.push_back({id, node->id()});
return true;
} else {
riftEmber marked this conversation as resolved.
Show resolved Hide resolved
isValidPreInitMention = true;
}
}

if (!isValidPreInitMention) {
ctx_->error(node, "'%s' is used before it is initialized",
state->name.c_str());
Expand All @@ -832,66 +879,6 @@ ID InitResolver::solveNameConflictByIgnoringField(const MatchingIdsWithName& vec
return ret;
}

bool InitResolver::handleResolvingFieldAccess(const Identifier* node) {
bool resolvingCalledIdent = initResolver_.nearestCalledExpression() == node;
auto parenlessInfo = Resolver::ParenlessOverloadInfo();
auto ids =
initResolver_.lookupIdentifier(node, resolvingCalledIdent, parenlessInfo);

// Handle and exit early if there were no ambiguities.
if (ids.numIds() == 1) {
auto& id = ids.firstId();
if (parsing::idIsField(ctx_, id)) {
auto state = fieldStateFromId(id);
auto qt = state->qt;
auto& re = initResolver_.byPostorder.byAst(node);
re.setToId(id);
re.setType(qt);
return true;
}
}

// If there are two names and one is a field, get the other name.
// TODO: is this still needed?
auto id = solveNameConflictByIgnoringField(ids);
if (!id.isEmpty()) {
CHPL_ASSERT(!parsing::idIsField(ctx_, id));
const bool localGenericToUnknown = true;
auto qt = initResolver_.typeForId(id, localGenericToUnknown);
auto& re = initResolver_.byPostorder.byAst(node);
re.setToId(id);
re.setType(qt);
return true;
}

return false;
}

bool InitResolver::handleResolvingFieldAccess(const Dot* node) {
if (auto ident = node->receiver()->toIdentifier()) {

// TODO: Check parent for more context, and 'this.this'.
if (ident->name() == USTR("this")) {
auto name = node->field();
auto id = fieldIdFromName(name);
if (!id.isEmpty()) {
auto state = fieldStateFromId(id);
CHPL_ASSERT(state);
auto qt = state->qt;
auto& re = initResolver_.byPostorder.byAst(node);
re.setToId(id);
re.setType(qt);
return true;
}
} else {
// Otherwise, proceed normally.
return false;
}
}

return false;
}

void InitResolver::checkEarlyReturn(const Return* ret) {
// Something like:
// this.z = 5;
Expand Down
21 changes: 15 additions & 6 deletions frontend/lib/resolution/InitResolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class InitResolver {
Resolver& initResolver_;
const uast::Function* fn_;
const types::Type* initialRecvType_;
const uast::AggregateDecl* aggregateDecl_;

std::map<ID, FieldInitState> fieldToInitState_;
std::vector<ID> fieldIdsByOrdinal_;
Expand All @@ -63,15 +64,24 @@ class InitResolver {
std::vector<ID> thisCompleteIds_;
bool isDescendingIntoAssignment_ = false;
const types::Type* currentRecvType_ = initialRecvType_;

const types::BasicClassType* superType_ = nullptr;

bool explicitSuperInit = false;

// Uses of parent fields before a super.init is seen.
// Stores field ID and ID of the uAST referencing the field.
std::vector<std::pair<ID, ID>> useOfSuperFields_;

InitResolver(Context* ctx, Resolver& visitor,
const uast::Function* fn,
const types::Type* recvType)
: ctx_(ctx),
initResolver_(visitor),
fn_(fn),
initialRecvType_(recvType) {
auto typeID = initialRecvType_->getCompositeType()->id();
aggregateDecl_ = parsing::idToAst(ctx_, typeID)->toAggregateDecl();
return;
}

Expand Down Expand Up @@ -99,14 +109,17 @@ class InitResolver {
FieldInitState* fieldStateFromId(ID id);
FieldInitState* fieldStateFromIndex(int idx);
bool isMentionOfNodeInLhsOfAssign(const uast::AstNode* node);
ID fieldIdFromName(UniqueString name);
ID fieldIdFromPossibleMentionOfField(const uast::AstNode* node);
std::pair<ID,bool> fieldIdFromPossibleMentionOfField(const uast::AstNode* node);
bool isFieldInitialized(ID fieldId);

// handle a call to this.complete() or init this.
void handleInitMarker(const uast::AstNode* node);
bool handleCallToThisComplete(const uast::FnCall* node);

bool handleCallToSuperInit(const uast::FnCall* node, const CallResolutionResult* c);
void resolveImplicitSuperInit();
void updateSuperType(const CallResolutionResult* c);

bool handleCallToInit(const uast::FnCall* node, const CallResolutionResult* c);
bool handleAssignmentToField(const uast::OpCall* node);
ID solveNameConflictByIgnoringField(const MatchingIdsWithName& vec);
Expand Down Expand Up @@ -138,10 +151,6 @@ class InitResolver {
// Called in exit for dot expressions and identifiers.
bool handleUseOfField(const uast::AstNode* node);

// Called on entry for dot expressions and identifiers.
bool handleResolvingFieldAccess(const uast::Identifier* node);
bool handleResolvingFieldAccess(const uast::Dot* node);

// Called to produce the final function signature.
const TypedFnSignature* finalize(void);

Expand Down
Loading