diff --git a/compiler/passes/convert-uast.cpp b/compiler/passes/convert-uast.cpp index f007cd4fbd1..eab022d674d 100644 --- a/compiler/passes/convert-uast.cpp +++ b/compiler/passes/convert-uast.cpp @@ -4530,6 +4530,11 @@ Type* Converter::helpConvertType(types::QualifiedType qt) { case typetags::UnknownType: return dtUnknown; case typetags::VoidType: return dtVoid; + // subclasses of IterableType + case typetags::FnIteratorType: return dtUnknown; // a lie + case typetags::LoopExprIteratorType: return dtUnknown; // a lie + case typetags::PromotionIteratorType: return dtUnknown; // a lie + // subclasses of BuiltinType // concrete builtin types @@ -4592,6 +4597,8 @@ Type* Converter::helpConvertType(types::QualifiedType qt) { case typetags::END_CompositeType: case typetags::START_PrimitiveType: case typetags::END_PrimitiveType: + case typetags::START_IteratorType: + case typetags::END_IteratorType: case typetags::NUM_TYPE_TAGS: INT_FATAL("should not be reachable"); return dtUnknown; diff --git a/frontend/include/chpl/framework/Context.h b/frontend/include/chpl/framework/Context.h index 7b68949aeb6..3d502879d31 100644 --- a/frontend/include/chpl/framework/Context.h +++ b/frontend/include/chpl/framework/Context.h @@ -879,6 +879,47 @@ class Context { #endif ; + /** + Note an warning for the currently running query. + This is a convenience overload. + This version takes in a Location and a printf-style format string. + */ + void warning(Location loc, const char* fmt, ...) + #ifndef DOXYGEN + // docs generator has trouble with the attribute applied to 'build' + // so the above ifndef works around the issue. + __attribute__ ((format (printf, 3, 4))) + #endif + ; + + /** + Note an warning for the currently running query. + This is a convenience overload. + This version takes in an ID and a printf-style format string. + The ID is used to compute a Location using parsing::locateId. + */ + void warning(ID id, const char* fmt, ...) + #ifndef DOXYGEN + // docs generator has trouble with the attribute applied to 'build' + // so the above ifndef works around the issue. + __attribute__ ((format (printf, 3, 4))) + #endif + ; + + /** + Note an warning for the currently running query. + This is a convenience overload. + This version takes in an AST node and a printf-style format string. + The AST node is used to compute a Location by using a parsing::locateAst. + */ + void warning(const uast::AstNode* ast, const char* fmt, ...) + #ifndef DOXYGEN + // docs generator has trouble with the attribute applied to 'build' + // so the above ifndef works around the issue. + __attribute__ ((format (printf, 3, 4))) + #endif + ; + /** Sets the enableDebugTrace flag. This was needed because the context in main gets created before the arguments to the compiler are parsed. diff --git a/frontend/include/chpl/resolution/resolution-types.h b/frontend/include/chpl/resolution/resolution-types.h index d8c043adaa8..0a064d06f3c 100644 --- a/frontend/include/chpl/resolution/resolution-types.h +++ b/frontend/include/chpl/resolution/resolution-types.h @@ -1105,6 +1105,13 @@ class TypedFnSignature { return fetchIterKindStr(context, str) && str == USTR("follower"); } + /** Returns 'true' if this signature is for a parallel iterator. */ + bool isParallelIterator(Context* context) const { + return isParallelStandaloneIterator(context) || + isParallelLeaderIterator(context) || + isParallelFollowerIterator(context); + } + /** Returns 'true' if this signature is for a serial iterator. */ bool isSerialIterator(Context* context) const { UniqueString str; diff --git a/frontend/include/chpl/types/FnIteratorType.h b/frontend/include/chpl/types/FnIteratorType.h new file mode 100644 index 00000000000..079138d4e96 --- /dev/null +++ b/frontend/include/chpl/types/FnIteratorType.h @@ -0,0 +1,69 @@ +/* + * Copyright 2024 Hewlett Packard Enterprise Development LP + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is licensed under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CHPL_TYPES_FN_ITERATOR_TYPE_H +#define CHPL_TYPES_FN_ITERATOR_TYPE_H + +#include "chpl/types/Type.h" +#include "chpl/types/QualifiedType.h" +#include "chpl/resolution/resolution-types.h" +#include "chpl/types/IteratorType.h" + +namespace chpl { +namespace types { + +class FnIteratorType final : public IteratorType { + private: + /* + The iterator procedure that was invoked to construct an iterator of this + type. E.g,, the type of `foo()` will contain `iter foo()` as the iterator + function. + */ + const resolution::TypedFnSignature* iteratorFn_; + + FnIteratorType(QualifiedType yieldType, const resolution::TypedFnSignature* iteratorFn) + : IteratorType(typetags::FnIteratorType, std::move(yieldType)), iteratorFn_(iteratorFn) {} + + bool contentsMatchInner(const Type* other) const override { + auto rhs = (FnIteratorType*) other; + return this->yieldType_ == rhs->yieldType_ && + this->iteratorFn_ == rhs->iteratorFn_; + } + + void markUniqueStringsInner(Context* context) const override; + + static const owned & + getFnIteratorType(Context* context, + QualifiedType yieldType, + const resolution::TypedFnSignature* iteratorFn); + + public: + static const FnIteratorType* get(Context* context, + QualifiedType yieldType, + const resolution::TypedFnSignature* iteratorFn); + + const resolution::TypedFnSignature* iteratorFn() const { + return iteratorFn_; + } +}; + +} // end namespace types +} // end namespace chpl + +#endif diff --git a/frontend/include/chpl/types/IteratorType.h b/frontend/include/chpl/types/IteratorType.h new file mode 100644 index 00000000000..7f5f34b1c2f --- /dev/null +++ b/frontend/include/chpl/types/IteratorType.h @@ -0,0 +1,57 @@ +/* + * Copyright 2024 Hewlett Packard Enterprise Development LP + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is licensed under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CHPL_TYPES_ITERATOR_TYPE_H +#define CHPL_TYPES_ITERATOR_TYPE_H + +#include "chpl/types/Type.h" +#include "chpl/types/QualifiedType.h" + +namespace chpl { +namespace types { + +class IteratorType : public Type { + protected: + /* + The type produced by this iterator. E.g., in a loop such + as the right hand side in the below assignment: + + var A = foreach i in 1..10 do (i,i); + + the yield type is (int, int). + */ + QualifiedType yieldType_; + + IteratorType(typetags::TypeTag tag, QualifiedType yieldType) + : Type(tag), yieldType_(std::move(yieldType)) {} + + Genericity genericity() const override { + return CONCRETE; + } + + public: + const QualifiedType& yieldType() const { + return yieldType_; + } +}; + +} // end namespace types +} // end namespace chpl + +#endif diff --git a/frontend/include/chpl/types/LoopExprIteratorType.h b/frontend/include/chpl/types/LoopExprIteratorType.h new file mode 100644 index 00000000000..ba39bf4058d --- /dev/null +++ b/frontend/include/chpl/types/LoopExprIteratorType.h @@ -0,0 +1,124 @@ +/* + * Copyright 2024 Hewlett Packard Enterprise Development LP + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is licensed under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CHPL_TYPES_LOOP_EXPR_ITERATOR_TYPE_H +#define CHPL_TYPES_LOOP_EXPR_ITERATOR_TYPE_H + +#include "chpl/types/Type.h" +#include "chpl/types/QualifiedType.h" +#include "chpl/types/IteratorType.h" + +namespace chpl { +namespace types { + +class LoopExprIteratorType final : public IteratorType { + private: + /* + Whether the loop expression is zippered. In production, zippered + loop expressions contain all of their iterands and separately invoke + leader/follower iterators on each one + */ + bool isZippered_; + /* + Whether this loop can be iterated over in parallel. Some loops + ('for' and 'foreach') can't, even when their iterands support parallel + iteration. + */ + bool supportsParallel_; + /* + The type of the thing being iterated over, used for resolving the + needed leader/follower iterators. If the loop is zippered, iterand + should be a tuple type. + */ + QualifiedType iterand_; + /* + The ID of the AST node that caused this loop type to be constructed. + This is part of the type so that loop expressions created at different + places are considered to have a different type. + + Note: this may need to change if we compiler-generated loop expressions, + since the IDs of those would be nebulous. + */ + ID sourceLocation_; + + LoopExprIteratorType(QualifiedType yieldType, + bool isZippered, + bool supportsParallel, + QualifiedType iterand, + ID sourceLocation) + : IteratorType(typetags::LoopExprIteratorType, std::move(yieldType)), + isZippered_(isZippered), supportsParallel_(supportsParallel), + iterand_(std::move(iterand)), sourceLocation_(std::move(sourceLocation)) { + if (isZippered_) { + CHPL_ASSERT(iterand_.type() && iterand_.type()->isTupleType()); + } + } + + bool contentsMatchInner(const Type* other) const override { + auto rhs = (LoopExprIteratorType*) other; + return yieldType_ == rhs->yieldType_ && + isZippered_ == rhs->isZippered_ && + supportsParallel_ == rhs->supportsParallel_ && + iterand_ == rhs->iterand_ && + sourceLocation_ == rhs->sourceLocation_; + } + + void markUniqueStringsInner(Context* context) const override { + yieldType_.mark(context); + iterand_.mark(context); + sourceLocation_.mark(context); + } + + static const owned& + getLoopExprIteratorType(Context* context, + QualifiedType yieldType, + bool isZippered, + bool supportsParallel, + QualifiedType iterand, + ID sourceLocation); + + public: + static const LoopExprIteratorType* get(Context* context, + QualifiedType yieldType, + bool isZippered, + bool supportsParallel, + QualifiedType iterand, + ID sourceLocation); + + bool isZippered() const { + return isZippered_; + } + + bool supportsParallel() const { + return supportsParallel_; + } + + const ID& sourceLocation() const { + return sourceLocation_; + } + + const QualifiedType& iterand() const { + return iterand_; + } +}; + +} // end namespace types +} // end namespace chpl + +#endif diff --git a/frontend/include/chpl/types/PromotionIteratorType.h b/frontend/include/chpl/types/PromotionIteratorType.h new file mode 100644 index 00000000000..591782cac35 --- /dev/null +++ b/frontend/include/chpl/types/PromotionIteratorType.h @@ -0,0 +1,84 @@ +/* + * Copyright 2024 Hewlett Packard Enterprise Development LP + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is licensed under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CHPL_TYPES_PROMOTION_ITERATOR_TYPE_H +#define CHPL_TYPES_PROMOTION_ITERATOR_TYPE_H + +#include "chpl/types/Type.h" +#include "chpl/types/QualifiedType.h" +#include "chpl/types/IteratorType.h" +#include "chpl/resolution/resolution-types.h" +#include "chpl/uast/Decl.h" +#include + +namespace chpl { + +namespace types { + +class PromotionIteratorType final : public IteratorType { + private: + /* The scalar (non-array) function that was invoked as part of the promotion. */ + const resolution::TypedFnSignature* scalarFn_; + /* + Mapping of promoted formals to their array (non-scalar) types, for the + purposes of determining the parallel iteration strategy. + */ + const resolution::SubstitutionsMap promotedFormals_; + + PromotionIteratorType(const resolution::TypedFnSignature* scalarFn, + resolution::SubstitutionsMap promotedFormals, + QualifiedType yieldType) + : IteratorType(typetags::PromotionIteratorType, std::move(yieldType)), + scalarFn_(scalarFn), + promotedFormals_(std::move(promotedFormals)) {} + + bool contentsMatchInner(const Type* other) const override { + auto rhs = (PromotionIteratorType*) other; + return this->yieldType_ == rhs->yieldType_ && + this->scalarFn_ == rhs->scalarFn_ && + this->promotedFormals_ == rhs->promotedFormals_; + } + + void markUniqueStringsInner(Context* context) const override; + + static const owned& + getPromotionIteratorType(Context* context, + QualifiedType yieldType, + const resolution::TypedFnSignature* scalarFn, + resolution::SubstitutionsMap promotedFormals); + + public: + static const PromotionIteratorType* get(Context* context, + QualifiedType yieldType, + const resolution::TypedFnSignature* scalarFn, + resolution::SubstitutionsMap promotedFormals); + + const resolution::TypedFnSignature* scalarFn() const { + return scalarFn_; + } + + const resolution::SubstitutionsMap& promotedFormals() const { + return promotedFormals_; + } +}; + +} // end namespace types +} // end namespace chpl + +#endif diff --git a/frontend/include/chpl/types/all-types.h b/frontend/include/chpl/types/all-types.h index a8fcdd61a41..a2b17759cd8 100644 --- a/frontend/include/chpl/types/all-types.h +++ b/frontend/include/chpl/types/all-types.h @@ -33,12 +33,16 @@ #include "chpl/types/EnumType.h" #include "chpl/types/ErroneousType.h" #include "chpl/types/ExternType.h" +#include "chpl/types/FnIteratorType.h" #include "chpl/types/ImagType.h" #include "chpl/types/IntType.h" +#include "chpl/types/IteratorType.h" +#include "chpl/types/LoopExprIteratorType.h" #include "chpl/types/NilType.h" #include "chpl/types/NothingType.h" #include "chpl/types/Param.h" #include "chpl/types/PrimitiveType.h" +#include "chpl/types/PromotionIteratorType.h" #include "chpl/types/QualifiedType.h" #include "chpl/types/RealType.h" #include "chpl/types/RecordType.h" diff --git a/frontend/include/chpl/types/type-classes-list.h b/frontend/include/chpl/types/type-classes-list.h index 80943d0682d..b876d7249de 100644 --- a/frontend/include/chpl/types/type-classes-list.h +++ b/frontend/include/chpl/types/type-classes-list.h @@ -48,6 +48,13 @@ TYPE_NODE(UnknownType) TYPE_NODE(VoidType) TYPE_NODE(CPtrType) +TYPE_BEGIN_SUBCLASSES(IteratorType) + TYPE_NODE(LoopExprIteratorType) + TYPE_NODE(FnIteratorType) + TYPE_NODE(PromotionIteratorType) +TYPE_END_SUBCLASSES(IteratorType) + + // TODO: // migrate BytesType / StringType to something backed by the modules // (if the modules are parsed) and also do the same for array, domain, diff --git a/frontend/lib/framework/Context.cpp b/frontend/lib/framework/Context.cpp index e1b71917e47..8058c1fd34d 100644 --- a/frontend/lib/framework/Context.cpp +++ b/frontend/lib/framework/Context.cpp @@ -977,6 +977,18 @@ void Context::error(const resolution::TypedFnSignature* inFn, // TODO: add note about instantiation & POI stack } +void Context::warning(Location loc, const char* fmt, ...) { + CHPL_CONTEXT_LOG_ERROR_HELPER(this, ErrorBase::WARNING, loc, fmt); +} + +void Context::warning(ID id, const char* fmt, ...) { + CHPL_CONTEXT_LOG_ERROR_HELPER(this, ErrorBase::WARNING, id, fmt); +} + +void Context::warning(const uast::AstNode* ast, const char* fmt, ...) { + CHPL_CONTEXT_LOG_ERROR_HELPER(this, ErrorBase::WARNING, ast, fmt); +} + #undef CHPL_CONTEXT_LOG_ERROR_HELPER void Context::recomputeIfNeeded(const QueryMapResultBase* resultEntry) { diff --git a/frontend/lib/resolution/Resolver.cpp b/frontend/lib/resolution/Resolver.cpp index cc053369f7c..af907dda773 100644 --- a/frontend/lib/resolution/Resolver.cpp +++ b/frontend/lib/resolution/Resolver.cpp @@ -70,9 +70,7 @@ namespace { // When an iter is resolved these pieces of the process will be stored. struct Pieces { - bool wasCallInjected = false; - CallResolutionResult crr; - const TypedFnSignature* sig = crr.mostSpecific().only().fn(); + const IteratorType* iterType = nullptr; }; Pieces standalone; Pieces leader; @@ -101,6 +99,7 @@ getIterKindConstantOrUnknownQuery(Context* context, UniqueString constant); static QualifiedType resolveIterTypeWithTag(Resolver& rv, IterDetails::Pieces& outIterPieces, + const IteratorType* iteratingOver, const AstNode* astForErr, const AstNode* iterand, UniqueString iterKindStr, @@ -4463,7 +4462,7 @@ getIterKindConstantOrUnknownQuery(Context* context, UniqueString constant) { // This helper resolves by priority order as described in 'IterDetails'. static IterDetails resolveIterDetailsInPriorityOrder(Resolver& rv, - bool& outWasIterSigResolved, + const IteratorType* iteratingOver, const AstNode* astForErr, const AstNode* iterand, const QualifiedType& leaderYieldType, @@ -4471,9 +4470,8 @@ resolveIterDetailsInPriorityOrder(Resolver& rv, IterDetails ret; bool computedLeaderYieldType = false; if (mask & IterDetails::STANDALONE) { - ret.idxType = resolveIterTypeWithTag(rv, ret.standalone, astForErr, + ret.idxType = resolveIterTypeWithTag(rv, ret.standalone, iteratingOver, astForErr, iterand, USTR("standalone"), {}); - outWasIterSigResolved = (ret.standalone.sig != nullptr); if (!ret.idxType.isUnknownOrErroneous()) { ret.succeededAt = IterDetails::STANDALONE; return ret; @@ -4481,7 +4479,7 @@ resolveIterDetailsInPriorityOrder(Resolver& rv, } if (mask & IterDetails::LEADER_FOLLOWER) { - ret.leaderYieldType = resolveIterTypeWithTag(rv, ret.leader, astForErr, + ret.leaderYieldType = resolveIterTypeWithTag(rv, ret.leader, iteratingOver, astForErr, iterand, USTR("leader"), {}); computedLeaderYieldType = true; @@ -4492,10 +4490,9 @@ resolveIterDetailsInPriorityOrder(Resolver& rv, if (mask & IterDetails::LEADER_FOLLOWER || mask & IterDetails::FOLLOWER) { if (!ret.leaderYieldType.isUnknownOrErroneous()) { - ret.idxType = resolveIterTypeWithTag(rv, ret.follower, astForErr, + ret.idxType = resolveIterTypeWithTag(rv, ret.follower, iteratingOver, astForErr, iterand, USTR("follower"), ret.leaderYieldType); - outWasIterSigResolved = (ret.follower.sig != nullptr); if (!ret.idxType.isUnknownOrErroneous()) { ret.succeededAt = computedLeaderYieldType ? IterDetails::LEADER_FOLLOWER @@ -4506,9 +4503,8 @@ resolveIterDetailsInPriorityOrder(Resolver& rv, } if (mask & IterDetails::SERIAL) { - ret.idxType = resolveIterTypeWithTag(rv, ret.serial, astForErr, + ret.idxType = resolveIterTypeWithTag(rv, ret.serial, iteratingOver, astForErr, iterand, {}, {}); - outWasIterSigResolved = (ret.serial.sig != nullptr); if (!ret.idxType.isUnknownOrErroneous()) { ret.succeededAt = IterDetails::SERIAL; } @@ -4522,25 +4518,23 @@ static IterDetails resolveIterDetails(Resolver& rv, const AstNode* iterand, const QualifiedType& leaderYieldType, int mask) { - Context* context = rv.context; - - if (mask == IterDetails::NONE || rv.scopeResolveOnly) { - // Resolve the iterand as much as possible even if there is nothing to do. - iterand->traverse(rv); + if (rv.scopeResolveOnly) { return {}; } - // Resolve the iterand but suppress errors for now. We'll reissue them - // next, possibly suppressing a "NoMatchingCandidates" for the iterand if - // our injected call is successful. - auto runResult = context->runAndTrackErrors([&](Context* context) { - iterand->traverse(rv); - return nullptr; - }); + auto iterandRe = rv.byPostorder.byAst(iterand); + const IteratorType* iteratingOver = nullptr; + if (!iterandRe.type().isUnknownOrErroneous()) { + iteratingOver = iterandRe.type().type()->toIteratorType(); + } else { + // The thing-to-be-iterated is not an iterator, but it might be iterable + // using its 'these()' method. Don't resolve it now, since we haven't + // decided on which overloads we need; resolveIterTypeWithTag will do that + // on finding that toIterate is null. + } // Resolve iterators, stopping immediately when we get a valid yield type. - bool wasIterSigResolved = false; - auto ret = resolveIterDetailsInPriorityOrder(rv, wasIterSigResolved, + auto ret = resolveIterDetailsInPriorityOrder(rv, iteratingOver, astForErr, iterand, leaderYieldType, mask); @@ -4548,33 +4542,57 @@ static IterDetails resolveIterDetails(Resolver& rv, // Only issue a "not iterable" error if the iterand has a type. If it was // not typed then earlier resolution of the iterand will have spit out an // approriate error for us already. - bool skipNoCandidatesError = true; - if (!wasIterSigResolved) { + if (ret.succeededAt == IterDetails::NONE && !iterandRe.type().isUnknownOrErroneous()) { auto& iterandRE = rv.byPostorder.byAst(iterand); if (!iterandRE.type().isUnknownOrErroneous()) { - ret.idxType = CHPL_TYPE_ERROR(context, NonIterable, astForErr, iterand, + ret.idxType = CHPL_TYPE_ERROR(rv.context, NonIterable, astForErr, iterand, iterandRE.type()); - } else { - skipNoCandidatesError = false; } } - // Reissue the errors. - for (auto& e : runResult.errors()) { - if (e->type() == NoMatchingCandidates) { - auto nmc = static_cast(e.get()); - auto& f = std::get<0>(nmc->info()); - if (skipNoCandidatesError && f == iterand) continue; - } - context->report(std::move(e)); + return ret; +} + +static QualifiedType resolveTheseMethod(Resolver& rv, + const AstNode* iterand, + const QualifiedType& iterandType, + const QualifiedType& tagType, + const QualifiedType& followThisType) { + auto& iterandRe = rv.byPostorder.byAst(iterand); + std::vector actuals; + + actuals.push_back(CallInfoActual(iterandType, USTR("this"))); + + if (!tagType.isUnknown()) { + actuals.emplace_back(tagType, USTR("tag")); } - return ret; + if (!followThisType.isUnknown()) { + actuals.emplace_back(followThisType, USTR("followThis")); + } + + auto ci = CallInfo(USTR("these"), + iterandType, + /* isMethodCall */ true, + /* hasQuestionArg */ false, + /* isParenless */ false, + /* actuals */ std::move(actuals)); + + auto inScope = rv.scopeStack.back(); + auto inScopes = CallScopeInfo::forNormalCall(inScope, rv.poiScope); + auto c = resolveGeneratedCall(rv.context, iterand, ci, inScopes); + + rv.handleResolvedCallWithoutError(iterandRe, iterand, ci, c, + { { AssociatedAction::ITERATE, + iterand->id() } }); + + return c.exprType(); } static QualifiedType resolveIterTypeWithTag(Resolver& rv, IterDetails::Pieces& outIterPieces, + const IteratorType* iteratingOver, const AstNode* astForErr, const AstNode* iterand, UniqueString iterKindStr, @@ -4584,114 +4602,64 @@ resolveIterTypeWithTag(Resolver& rv, QualifiedType error(QualifiedType::UNKNOWN, ErroneousType::get(context)); auto iterKindFormal = getIterKindConstantOrUnknownQuery(context, iterKindStr); - bool needStandalone = iterKindStr == USTR("standalone"); - bool needLeader = iterKindStr == USTR("leader"); - bool needFollower = iterKindStr == USTR("follower"); bool needSerial = iterKindStr.isEmpty(); // Exit early if we need a parallel iterator and don't have the enum. - if (!needSerial && iterKindFormal.isUnknown()) return error; - - auto iterKindType = EnumType::getIterKindType(context); - CHPL_ASSERT(needSerial || (iterKindFormal.type() == iterKindType && - iterKindFormal.hasParamPtr())); + if (!needSerial && iterKindFormal.isUnknown()) { + context->warning(astForErr, "resolving parallel iterators is not supported " + "without module code"); + return error; + } // Inspect the resolution result to determine what should be done next. auto& iterandRE = rv.byPostorder.byAst(iterand); + auto iterandType = iterandRE.type(); + + // If the user explicitly provided a tag, this is an error: tags + // are automatically provided by the compiler. Report an error. auto& MSC = iterandRE.mostSpecific(); auto fn = MSC.only() ? MSC.only().fn() : nullptr; - bool wasIterandTypeResolved = !iterandRE.type().isUnknownOrErroneous(); - bool wasIterResolved = fn && fn->isIterator(); - bool wasMatchingIterResolved = wasIterResolved && - ((fn->isParallelStandaloneIterator(context) && needStandalone) || - (fn->isParallelLeaderIterator(context) && needLeader) || - (fn->isParallelFollowerIterator(context) && needFollower) || - (fn->isSerialIterator(context) && needSerial)); - - QualifiedType ret = error; - - // We resolved the iterator we need right off the bat, so use it. - if (wasMatchingIterResolved) { - ret = iterandRE.type(); - outIterPieces = { false, {}, fn }; - - // We cannot inject e.g., the 'tag' actual in this case, so error out. - } else if (needSerial && wasIterResolved) { + if (fn && fn->isParallelIterator(context)) { + context->error(astForErr, + "explicitly invoking parallel iterators is not allowed -- " + "they are invoked implicitly by the compiler."); return error; + } + + bool wasIterandTypeResolved = !iterandType.isUnknownOrErroneous(); + bool wasMatchingIterResolved = + // Call to a serial iterator overload, and we are looking for a serial iterator. + (fn && fn->isSerialIterator(context) && needSerial) || + // Loop expressions (which we just resolved) and we are looking for a serial iterator. + (iterandType.type() && iterandType.type()->isLoopExprIteratorType() && needSerial); + + // The iterand was a call to a serial iterator, and we need a serial iterator. + if (wasMatchingIterResolved && wasIterandTypeResolved) { + CHPL_ASSERT(iterandType.type()->isIteratorType() && + iterandType.type() == iteratingOver && + "an iterator was resolved, expecting an iterator type"); + outIterPieces = { iteratingOver }; + return iteratingOver->yieldType(); // There's nothing to do in this case, so error out. } else if (needSerial && !wasIterandTypeResolved) { return error; + } - // In this branch we prepare a generated iterator call. It could be a call - // to 'these()' on the iterand type, or it could be a redirect of the - // existing call with 'iterKind' and (optionally) 'followThis' arguments - // tacked onto the end. - } else { - bool shouldCreateTheseCall = !wasIterResolved && wasIterandTypeResolved; - - // We need to fill in the following pieces to construct a 'CallInfo'. - UniqueString callName; - types::QualifiedType callCalledType; - bool callIsMethodCall = false; - bool callHasQuestionArg = false; - bool callIsParenless = false; - std::vector callActuals; - - // If we are constructing a new 'these()' call then add a receiver. - if (shouldCreateTheseCall) { - callName = USTR("these"); - callCalledType = iterandRE.type(); - callIsMethodCall = true; - callActuals.push_back(CallInfoActual(iterandRE.type(), USTR("this"))); - - // The iterand is an unresolved call, or it is a resolved iterator but - // not the one that we need. Regather existing actuals and reuse the - // receiver if it is present. - } else if (auto call = iterand->toCall()) { - - bool raiseErrors = false; - auto tmp = CallInfo::create(context, call, rv.byPostorder, raiseErrors); - - callName = tmp.name(); - callCalledType = tmp.calledType(); - callIsMethodCall = tmp.isMethodCall(); - callIsParenless = tmp.isParenless(); - for (auto& a : tmp.actuals()) callActuals.push_back(a); - } else { - CHPL_UNIMPL("unknown iterand"); - return error; - } - - if (!needSerial) { - callActuals.push_back(CallInfoActual(iterKindFormal, USTR("tag"))); - } - - if (needFollower) { - auto x = CallInfoActual(followThisFormal, USTR("followThis")); - callActuals.push_back(std::move(x)); - } - - auto ci = CallInfo(std::move(callName), - std::move(callCalledType), - std::move(callIsMethodCall), - std::move(callHasQuestionArg), - std::move(callIsParenless), - std::move(callActuals)); - auto inScope = rv.scopeStack.back(); - auto inScopes = CallScopeInfo::forNormalCall(inScope, rv.poiScope); - auto c = resolveGeneratedCall(context, iterand, ci, inScopes); - - outIterPieces = { true, c, c.mostSpecific().only().fn() }; - ret = c.exprType(); + // The iterand is either not an iterator (but could have a 'these' method) + // or an iterator. The latter have compiler-generated 'these' methods + // which implement the dispatch logic like rewriting an iterator from `iter foo()` + // to `iter foo(tag)`. So just resolve the 'these' method. + auto qt = resolveTheseMethod(rv, iterand, iterandType, iterKindFormal, followThisFormal); + if (!qt.isUnknownOrErroneous() && qt.type()->isIteratorType()) { + // These produced a valid iterator. We already configured the call + // with the desired tag, so that's sufficient. - if (!ret.isUnknownOrErroneous()) { - rv.handleResolvedCall(iterandRE, astForErr, ci, c, - { { AssociatedAction::ITERATE, iterand->id() } }); - } + iteratingOver = qt.type()->toIteratorType(); + outIterPieces = { iteratingOver }; + return iteratingOver->yieldType(); } - - return ret; + return qt; } static bool resolveParamForLoop(Resolver& rv, const For* forLoop) { @@ -4756,35 +4724,6 @@ static bool resolveParamForLoop(Resolver& rv, const For* forLoop) { return false; } -static void -backpatchArrayTypeSpecifier(Resolver& rv, const IndexableLoop* loop) { - if (rv.scopeResolveOnly || !loop->isBracketLoop()) return; - Context* context = rv.context; - - // Check if this is an array - auto iterandType = rv.byPostorder.byAst(loop->iterand()).type(); - if (!iterandType.isUnknown() && iterandType.type()->isDomainType()) { - QualifiedType eltType; - - CHPL_ASSERT(loop->isExpressionLevel() && loop->numStmts() <= 1); - if (loop->numStmts() == 1) { - eltType = rv.byPostorder.byAst(loop->stmt(0)).type(); - } else if (loop->numStmts() == 0) { - eltType = QualifiedType(QualifiedType::TYPE, AnyType::get(context)); - } - - // TODO: resolve array types when the iterand is something other than - // a domain. - if (eltType.isType() || eltType.kind() == QualifiedType::TYPE_QUERY) { - eltType = QualifiedType(QualifiedType::TYPE, eltType.type()); - auto arrayType = ArrayType::getArrayType(context, iterandType, eltType); - - auto& re = rv.byPostorder.byAst(loop); - re.setType(QualifiedType(QualifiedType::TYPE, arrayType)); - } - } -} - static QualifiedType resolveZipExpression(Resolver& rv, const IndexableLoop* loop, const Zip* zip) { Context* context = rv.context; @@ -4860,6 +4799,119 @@ resolveZipExpression(Resolver& rv, const IndexableLoop* loop, const Zip* zip) { return ret; } +static bool isShapedLikeArray(const IndexableLoop* loop) { + // 'forall' expressions are not arrays, only [] ... expressions could be. + if (!loop->isBracketLoop() || !loop->isExpressionLevel()) return false; + + // If there's an 'in' or 'with' clause, it's not an array + if (loop->index() != nullptr || loop->withClause() != nullptr) return false; + + // If there's a 'zip', it's not an array + if (loop->iterand()->isZip()) return false; + + // If there's more than one statement, it's not an array + if (loop->numStmts() > 1) return false; + + return true; +} + +static bool handleArrayTypeExpr(Resolver& rv, + const IndexableLoop* loop) { + + auto bodyType = QualifiedType(); + if (loop->numStmts() == 1) { + bodyType = rv.byPostorder.byAst(loop->stmt(0)).type(); + } else { + bodyType = QualifiedType(QualifiedType::TYPE, AnyType::get(rv.context)); + } + + // The body wasn't a type, so this isn't an array type epxression + // Make an exception for unknown or erroneous bodies, since the user may + // have been trying to define a type but made a mistake (or we may be + // in a partially-instantiated situation and the type is not yet known). + if (!bodyType.isUnknownOrErroneous() && + !bodyType.isType() && + bodyType.kind() != QualifiedType::TYPE_QUERY) { + return false; + } + + // It is an array. Time to build the array type. + + auto domainType = QualifiedType(); + auto iterandType = rv.byPostorder.byAst(loop->iterand()).type(); + if (!iterandType.isUnknownOrErroneous()) { + if (iterandType.type()->isDomainType()) { + domainType = iterandType; + } else { + // TODO: convert range into domain + } + } + + if (domainType.isUnknown()) { + // TODO: emit an error here + return true; + } + + auto eltType = QualifiedType(QualifiedType::TYPE, bodyType.type()); + auto arrayType = ArrayType::getArrayType(rv.context, domainType, eltType); + + auto& re = rv.byPostorder.byAst(loop); + re.setType(QualifiedType(QualifiedType::TYPE, arrayType)); + + return true; +} + +static void noteLoopExprType(Resolver& rv, const IndexableLoop* loop) { + if (!loop->isExpressionLevel()) return; + + CHPL_ASSERT(loop->numStmts() == 1); + auto bodyType = rv.byPostorder.byAst(loop->stmt(0)).type(); + + auto loopType = QualifiedType(); + if (!bodyType.isUnknownOrErroneous()) { + // Loop expressions keep the types of their iterands (effectively) + // because they need to preserve them for resolving leaders and followers + // later. + + QualifiedType iterandType; + bool isZippered = false; + if (auto zip = loop->iterand()->toZip()) { + isZippered = true; + bool allChildrenResolved = true; + std::vector iterandTypes; + + for (auto child : zip->children()) { + auto childType = rv.byPostorder.byAst(child).type(); + if (childType.isUnknownOrErroneous()) { + allChildrenResolved = false; + break; + } + iterandTypes.push_back(childType); + } + + if (allChildrenResolved) { + iterandType = + QualifiedType(QualifiedType::TYPE, + TupleType::getQualifiedTuple(rv.context, + std::move(iterandTypes))); + } + } else { + iterandType = rv.byPostorder.byAst(loop->iterand()).type(); + } + + if (!iterandType.isUnknownOrErroneous()) { + bool supportsParallel = loop->isForall() || loop->isBracketLoop(); + auto loopExprType = + LoopExprIteratorType::get(rv.context, bodyType, isZippered, + supportsParallel, iterandType, loop->id()); + loopType = QualifiedType(QualifiedType::CONST_VAR, loopExprType); + } + } + + auto& re = rv.byPostorder.byAst(loop); + re.setType(loopType); +} + bool Resolver::enter(const IndexableLoop* loop) { auto forLoop = loop->toFor(); bool isParamForLoop = forLoop != nullptr && forLoop->isParam(); @@ -4874,11 +4926,35 @@ bool Resolver::enter(const IndexableLoop* loop) { if (isParamForLoop) return resolveParamForLoop(*this, loop->toFor()); auto iterand = loop->iterand(); - QualifiedType idxType; + iterand->traverse(*this); + + bool shapedLikeArray = false; + if ((shapedLikeArray = isShapedLikeArray(loop))) { + // Array expressions and bracket loops can look very similar. + // For array type expressions, we do not need to go through the + // iterator/'these' logic handled below. Resolve the body so we can check + // if it's an array. + enterScope(loop); + loop->body()->traverse(*this); + + // If it's an array, no need to do any more work. + if (handleArrayTypeExpr(*this, loop)) { + return false; + + // Otherwise, we need to continue doing loop resolution, including the + // iterator/'these' logic. When performing that logic, we don't want + // the loop body to be our scope (indices are outside the loop), so + // exit the scope here. + } else { + exitScope(loop); + } + } + // Not an array expression. In this case, depending on the loop type, + // we need to resolve various iterators. + QualifiedType idxType; if (iterand->isZip()) { idxType = resolveZipExpression(*this, loop, iterand->toZip()); - } else { bool loopRequiresParallel = loop->isForall(); bool loopPrefersParallel = loopRequiresParallel || loop->isBracketLoop(); @@ -4904,11 +4980,12 @@ bool Resolver::enter(const IndexableLoop* loop) { with->traverse(*this); } - loop->body()->traverse(*this); + // If the loop is shaped like an array, we've already resolved the body + if (!shapedLikeArray) { + loop->body()->traverse(*this); + } - // TODO: Resolve the loop body first when it looks like an array type, - // and if the body is a type, then skip resolving iterators to save time. - backpatchArrayTypeSpecifier(*this, loop); + noteLoopExprType(*this, loop); return false; } @@ -5126,6 +5203,7 @@ static QualifiedType resolveReduceScanOp(Resolver& resolver, const AstNode* reduceOrScan, const AstNode* op, const AstNode* iterand) { + iterand->traverse(resolver); auto dt = resolveIterDetails(resolver, reduceOrScan, iterand, {}, IterDetails::SERIAL); auto idxType = dt.idxType; @@ -5305,6 +5383,11 @@ bool Resolver::enter(const Import* node) { void Resolver::exit(const Import* node) {} +bool Resolver::enter(const uast::Zip* zip) { + return true; +} +void Resolver::exit(const uast::Zip* zip) {} + bool Resolver::enter(const AstNode* ast) { enterScope(ast); diff --git a/frontend/lib/resolution/Resolver.h b/frontend/lib/resolution/Resolver.h index 1e23f02e7f7..67a2295e1ac 100644 --- a/frontend/lib/resolution/Resolver.h +++ b/frontend/lib/resolution/Resolver.h @@ -680,6 +680,9 @@ struct Resolver { bool enter(const uast::Import* node); void exit(const uast::Import* node); + bool enter(const uast::Zip* node); + void exit(const uast::Zip* node); + // if none of the above is called, fall back on this one bool enter(const uast::AstNode* ast); void exit(const uast::AstNode* ast); diff --git a/frontend/lib/resolution/resolution-queries.cpp b/frontend/lib/resolution/resolution-queries.cpp index 71088b9fc5a..8e8b237b802 100644 --- a/frontend/lib/resolution/resolution-queries.cpp +++ b/frontend/lib/resolution/resolution-queries.cpp @@ -3660,6 +3660,153 @@ bool resolvePostfixNilableAppliedToNew(Context* context, const Call* call, return true; } +static optional +resolveIteratorTheseCall(Context* context, + const AstNode* astForErr, + const CallInfo& ci, + const CallScopeInfo& inScopes) { + if (ci.name() != USTR("these") || !ci.isMethodCall()) return empty; + auto receiver = ci.actual(0).type(); + auto it = receiver.type() ? receiver.type()->toIteratorType() : nullptr; + + if (!it) return empty; + + // When it's an iterator created from a function, we need to set up + // a call with the same name and actuals as the function originally had, + // but in the current scope and with potential 'tag' and 'followThis' calls + if (auto fnIt = it->toFnIteratorType()) { + std::vector actuals; + + auto iterKindType = EnumType::getIterKindType(context); + + // We have a call to an iterator signature, but it may not be the right + // overload. So, construct a call with the same name and actuals, with + // possibly a different tag or followThis. + auto typedSig = fnIt->iteratorFn(); + auto untypedSig = typedSig->untyped(); + for (int i = 0; i < typedSig->numFormals(); i++) { + auto formalQt = typedSig->formalType(i); + + // We explicitly insert the tag below. + if (formalQt.type() == iterKindType) continue; + + actuals.emplace_back(formalQt, untypedSig->formalName(i)); + } + + // Forward the tag and followThis arguments. + // + // Performance: if we were sure that FnIterators can only be constructed + // without tags, and if we don't find a tag/followThis argument here, + // that would mean we are constructing a call info for the same function + // that just produced this FnIteratorType, so we could avoid the call + // resolution below. + for (const auto& actual : ci.actuals()) { + if (actual.byName() == USTR("tag") || + actual.byName() == USTR("followThis")) { + actuals.push_back(actual); + } + } + + auto receiverType = + untypedSig->isMethod() ? + typedSig->formalType(0) : + QualifiedType(); + + auto genCi = CallInfo(untypedSig->name(), + receiverType, + /* isMethodCall */ typedSig->isMethod(), + /* hasQuestionArg */ false, + /* isParenless */ false, + std::move(actuals)); + + auto c = resolveGeneratedCall(context, astForErr, genCi, inScopes); + return c; + } else if (auto loopIt = it->toLoopExprIteratorType()) { + // When resolving the leader iterator of a zippered loop expression, + // we only resolve the leader of its first iterand. On the other hand, + // we resolve all follower iterators of the loop expression. + + std::vector receiverTypes; + if (loopIt->isZippered()) { + auto receiverQt = loopIt->iterand(); + CHPL_ASSERT(receiverQt.type()->toTupleType()); + auto tupleType = receiverQt.type()->toTupleType(); + + for (int i = 0; i < tupleType->numElements(); i++) { + receiverTypes.push_back(tupleType->elementType(i)); + } + } else { + receiverTypes.push_back(loopIt->iterand()); + } + + // To robustly match the production implementation, we actually need + // to re-resolve the loop expr body given the (potentially new) + // results of resolving the follower iterators. However, this raises + // some challenges (e.g., suddenly loops are closures since they + // refer to their surrounding variables). Moreover, consensus at the time + // of writing is that allowing follower iterator types to change depending + // on usage context is undesirable, and allowing the yielded type to change + // is even more undesireable. So, resolve the followers if that's what + // we're doing, but return the existig yield instead of re-resolving the body. + + bool leaderOnly = false; + bool standalone = false; + bool serial = true; + for (auto actual : ci.actuals()) { + if (actual.byName() == USTR("tag")) { + serial = false; + if (auto paramValue = actual.type().param()) { + if (auto enumValue = paramValue->toEnumParam()) { + leaderOnly = enumValue->value().str == "leader"; + standalone = enumValue->value().str == "standalone"; + } + } + break; + } + } + + // Loop expressions don't have standalone iterators. + if (standalone) return empty; + + // the loop was written as a serial loop expression, so no parallel + // 'these' calls are allowed. + if (!serial && !loopIt->supportsParallel()) return empty; + + bool succeeded = true; + for (auto receiverType : receiverTypes) { + std::vector actuals; + actuals.emplace_back(receiverType, USTR("this")); + for (size_t i = 1; i < ci.numActuals(); i++) { + actuals.push_back(ci.actual(i)); + } + + auto genCi = CallInfo(USTR("these"), + receiverType, + /* isMethodCall */ true, + /* hasQuestionArg */ false, + /* isParenless */ false, + std::move(actuals)); + + auto c = resolveGeneratedCall(context, astForErr, genCi, inScopes); + + if (c.exprType().isUnknownOrErroneous() || + !c.exprType().type()->isIteratorType()) { + succeeded = false; + break; + } + + if (leaderOnly) return c; + } + + if (!succeeded) { + return empty; + } + + return CallResolutionResult(loopIt->yieldType()); + } + return empty; +} + // Resolving calls for certain compiler-supported patterns // without requiring module implementations exist at all. static bool resolveFnCallSpecial(Context* context, @@ -4904,9 +5051,15 @@ CallResolutionResult resolveGeneratedCall(Context* context, const CallInfo& ci, const CallScopeInfo& inScopes, std::vector* rejected) { - // see if the call is handled directly by the compiler QualifiedType tmpRetType; - if (resolveFnCallSpecial(context, astForErr, ci, tmpRetType)) { + + // Resolving 'these' is a bit trickier than other compiler-generated calls, + // so it's separately handled here instead of inside resolveFnCallSpecial. + if (auto cr = resolveIteratorTheseCall(context, astForErr, ci, inScopes)) { + return *cr; + + // see if the call is handled directly by the compiler + } else if (resolveFnCallSpecial(context, astForErr, ci, tmpRetType)) { return CallResolutionResult(std::move(tmpRetType)); } // otherwise do regular call resolution diff --git a/frontend/lib/resolution/return-type-inference.cpp b/frontend/lib/resolution/return-type-inference.cpp index ae75105612a..b9f38d680b1 100644 --- a/frontend/lib/resolution/return-type-inference.cpp +++ b/frontend/lib/resolution/return-type-inference.cpp @@ -1160,6 +1160,11 @@ static const QualifiedType& returnTypeQuery(ResolutionContext* rc, } } + if (sig->isIterator()) { + result = QualifiedType(result.kind(), + FnIteratorType::get(context, result, sig)); + } + return CHPL_RESOLUTION_QUERY_END(result); } diff --git a/frontend/lib/types/CMakeLists.txt b/frontend/lib/types/CMakeLists.txt index 90a66fda2c8..5c6924dc1d6 100644 --- a/frontend/lib/types/CMakeLists.txt +++ b/frontend/lib/types/CMakeLists.txt @@ -34,12 +34,15 @@ target_sources(ChplFrontend-obj EnumType.cpp ErroneousType.cpp ExternType.cpp + FnIteratorType.cpp ImagType.cpp IntType.cpp + LoopExprIteratorType.cpp NilType.cpp NothingType.cpp Param.cpp PrimitiveType.cpp + PromotionIteratorType.cpp QualifiedType.cpp RealType.cpp RecordType.cpp diff --git a/frontend/lib/types/FnIteratorType.cpp b/frontend/lib/types/FnIteratorType.cpp new file mode 100644 index 00000000000..26911cb6e81 --- /dev/null +++ b/frontend/lib/types/FnIteratorType.cpp @@ -0,0 +1,49 @@ +/* + * Copyright 2024 Hewlett Packard Enterprise Development LP + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is licensed under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "chpl/types/FnIteratorType.h" +#include "chpl/framework/query-impl.h" +#include "chpl/resolution/resolution-types.h" + +namespace chpl { +namespace types { + +void FnIteratorType::markUniqueStringsInner(Context* context) const { + yieldType_.mark(context); + iteratorFn_->mark(context); +} + +const owned& +FnIteratorType::getFnIteratorType(Context* context, + QualifiedType yieldType, + const resolution::TypedFnSignature* iteratorFn) { + QUERY_BEGIN(getFnIteratorType, context, yieldType, iteratorFn); + auto result = toOwned(new FnIteratorType(std::move(yieldType), iteratorFn)); + return QUERY_END(result); +} + +const FnIteratorType* +FnIteratorType::get(Context* context, + QualifiedType yieldType, + const resolution::TypedFnSignature* iteratorFn) { + return getFnIteratorType(context, yieldType, iteratorFn).get(); +} + +} // end namespace types +} // end namespace chpl diff --git a/frontend/lib/types/LoopExprIteratorType.cpp b/frontend/lib/types/LoopExprIteratorType.cpp new file mode 100644 index 00000000000..d14678ad34c --- /dev/null +++ b/frontend/lib/types/LoopExprIteratorType.cpp @@ -0,0 +1,49 @@ +/* + * Copyright 2024 Hewlett Packard Enterprise Development LP + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is licensed under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "chpl/types/LoopExprIteratorType.h" +#include "chpl/framework/query-impl.h" + +namespace chpl { +namespace types { + +const owned& +LoopExprIteratorType::getLoopExprIteratorType(Context* context, + QualifiedType yieldType, + bool isZippered, + bool supportsParallel, + QualifiedType iterand, + ID sourceLocation) { + QUERY_BEGIN(getLoopExprIteratorType, context, yieldType, isZippered, supportsParallel, iterand, sourceLocation); + auto result = toOwned(new LoopExprIteratorType(std::move(yieldType), isZippered, supportsParallel, std::move(iterand), std::move(sourceLocation))); + return QUERY_END(result); +} + +const LoopExprIteratorType* +LoopExprIteratorType::get(Context* context, + QualifiedType yieldType, + bool isZippered, + bool supportsParallel, + QualifiedType iterand, + ID sourceLocation) { + return getLoopExprIteratorType(context, std::move(yieldType), isZippered, supportsParallel, std::move(iterand), std::move(sourceLocation)).get(); +} + +} // end namespace types +} // end namespace chpl diff --git a/frontend/lib/types/PromotionIteratorType.cpp b/frontend/lib/types/PromotionIteratorType.cpp new file mode 100644 index 00000000000..49e3e3af4c2 --- /dev/null +++ b/frontend/lib/types/PromotionIteratorType.cpp @@ -0,0 +1,54 @@ +/* + * Copyright 2024 Hewlett Packard Enterprise Development LP + * Other additional copyright holders may be indicated within. + * + * The entirety of this work is licensed under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "chpl/types/PromotionIteratorType.h" +#include "chpl/framework/query-impl.h" +#include "chpl/resolution/resolution-types.h" + +namespace chpl { +namespace types { + +void PromotionIteratorType::markUniqueStringsInner(Context* context) const { + yieldType_.mark(context); + scalarFn_->mark(context); + for (const auto& pair : promotedFormals_) { + pair.second.mark(context); + } +} + +const owned& +PromotionIteratorType::getPromotionIteratorType(Context* context, + QualifiedType yieldType, + const resolution::TypedFnSignature* scalarFn, + resolution::SubstitutionsMap promotedFormals) { + QUERY_BEGIN(getPromotionIteratorType, context, yieldType, scalarFn, promotedFormals); + auto result = toOwned(new PromotionIteratorType(scalarFn, std::move(promotedFormals), std::move(yieldType))); + return QUERY_END(result); +} + +const PromotionIteratorType* +PromotionIteratorType::get(Context* context, + QualifiedType yieldType, + const resolution::TypedFnSignature* scalarFn, + resolution::SubstitutionsMap promotedFormals) { + return getPromotionIteratorType(context, std::move(yieldType), scalarFn, std::move(promotedFormals)).get(); +} + +} // end namespace types +} // end namespace chpl diff --git a/frontend/test/ErrorGuard.h b/frontend/test/ErrorGuard.h index bb5a1eea393..66623056a8e 100644 --- a/frontend/test/ErrorGuard.h +++ b/frontend/test/ErrorGuard.h @@ -78,7 +78,14 @@ class ErrorGuard { } /** Get the number of errors contained in the guard. */ - inline size_t numErrors() const { return this->errors().size(); } + inline size_t numErrors(bool countWarnings = true) const { + size_t ret = handler_->errors().size(); + if (!countWarnings) { + for (auto& err : handler_->errors()) + if (err->kind() == chpl::ErrorBase::WARNING) --ret; + } + return ret; + } const chpl::owned& error(size_t idx) const { assert(idx < numErrors()); diff --git a/frontend/test/resolution/testInitSemantics.cpp b/frontend/test/resolution/testInitSemantics.cpp index 28d5a22321c..3978d6d23b8 100644 --- a/frontend/test/resolution/testInitSemantics.cpp +++ b/frontend/test/resolution/testInitSemantics.cpp @@ -74,7 +74,7 @@ static void testFieldUseBeforeInit1(void) { // Resolve the module. std::ignore = resolveModule(ctx, mod->id()); - assert(guard.errors().size() == 6); + assert(guard.numErrors(/* countWarnings */ false) == 6); // Check the first error to see if it lines up. auto& msg = guard.errors()[0]; @@ -112,10 +112,10 @@ static void testInitReturnVoid(void) { // Resolve the module. std::ignore = resolveModule(ctx, mod->id()); - assert(guard.errors().size() == 1); + assert(guard.numErrors(/* countWarnings */ false) == 1); - // Check the first error to see if it lines up. - auto& msg = guard.errors()[0]; + // Check the error (which comes last) to see if it lines up. + auto& msg = guard.errors().back(); assert(msg->message() == "initializers can only return 'void'"); assert(msg->location(ctx).firstLine() == 12); assert(guard.realizeErrors()); @@ -283,10 +283,10 @@ static void testInitInsideLoops(void) { // Resolve the module. std::ignore = resolveModule(ctx, mod->id()); - assert(guard.errors().size() == 1); + assert(guard.numErrors(/* countWarnings */ false) == 1); - // Check the first error to see if it lines up. - auto& msg = guard.errors()[0]; + // Check the error (which comes last) to see if it lines up. + auto& msg = guard.errors().back(); assert(msg->message() == message); assert(msg->location(ctx).firstLine() == 11); assert(guard.realizeErrors()); diff --git a/frontend/test/resolution/testLoopIndexVars.cpp b/frontend/test/resolution/testLoopIndexVars.cpp index fd80f401fe5..d21ccab6cc5 100644 --- a/frontend/test/resolution/testLoopIndexVars.cpp +++ b/frontend/test/resolution/testLoopIndexVars.cpp @@ -794,18 +794,22 @@ static void testForallStandaloneThese(Context* context) { assert(!guard.realizeErrors()); } -static void testForallStandaloneRedirect(Context* context) { - printf("%s\n", __FUNCTION__); - ErrorGuard guard(context); - - ADVANCE_PRESERVING_STANDARD_MODULES_(context); - auto program = R""""( - iter foo(param tag: iterKind) where tag == iterKind.standalone do yield 0; - forall i in foo() do i; - )""""; - assertLoopMatches(context, program, "standalone", 1, 0); - assert(!guard.realizeErrors()); -} +// Daniel 07/19/24: Disabling this deliberately. Production requires a +// serial iteartor even when a standalone iterator is +// available and would be preferred. +// +// static void testForallStandaloneRedirect(Context* context) { +// printf("%s\n", __FUNCTION__); +// ErrorGuard guard(context); +// +// ADVANCE_PRESERVING_STANDARD_MODULES_(context); +// auto program = R""""( +// iter foo(param tag: iterKind) where tag == iterKind.standalone do yield 0; +// forall i in foo() do i; +// )""""; +// assertLoopMatches(context, program, "standalone", 1, 0); +// assert(!guard.realizeErrors()); +// } static void testForallLeaderFollowerThese(Context* context) { printf("%s\n", __FUNCTION__); @@ -824,18 +828,211 @@ static void testForallLeaderFollowerThese(Context* context) { assert(!guard.realizeErrors()); } -static void testForallLeaderFollowerRedirect(Context* context) { +// Daniel 07/19/24: Disabling this deliberately. Production requires a +// serial iteartor even when a leader/follower pair is +// available and would be preferred. +// +// static void testForallLeaderFollowerRedirect(Context* context) { +// printf("%s\n", __FUNCTION__); +// ErrorGuard guard(context); +// +// ADVANCE_PRESERVING_STANDARD_MODULES_(context); +// auto program = R""""( +// iter foo(param tag: iterKind) where tag == iterKind.leader do yield (0, 0); +// iter foo(param tag: iterKind, followThis) where tag == iterKind.follower do yield 0; +// forall i in foo() do i; +// )""""; +// assertLoopMatches(context, program, "leader", 2, 0, 1); +// assert(!guard.realizeErrors()); +// } + +// Invoke an iterator in a loop expression, yielding a tuple of the iterator's +// yield values, then invoke the loop expression in a regular loop and ensure +// the index variable is as expected. +// +// +// Note: this test will need to be updated when we have support for +// "precipitating" loop expressions into arrays. Right now, it assumes +// the ability to store loop expressions into variables. +template +static void pairIteratorInLoopExpression( + Context* context, const char* iterators, const char* iterCall, + std::array loopExprType, std::array loopType, + Predicate&& pred, int expectErrors = 0) { + ErrorGuard guard(context); + ADVANCE_PRESERVING_STANDARD_MODULES_(context); + + std::string program = std::string(iterators) + "\n"; + program = program + "var r1 = " + loopExprType[0] + " i in " + iterCall + " " + loopExprType[1] + " (i, i);\n"; + program = program + loopType[0] + " j in r1 " + loopType[1] + " j;\n"; + + printf("===== Resolving program =====\n"); + printf("%s\n", program.c_str()); + printf("=============================\n\n"); + + auto vars = resolveTypesOfVariables(context, program, { "r1", "j" }); + + assert(guard.realizeErrors() == expectErrors); + if (expectErrors) return; + + assert(vars.at("r1").kind() == QualifiedType::VAR); + assert(vars.at("r1").type()->isLoopExprIteratorType()); + assert(vars.at("r1").type()->toLoopExprIteratorType()->yieldType().type()); + + auto yieldedType = vars.at("r1").type()->toLoopExprIteratorType()->yieldType(); + assert(yieldedType.type()->isTupleType()); + assert(yieldedType.type()->toTupleType()->isStarTuple()); + assert(yieldedType.type()->toTupleType()->numElements() == 2); + assert(pred(yieldedType.type()->toTupleType()->starType())); + + assert(vars.at("j") == yieldedType); +} + +static void testForLoopExpression(Context* context) { + printf("%s\n", __FUNCTION__); ErrorGuard guard(context); - ADVANCE_PRESERVING_STANDARD_MODULES_(context); - auto program = R""""( - iter foo(param tag: iterKind) where tag == iterKind.leader do yield (0, 0); - iter foo(param tag: iterKind, followThis) where tag == iterKind.follower do yield 0; - forall i in foo() do i; - )""""; - assertLoopMatches(context, program, "leader", 2, 0, 1); - assert(!guard.realizeErrors()); + auto iters = + R""""( + iter i1() { yield 0.0; } + iter i1(param tag: iterKind) where tag == iterKind.standalone { yield 0; } + iter i1(param tag: iterKind) where tag == iterKind.leader { yield (0,0); } + iter i1(param tag: iterKind, followThis) where tag == iterKind.follower { yield followThis; } + )""""; + + // Serial iterator yields reals, so expect real. + pairIteratorInLoopExpression(context, iters, "i1()", {"for", "do"}, {"for", "do"}, + [](const QualifiedType& t) { return t.type()->isRealType(); }); +} + +static void testForallLoopExpressionStandalone(Context* context) { + auto iters = + R""""( + iter i1() { yield 0.0; } + iter i1(param tag: iterKind) where tag == iterKind.standalone { yield 0; } + iter i1(param tag: iterKind) where tag == iterKind.leader { yield (0,0); } + iter i1(param tag: iterKind, followThis) where tag == iterKind.follower { yield followThis; } + )""""; + + // Standalone iterator yields ints, so expect ints. + pairIteratorInLoopExpression(context, iters, "i1()", {"forall", "do"}, {"forall", "do"}, + [](const QualifiedType& t) { return t.type()->isIntType(); }); +} + +static void testForallLoopExpressionLeaderFollower(Context* context) { + // Note: compred to the previous test, no standalone iterator is available + auto iters = + R""""( + iter i1() { yield 0.0; } + iter i1(param tag: iterKind) where tag == iterKind.leader { yield (0,0); } + iter i1(param tag: iterKind, followThis) where tag == iterKind.follower { yield followThis; } + )""""; + + // Follower iterator yields tuples of ints, so expect tuples of ints. + pairIteratorInLoopExpression(context, iters, "i1()", {"forall", "do"}, {"forall", "do"}, + [](const QualifiedType& t) { + auto type = t.type(); + if (!type->isTupleType()) return false; + + auto tt = type->toTupleType(); + return tt->isStarTuple() && tt->numElements() == 2 && tt->starType().type()->isIntType(); + }); +} + +static void testBracketLoopExpressionStandalone(Context* context) { + auto iters = + R""""( + iter i1() { yield 0.0; } + iter i1(param tag: iterKind) where tag == iterKind.standalone { yield 0; } + iter i1(param tag: iterKind) where tag == iterKind.leader { yield (0,0); } + iter i1(param tag: iterKind, followThis) where tag == iterKind.follower { yield followThis; } + )""""; + + // Standalone iterator yields ints, so expect ints. + pairIteratorInLoopExpression(context, iters, "i1()", {"[", "]"}, {"[", "]"}, + [](const QualifiedType& t) { return t.type()->isIntType(); }); +} + +static void testBracketLoopExpressionLeaderFollower(Context* context) { + // Note: compred to the previous test, no standalone iterator is available + auto iters = + R""""( + iter i1() { yield 0.0; } + iter i1(param tag: iterKind) where tag == iterKind.leader { yield (0,0); } + iter i1(param tag: iterKind, followThis) where tag == iterKind.follower { yield followThis; } + )""""; + + // Follower iterator yields tuples of ints, so expect tuples of ints. + pairIteratorInLoopExpression(context, iters, "i1()", {"[", "]"}, {"[", "]"}, + [](const QualifiedType& t) { + auto type = t.type(); + if (!type->isTupleType()) return false; + + auto tt = type->toTupleType(); + return tt->isStarTuple() && tt->numElements() == 2 && tt->starType().type()->isIntType(); + }); +} + +static void testBracketLoopExpressionSerial(Context* context) { + // Note: compred to the previous test, no standalone iterator is available + auto iters = + R""""( + iter i1() { yield 0.0; } + )""""; + + // Follower iterator yields tuples of ints, so expect tuples of ints. + pairIteratorInLoopExpression(context, iters, "i1()", {"[", "]"}, {"[", "]"}, + [](const QualifiedType& t) { return t.type()->isRealType(); }); +} + +static void testForLoopExpressionInForall(Context* context) { + auto iters = + R""""( + iter i1() { yield 0.0; } + iter i1(param tag: iterKind) where tag == iterKind.standalone { yield 0; } + iter i1(param tag: iterKind) where tag == iterKind.leader { yield (0,0); } + iter i1(param tag: iterKind, followThis) where tag == iterKind.follower { yield followThis; } + )""""; + + // You can't iterate in paralell over a serial loop expression, even if + // the overloads for the iterands are present. + pairIteratorInLoopExpression(context, iters, "i1()", {"for", "do"}, {"forall", "do"}, + [](const QualifiedType& t) { return true; }, + /* expectErrors */ 1); +} + +static void testForLoopExpressionInBracketLoop(Context* context) { + auto iters = + R""""( + iter i1() { yield 0.0; } + iter i1(param tag: iterKind) where tag == iterKind.standalone { yield 0; } + iter i1(param tag: iterKind) where tag == iterKind.leader { yield (0,0); } + iter i1(param tag: iterKind, followThis) where tag == iterKind.follower { yield followThis; } + )""""; + + // Bracket loop falls back to serial, so it's okay to give it a for expression. + pairIteratorInLoopExpression(context, iters, "i1()", {"for", "do"}, {"[", "]"}, + [](const QualifiedType& t) { return t.type()->isRealType(); }); +} + +// At this time, because parallel loops require a serial overload, you can +// fall back to the serial iterator even if you're using a forall expression. +// However, since we are not liking the idea of changing the return type based +// on context, the loop yield type is still the one from the parallel loop. +// This probably won't compile if we were to add type compatibility checks. +static void testForallExpressionInForLoop(Context* context) { + auto iters = + R""""( + iter i1() { yield 0.0; } + iter i1(param tag: iterKind) where tag == iterKind.standalone { yield 0; } + iter i1(param tag: iterKind) where tag == iterKind.leader { yield (0,0); } + iter i1(param tag: iterKind, followThis) where tag == iterKind.follower { yield followThis; } + )""""; + + // Bracket loop falls back to serial, so it's okay to give it a for expression. + pairIteratorInLoopExpression(context, iters, "i1()", {"forall", "do"}, {"for", "do"}, + [](const QualifiedType& t) { return t.type()->toIntType(); }); } int main() { @@ -863,9 +1060,19 @@ int main() { testSerialZip(context); testParallelZip(context); testForallStandaloneThese(context); - testForallStandaloneRedirect(context); + // testForallStandaloneRedirect(context); testForallLeaderFollowerThese(context); - testForallLeaderFollowerRedirect(context); + // testForallLeaderFollowerRedirect(context); + + testForLoopExpression(context); + testForallLoopExpressionStandalone(context); + testForallLoopExpressionLeaderFollower(context); + testBracketLoopExpressionStandalone(context); + testBracketLoopExpressionLeaderFollower(context); + testBracketLoopExpressionSerial(context); + testForLoopExpressionInForall(context); + testForLoopExpressionInBracketLoop(context); + testForallExpressionInForLoop(context); return 0; } diff --git a/frontend/test/resolution/testTaskIntents.cpp b/frontend/test/resolution/testTaskIntents.cpp index e3c380b5a90..d68dee583cf 100644 --- a/frontend/test/resolution/testTaskIntents.cpp +++ b/frontend/test/resolution/testTaskIntents.cpp @@ -222,7 +222,7 @@ static Collector customHelper(std::string program, ResolutionContext* rc, Module } if (!fail) { - assert(!guard.realizeErrors()); + assert(!guard.realizeErrors(/* countWarnings */ false)); } return pc;