diff --git a/frontend/include/chpl/resolution/resolution-error-classes-list.h b/frontend/include/chpl/resolution/resolution-error-classes-list.h index 69402a9a0517..03079775d5d1 100644 --- a/frontend/include/chpl/resolution/resolution-error-classes-list.h +++ b/frontend/include/chpl/resolution/resolution-error-classes-list.h @@ -37,6 +37,7 @@ ERROR_CLASS(AmbiguousConfigSet, const uast::Variable*, std::string, std::string) ERROR_CLASS(AmbiguousIdentifier, const uast::Identifier*, bool, std::vector, std::vector) ERROR_CLASS(AmbiguousVisibilityIdentifier, UniqueString, ID, std::vector) ERROR_CLASS(AsWithUseExcept, const uast::Use*, const uast::As*) +ERROR_CLASS(AssignFieldBeforeInit, const uast::FnCall*, std::vector>) ERROR_CLASS(ConstRefCoercion, const uast::AstNode*, resolution::MostSpecificCandidate) WARNING_CLASS(Deprecation, std::string, const uast::AstNode*, const uast::NamedDecl*) ERROR_CLASS(DotExprInUseImport, const uast::VisibilityClause*, const uast::VisibilityClause::LimitationKind, const uast::Dot*) diff --git a/frontend/lib/resolution/InitResolver.cpp b/frontend/lib/resolution/InitResolver.cpp index ce536a00a25f..2c1e5416fd32 100644 --- a/frontend/lib/resolution/InitResolver.cpp +++ b/frontend/lib/resolution/InitResolver.cpp @@ -87,23 +87,48 @@ bool InitResolver::isCallToSuperInitRequired(void) { return false; } -void InitResolver::doSetupInitialState(void) { +bool InitResolver::setupFromType(const Type* type) { fieldToInitState_.clear(); + fieldIdsByOrdinal_.clear(); - // Determine the initial phase. - phase_ = isCallToSuperInitRequired() ? PHASE_NEED_SUPER_INIT - : PHASE_NEED_COMPLETE; - - auto ct = typeToCompType(initialRecvType_); + auto ct = typeToCompType(type); auto& rf = fieldsForTypeDecl(ctx_, ct, DefaultsPolicy::USE_DEFAULTS); - // Populate the fields with initial values. + // If any of the newly-set fields are type or params, setting them + // effectively means the receiver is a different type. + bool anyAffectsResultingType = false; + + // Populate the fields with values from the type. for (int i = 0; i < rf.numFields(); i++) { auto id = rf.fieldDeclId(i); FieldInitState state; - state = { i, ID(), rf.fieldType(i), rf.fieldName(i), false }; + auto fieldQt = rf.fieldType(i); + state = { i, ID(), fieldQt, rf.fieldName(i), false }; fieldToInitState_.insert({id, std::move(state)}); fieldIdsByOrdinal_.push_back(id); + + if (fieldQt.isType() || fieldQt.isParam()) { + anyAffectsResultingType = true; + } + } + + return anyAffectsResultingType; +} + +void InitResolver::doSetupInitialState(void) { + // Determine the initial phase. + phase_ = isCallToSuperInitRequired() ? PHASE_NEED_SUPER_INIT + : PHASE_NEED_COMPLETE; + + std::ignore = setupFromType(initialRecvType_); +} + +void InitResolver::markComplete() { + phase_ = PHASE_COMPLETE; + currentFieldIndex_ = fieldIdsByOrdinal_.size(); + for (auto& fieldPair : fieldToInitState_) { + auto& state = fieldPair.second; + state.isInitialized = true; } } @@ -172,7 +197,8 @@ void InitResolver::merge(owned& A, owned& B) { assert(stateA->isInitialized && stateB->isInitialized); state->isInitialized = true; - assert(stateA->qt.type() == stateB->qt.type()); + // Below, we issue an error if the resulting types do not compute to + // be the same, so picking one is fine. state->qt = stateA->qt; // TODO: need to keep track of these in a different way so we can @@ -512,7 +538,7 @@ void InitResolver::handleInitMarker(const uast::AstNode* node) { CHPL_REPORT(ctx_, PhaseTwoInitMarker, node, thisCompleteIds_); } else { thisCompleteIds_.push_back(node->id()); - phase_ = PHASE_COMPLETE; + markComplete(); } } @@ -535,13 +561,82 @@ bool InitResolver::handleCallToThisComplete(const FnCall* node) { } // TODO: Detect calls to super. -bool InitResolver::handleCallToSuperInit(const FnCall* node) { +bool InitResolver::handleCallToSuperInit(const FnCall* node, + const CallResolutionResult* c) { return false; } -// TODO: Detect calls to init. -bool InitResolver::handleCallToInit(const FnCall* node) { - return false; +bool InitResolver::applyResolvedInitCallToState(const FnCall* node, + const CallResolutionResult* c) { + if (!c || !c->mostSpecific().only()) return false; + + auto& only = c->mostSpecific().only(); + auto fn = only.fn(); + + CHPL_ASSERT(fn->formalName(0) == USTR("this")); + auto receiverType = fn->formalType(0).type(); + auto receiverCompType = typeToCompType(receiverType); + if (receiverCompType->instantiatedFromCompositeType()) { + receiverCompType = receiverCompType->instantiatedFromCompositeType(); + } + + auto initialCompType = typeToCompType(initialRecvType_); + if (initialCompType->instantiatedFromCompositeType()) { + initialCompType = initialCompType->instantiatedFromCompositeType(); + } + + CHPL_ASSERT(receiverCompType == initialCompType); + if (setupFromType(receiverType)) { + updateResolverVisibleReceiverType(); + } + + markComplete(); + return true; +} + +bool InitResolver::handleCallToInit(const FnCall* node, + const CallResolutionResult* c) { + auto calledExpr = node->calledExpression(); + if (!calledExpr) return false; + + if (auto calledIdent = calledExpr->toIdentifier()) { + if (calledIdent->name() != USTR("init")) return false; + } else if (auto calledDot = calledExpr->toDot()) { + if (calledDot->field() != USTR("init")) return false; + + auto receiver = calledDot->receiver(); + if (!receiver->isIdentifier() || + receiver->toIdentifier()->name() != USTR("this")) { + return false; + } + } + + // It's a call to 'this.init', which means any initialized fields are + // initialized erroneously. + if (currentFieldIndex_ != 0) { + std::vector> initializationPoints; + for (auto& fieldPair : fieldToInitState_) { + auto& state = fieldPair.second; + if (!state.isInitialized || state.initPointId.isEmpty()) continue; + + auto variable = parsing::idToAst(ctx_, fieldPair.first)->toVarLikeDecl(); + CHPL_ASSERT(variable != nullptr); + + initializationPoints.emplace_back(variable, state.initPointId); + } + CHPL_REPORT(ctx_, AssignFieldBeforeInit, node, initializationPoints); + } + + if (applyResolvedInitCallToState(node, c)) return true; + + // Something went wrong when resolving the 'init' call: we didn't + // try to resolve it, or we tried and failed, or we found a nonsensical + // candidate. + // + // By the rules of initializers, after this point variables will + // have been initialized, so mark them as such. + markComplete(); + return true; } void InitResolver::doDetectPossibleAssignmentToField(const OpCall* node) { @@ -622,8 +717,6 @@ bool InitResolver::handleResolvingCall(const Call* node) { if (auto fnCall = node->toFnCall()) { ret |= handleCallToThisComplete(fnCall); - ret |= handleCallToSuperInit(fnCall); - ret |= handleCallToInit(fnCall); } if (auto opCall = node->toOpCall()) { @@ -633,6 +726,18 @@ bool InitResolver::handleResolvingCall(const Call* node) { return ret; } +bool InitResolver::handleResolvedCall(const Call* node, + const CallResolutionResult* c) { + auto ret = false; + + if (auto fnCall = node->toFnCall()) { + ret |= handleCallToInit(fnCall, c); + ret |= handleCallToSuperInit(fnCall, c); + } + + return ret; +} + bool InitResolver::handleInitStatement(const uast::Init* node) { // current parser rules require this to always be this, but maybe someday // they won't. @@ -725,7 +830,7 @@ bool InitResolver::handleResolvingFieldAccess(const Dot* node) { auto& re = initResolver_.byPostorder.byAst(node); re.setToId(id); re.setType(qt); - return false; + return true; } } else { // Otherwise, proceed normally. @@ -733,7 +838,7 @@ bool InitResolver::handleResolvingFieldAccess(const Dot* node) { } } - return true; + return false; } void InitResolver::checkEarlyReturn(const Return* ret) { diff --git a/frontend/lib/resolution/InitResolver.h b/frontend/lib/resolution/InitResolver.h index 19826c378073..b753d0333b09 100644 --- a/frontend/lib/resolution/InitResolver.h +++ b/frontend/lib/resolution/InitResolver.h @@ -76,7 +76,9 @@ class InitResolver { } bool isCallToSuperInitRequired(void); + bool setupFromType(const types::Type* type); void doSetupInitialState(void); + void markComplete(); bool isFinalReceiverStateValid(void); const types::Type* computeReceiverTypeConsideringState(void); @@ -90,6 +92,9 @@ class InitResolver { void updateResolverVisibleReceiverType(void); bool implicitlyResolveFieldType(ID id); + bool applyResolvedInitCallToState(const uast::FnCall* node, + const CallResolutionResult* c); + const uast::AstNode* parentOf(const uast::AstNode* node); FieldInitState* fieldStateFromId(ID id); FieldInitState* fieldStateFromIndex(int idx); @@ -101,8 +106,8 @@ class InitResolver { // 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); - bool handleCallToInit(const uast::FnCall* node); + bool handleCallToSuperInit(const uast::FnCall* node, const CallResolutionResult* c); + bool handleCallToInit(const uast::FnCall* node, const CallResolutionResult* c); bool handleAssignmentToField(const uast::OpCall* node); ID solveNameConflictByIgnoringField(const NameVec& vec); @@ -121,8 +126,11 @@ class InitResolver { // Called on entry for calls. void doDetectPossibleAssignmentToField(const uast::OpCall* node); - // Called on exit for calls. + // Called on exit for calls. The first function is for cases in which we + // want to circumvent "regular" call resolution; the second is for when + // we want to use the results of resolving a call normally. bool handleResolvingCall(const uast::Call* node); + bool handleResolvedCall(const uast::Call* node, const CallResolutionResult* c); // Call on exit for 'init this' bool handleInitStatement(const uast::Init* node); diff --git a/frontend/lib/resolution/Resolver.cpp b/frontend/lib/resolution/Resolver.cpp index ff4e4bb948ec..6c087722f087 100644 --- a/frontend/lib/resolution/Resolver.cpp +++ b/frontend/lib/resolution/Resolver.cpp @@ -3570,7 +3570,11 @@ void Resolver::handleCallExpr(const uast::Call* call) { skip = UNKNOWN_PARAM; } else if (qt.isUnknown()) { skip = UNKNOWN_ACT; - } else if (t != nullptr) { + } else if (t != nullptr && !(ci.name() == USTR("init") && actualIdx == 0)) { + // For initializer calls, allow generic formals using the above + // condition; this way, 'this.init(..)' while 'this' is generic + // should be fine. + auto g = getTypeGenericity(context, t); bool isBuiltinGeneric = (g == Type::GENERIC && (t->isAnyType() || t->isBuiltinType())); @@ -3609,11 +3613,19 @@ void Resolver::handleCallExpr(const uast::Call* call) { // handle type inference for variables split-inited by 'out' formals adjustTypesForOutFormals(ci, actualAsts, c.mostSpecific()); + + if (initResolver) { + initResolver->handleResolvedCall(call, &c); + } } else { // We're skipping the call, but explicitly store the 'unknown type' // in the map. ResolvedExpression& r = byPostorder.byAst(call); r.setType(QualifiedType()); + + if (initResolver) { + initResolver->handleResolvedCall(call, /* call resolution result */ nullptr); + } } } diff --git a/frontend/lib/resolution/resolution-error-classes-list.cpp b/frontend/lib/resolution/resolution-error-classes-list.cpp index 8af72d47a529..a9bedf094646 100644 --- a/frontend/lib/resolution/resolution-error-classes-list.cpp +++ b/frontend/lib/resolution/resolution-error-classes-list.cpp @@ -274,6 +274,21 @@ void ErrorAsWithUseExcept::write(ErrorWriterBase& wr) const { wr.code(use, { as }); } +void ErrorAssignFieldBeforeInit::write(ErrorWriterBase& wr) const { + auto initCall = std::get(info_); + auto& initializations = std::get<1>(info_); + + wr.heading(kind_, type_, initCall, + "field initialization not allowed before 'super.init()' or 'this.init()'"); + + for (auto& initPair : initializations) { + auto decl = initPair.first; + auto id = initPair.second; + wr.note(id, "field '", decl->name(), "' is initialized before the 'init' call here:"); + wr.code(id, { id }); + } +} + void ErrorConstRefCoercion::write(ErrorWriterBase& wr) const { auto ast = std::get(info_); auto& c = std::get(info_); diff --git a/frontend/lib/uast/post-parse-checks.cpp b/frontend/lib/uast/post-parse-checks.cpp index 43a886b5dacd..c0c01dd8c1c3 100644 --- a/frontend/lib/uast/post-parse-checks.cpp +++ b/frontend/lib/uast/post-parse-checks.cpp @@ -114,6 +114,7 @@ struct Visitor { bool handleNestedDecoratorsInNew(const FnCall* node); bool handleNestedDecoratorsInTypeConstructors(const FnCall* node); void checkForNestedClassDecorators(const FnCall* node); + void checkExplicitInitCalls(const FnCall* node); void checkExplicitDeinitCalls(const FnCall* node); void checkNewBorrowed(const FnCall* node); void checkBorrowFromNew(const FnCall* node); @@ -617,29 +618,50 @@ void Visitor::checkForNestedClassDecorators(const FnCall* node) { } } -void Visitor::checkExplicitDeinitCalls(const FnCall* node) { - auto calledExpr = node->calledExpression(); - if (!calledExpr) return; - - bool doEmitError = false; +static const AstNode* isCallToMethodOrFnWithName(const FnCall* call, + UniqueString checkName) { + auto calledExpr = call->calledExpression(); + if (!calledExpr) return nullptr; if (auto ident = calledExpr->toIdentifier()) { - doEmitError = ident->name() == USTR("deinit"); + if (ident->name() == checkName) return ident; } else if (auto dot = calledExpr->toDot()) { - doEmitError = dot->field() == USTR("deinit"); + if (dot->field() == checkName) return dot; } - // Check if we are being called from the delete implementation. - if (doEmitError) { - if (auto foundFn = searchParents(asttags::Function, nullptr)) { - auto fn = foundFn->toFunction(); - if (fn->name() == "chpl__delete") doEmitError = false; + return nullptr; +} + +void Visitor::checkExplicitInitCalls(const FnCall* node) { + auto calledExpr = isCallToMethodOrFnWithName(node, USTR("init")); + if (!calledExpr) return; + + if (auto dot = calledExpr->toDot()) { + if (auto receiverIdent = dot->receiver()->toIdentifier()) { + // this.init(..) and super.init(..) are allowed. + if (receiverIdent->name() == USTR("this") || + receiverIdent->name() == USTR("super")) + return; } + } else { + // Standalone call; this is implicitly applied to 'this', so no problem. + return; } + error(node, "explicit calls to init() on anything other than" + " 'this' or 'super' are not allowed."); +} - if (doEmitError) { - error(node, "direct calls to deinit() are not allowed."); +void Visitor::checkExplicitDeinitCalls(const FnCall* node) { + auto calledExpr = isCallToMethodOrFnWithName(node, USTR("deinit")); + if (!calledExpr) return; + + // Check if we are being called from the delete implementation. + if (auto foundFn = searchParents(asttags::Function, nullptr)) { + auto fn = foundFn->toFunction(); + if (fn->name() == "chpl__delete") return; } + + error(node, "direct calls to deinit() are not allowed."); } void Visitor::checkNewBorrowed(const FnCall* node) { @@ -1494,6 +1516,7 @@ void Visitor::visit(const AttributeGroup* node) { void Visitor::visit(const FnCall* node) { checkNoDuplicateNamedArguments(node); checkForNestedClassDecorators(node); + checkExplicitInitCalls(node); checkExplicitDeinitCalls(node); checkNewBorrowed(node); checkBorrowFromNew(node); diff --git a/frontend/test/resolution/testInitSemantics.cpp b/frontend/test/resolution/testInitSemantics.cpp index 9be4d46040c6..cfd9136c5ec7 100644 --- a/frontend/test/resolution/testInitSemantics.cpp +++ b/frontend/test/resolution/testInitSemantics.cpp @@ -767,10 +767,302 @@ static void testOwnedUserInit(void) { std::ignore = resolveModule(ctx, mod->id()); } +static void testInitFromInit(void) { + // test calling 'this.init' from another initializer. + Context ctx; + Context* context = &ctx; + ErrorGuard guard(context); + + auto qt = resolveTypeOfXInit(context, + R"""( + record pair { + type fst; + type snd; + + proc init(type fst, type snd) { + this.fst = fst; + this.snd = snd; + } + + proc init(type both) { + this.init(both, (both, both)); + } + } + + var x = new pair(int); + )"""); + + assert(qt.type()); + auto recType = qt.type()->toCompositeType(); + assert(recType); + assert(recType->name() == "pair"); + + auto fields = fieldsForTypeDecl(context, recType, DefaultsPolicy::IGNORE_DEFAULTS); + + assert(fields.fieldName(0) == "fst"); + assert(fields.fieldName(1) == "snd"); + + auto fstType = fields.fieldType(0); + assert(fstType.type()); + assert(fstType.type()->isIntType()); + + auto sndType = fields.fieldType(1); + assert(sndType.type()); + auto sndTypeTup = sndType.type()->toTupleType(); + assert(sndTypeTup); + + auto fstTupEltType = sndTypeTup->elementType(0); + assert(fstTupEltType.type()); + assert(fstTupEltType.type()->isIntType()); + auto sndTupEltType = sndTypeTup->elementType(1); + assert(sndTupEltType.type()); + assert(sndTupEltType.type()->isIntType()); +} + +static void testInitInParamBranchFromInit(void) { + // test calling 'this.init' from another initializer. + Context ctx; + Context* context = &ctx; + ErrorGuard guard(context); + + auto qts = resolveTypesOfVariables(context, + R"""( + record pair { + type fst; + type snd; + + proc init(type fst, type snd) { + this.fst = fst; + this.snd = snd; + } + + proc init(param cond) { + if cond { + this.init(int, (int, int)); + } else { + this.fst = bool; + this.snd = bool; + } + } + } + + var x = new pair(true); + var y = new pair(false); + )""", {"x", "y"}); + + + { + auto qt = qts.at("x"); + assert(qt.type()); + auto recType = qt.type()->toCompositeType(); + assert(recType); + assert(recType->name() == "pair"); + + auto fields = fieldsForTypeDecl(context, recType, DefaultsPolicy::IGNORE_DEFAULTS); + + assert(fields.fieldName(0) == "fst"); + assert(fields.fieldName(1) == "snd"); + + auto fstType = fields.fieldType(0); + assert(fstType.type()); + assert(fstType.type()->isIntType()); + + auto sndType = fields.fieldType(1); + assert(sndType.type()); + auto sndTypeTup = sndType.type()->toTupleType(); + assert(sndTypeTup); + + auto fstTupEltType = sndTypeTup->elementType(0); + assert(fstTupEltType.type()); + assert(fstTupEltType.type()->isIntType()); + auto sndTupEltType = sndTypeTup->elementType(1); + assert(sndTupEltType.type()); + assert(sndTupEltType.type()->isIntType()); + } + { + auto qt = qts.at("y"); + assert(qt.type()); + auto recType = qt.type()->toCompositeType(); + assert(recType); + assert(recType->name() == "pair"); + + auto fields = fieldsForTypeDecl(context, recType, DefaultsPolicy::IGNORE_DEFAULTS); + + assert(fields.fieldName(0) == "fst"); + assert(fields.fieldName(1) == "snd"); + + auto fstType = fields.fieldType(0); + assert(fstType.type()); + assert(fstType.type()->isBoolType()); + auto sndType = fields.fieldType(1); + assert(sndType.type()); + assert(sndType.type()->isBoolType()); + } +} + +static void testInitInBranchFromInit(void) { + // test calling 'this.init' from another initializer. + Context ctx; + Context* context = &ctx; + ErrorGuard guard(context); + + auto qts = resolveTypesOfVariables(context, + R"""( + record pair { + type fst; + type snd; + + proc init(type fst, type snd) { + this.fst = fst; + this.snd = snd; + } + + proc init(cond: bool) { + if cond { + this.init(int, (int, int)); + } else { + this.fst = int; + this.snd = (int, int); + } + } + } + + var x = new pair(true); + var y = new pair(false); + )""", {"x", "y"}); + + + { + auto qt = qts.at("x"); + assert(qt.type()); + auto recType = qt.type()->toCompositeType(); + assert(recType); + assert(recType->name() == "pair"); + + auto fields = fieldsForTypeDecl(context, recType, DefaultsPolicy::IGNORE_DEFAULTS); + + assert(fields.fieldName(0) == "fst"); + assert(fields.fieldName(1) == "snd"); + + auto fstType = fields.fieldType(0); + assert(fstType.type()); + assert(fstType.type()->isIntType()); + + auto sndType = fields.fieldType(1); + assert(sndType.type()); + auto sndTypeTup = sndType.type()->toTupleType(); + assert(sndTypeTup); + + auto fstTupEltType = sndTypeTup->elementType(0); + assert(fstTupEltType.type()); + assert(fstTupEltType.type()->isIntType()); + auto sndTupEltType = sndTypeTup->elementType(1); + assert(sndTupEltType.type()); + assert(sndTupEltType.type()->isIntType()); + } + { + auto qt = qts.at("y"); + assert(qt.type()); + auto recType = qt.type()->toCompositeType(); + assert(recType); + assert(recType->name() == "pair"); + + auto fields = fieldsForTypeDecl(context, recType, DefaultsPolicy::IGNORE_DEFAULTS); + + assert(fields.fieldName(0) == "fst"); + assert(fields.fieldName(1) == "snd"); + + auto fstType = fields.fieldType(0); + assert(fstType.type()); + assert(fstType.type()->isIntType()); + + auto sndType = fields.fieldType(1); + assert(sndType.type()); + auto sndTypeTup = sndType.type()->toTupleType(); + assert(sndTypeTup); + + auto fstTupEltType = sndTypeTup->elementType(0); + assert(fstTupEltType.type()); + assert(fstTupEltType.type()->isIntType()); + auto sndTupEltType = sndTypeTup->elementType(1); + assert(sndTupEltType.type()); + assert(sndTupEltType.type()->isIntType()); + } +} + +static void testBadInitInBranchFromInit(void) { + // test calling 'this.init' from another initializer. + Context ctx; + Context* context = &ctx; + ErrorGuard guard(context); + + std::ignore = resolveTypeOfXInit(context, + R"""( + record pair { + type fst; + type snd; + + proc init(type fst, type snd) { + this.fst = fst; + this.snd = snd; + } + + proc init(cond: bool) { + if cond { + this.init(bool, (bool, bool)); + } else { + this.fst = int; + this.snd = (int, int); + } + } + } + + var x = new pair(true); + )"""); + + + // The above is invalid, since the two branches result in a different type. + assert(guard.realizeErrors() == 1); +} + +static void testAssignThenInit(void) { + // test calling 'this.init' from another initializer. + Context ctx; + Context* context = &ctx; + ErrorGuard guard(context); + + std::ignore = resolveTypeOfXInit(context, + R"""( + record pair { + type fst; + type snd; + + proc init(type fst, type snd) { + this.fst = fst; + this.snd = snd; + } + + proc init(type both) { + this.fst = both; + this.init(both, (both, both)); + } + } + + var x = new pair(int); + )"""); + + + // Can't initialize 'fst' then call initializer. Due to + // + // https://github.com/chapel-lang/chapel/issues/24900 + // + // The errors are currently issued twice. + assert(guard.realizeErrors() == 2); +} + // TODO: // - test using defaults for types and params // - also in conditionals -// - test this.init in branches // - test super.init in branches // - test this.complete in branches // - test then-only case (must know to insert 'else' branch eventually) @@ -798,6 +1090,12 @@ int main() { testRelevantInit(); testOwnedUserInit(); + testInitFromInit(); + testInitInParamBranchFromInit(); + testInitInBranchFromInit(); + testBadInitInBranchFromInit(); + testAssignThenInit(); + return 0; } diff --git a/frontend/test/resolution/testRanges.cpp b/frontend/test/resolution/testRanges.cpp index 924593f1361a..b9b89fa53ae8 100644 --- a/frontend/test/resolution/testRanges.cpp +++ b/frontend/test/resolution/testRanges.cpp @@ -21,14 +21,9 @@ #include "chpl/parsing/parsing-queries.h" #include "chpl/resolution/resolution-queries.h" -#include "chpl/resolution/scope-queries.h" -#include "chpl/types/all-types.h" -#include "chpl/uast/Identifier.h" -#include "chpl/uast/Module.h" -#include "chpl/uast/Record.h" -#include "chpl/uast/Variable.h" -static QualifiedType getRangeIndexType(Context* context, const RecordType* r, const std::string& ensureBoundedType) { +static std::tuple +getRangeInfo(Context* context, const RecordType* r) { assert(r->name() == "_range"); auto fields = fieldsForTypeDecl(context, r, DefaultsPolicy::IGNORE_DEFAULTS); @@ -44,7 +39,7 @@ static QualifiedType getRangeIndexType(Context* context, const RecordType* r, co auto id = boundedValue->value(); auto astNode = idToAst(context, id)->toNamedDecl(); assert(astNode != nullptr); - assert(astNode->name().str() == ensureBoundedType); + std::string boundTypeStr = astNode->name().str(); auto stridable = fields.fieldType(2); assert(stridable.kind() == QualifiedType::PARAM); @@ -54,9 +49,16 @@ static QualifiedType getRangeIndexType(Context* context, const RecordType* r, co auto idS = stridableValue->value(); auto astNodeS = idToAst(context, idS)->toNamedDecl(); assert(astNodeS != nullptr); - assert(astNodeS->name().str() == "one"); + std::string stridesStr = astNodeS->name().str(); + + return std::make_tuple(fields.fieldType(0), boundTypeStr, stridesStr); +} - return fields.fieldType(0); +static QualifiedType getRangeIndexType(Context* context, const RecordType* r, const std::string& ensureBoundedType) { + auto info = getRangeInfo(context, r); + assert(std::get<1>(info) == ensureBoundedType); + assert(std::get<2>(info) == "one"); + return std::get<0>(info); } static void test1() { @@ -271,6 +273,55 @@ static void test10(Context* context) { } } +static void test11(Context* context) { + // test the by operator on a bounded range + ErrorGuard guard(context); + context->advanceToNextRevision(false); + setupModuleSearchPaths(context, false, false, {}, {}); + auto qts = resolveTypesOfVariables(context, + R""""( + var x1 = 1..10; + var x2 = x1 by 2; + var x3 = x1 by -1; + var x4 = x1 by -2; + + var newStride = 10; + var x5 = x1 by newStride; + + var y1 = 1..10; + var y2 = y1 by -1; + var y3 = y2 by -1; + var y4 = y2 by 5; + var y5 = y2 by -5; + )"""", {"x1", "x2", "x3", "x4", "x5", "y1", "y2", "y3", "y4", "y5"}); + + + auto check = [&](const std::string& var, const std::string& stride) { + auto qt = qts.at(var); + assert(qt.type() != nullptr); + auto rangeType = qt.type()->toRecordType(); + assert(rangeType != nullptr); + auto info = getRangeInfo(context, rangeType); + + assert(std::get<0>(info).type() != nullptr); + assert(std::get<0>(info).type()->isIntType()); + assert(std::get<1>(info) == "both"); + assert(std::get<2>(info) == stride); + }; + + check("x1", "one"); + check("x2", "positive"); + check("x3", "negOne"); + check("x4", "negative"); + check("x5", "any"); + + check("y1", "one"); + check("y2", "negOne"); + check("y3", "one"); + check("y4", "negative"); + check("y5", "positive"); +} + int main() { // first test runs without environment and stdlib. test1(); @@ -288,5 +339,6 @@ int main() { test8(ctx); test9(ctx); test10(ctx); + test11(ctx); return 0; } diff --git a/test/errors/parsing/initnotthis.1.good b/test/errors/parsing/initnotthis.1.good new file mode 100644 index 000000000000..ac9067e5f2b4 --- /dev/null +++ b/test/errors/parsing/initnotthis.1.good @@ -0,0 +1,3 @@ +initnotthis.chpl:6: In initializer: +initnotthis.chpl:8: error: explicit calls to init() on anything other than 'this' or 'super' are not allowed +initnotthis.chpl:13: error: explicit calls to init() on anything other than 'this' or 'super' are not allowed diff --git a/test/errors/parsing/initnotthis.2.good b/test/errors/parsing/initnotthis.2.good new file mode 100644 index 000000000000..002e9efa008b --- /dev/null +++ b/test/errors/parsing/initnotthis.2.good @@ -0,0 +1,6 @@ +─── error in initnotthis.chpl:8 ─── + Explicit calls to init() on anything other than 'this' or 'super' are not allowed. + +─── error in initnotthis.chpl:13 ─── + Explicit calls to init() on anything other than 'this' or 'super' are not allowed. + diff --git a/test/errors/parsing/initnotthis.chpl b/test/errors/parsing/initnotthis.chpl new file mode 100644 index 000000000000..b94e5e544c7c --- /dev/null +++ b/test/errors/parsing/initnotthis.chpl @@ -0,0 +1,13 @@ +record myRec {} + +record container { + var rec: myRec; + + proc init() { + init this; + rec.init(); + } +} + +var rec = new myRec(); +rec.init();