diff --git a/compiler/passes/convert-uast.cpp b/compiler/passes/convert-uast.cpp index 79045ca4f861..f007cd4fbd10 100644 --- a/compiler/passes/convert-uast.cpp +++ b/compiler/passes/convert-uast.cpp @@ -3236,7 +3236,9 @@ struct Converter { // Update the function symbol with any resolution results. if (shouldResolveFunction && resolvedFn != nullptr) { - auto retType = resolution::returnType(context, resolvedFn->signature(), poiScope); + // TODO: Need to thread stack frames through the RC. + chpl::resolution::ResolutionContext rcval(context); + auto retType = resolution::returnType(&rcval, resolvedFn->signature(), poiScope); fn->retType = convertType(retType); } diff --git a/doc/util/nitpick_ignore b/doc/util/nitpick_ignore index 37bb8c3f70c8..7c43787f70d1 100644 --- a/doc/util/nitpick_ignore +++ b/doc/util/nitpick_ignore @@ -28,6 +28,7 @@ cpp:identifier querydetail::QueryMapResultBase cpp:identifier querydetail::QueryMap cpp:identifier querydetail::QueryMap::MapType cpp:identifier UniqueString +cpp:identifier Resolver cpp:identifier ID cpp:identifier Location cpp:identifier AstTag @@ -49,6 +50,7 @@ cpp:identifier int8_t cpp:identifier va_list cpp:identifier ErrorNote cpp:identifier ErrorCodeSnippet +cpp:identifier RetByVal # TODO: Expand macros before generating docs to remove these. cpp:identifier ErrorParseErr cpp:identifier ParamTag @@ -65,6 +67,7 @@ cpp:identifier types::UintParam cpp:identifier WHERE_TBD cpp:identifier ZERO cpp:identifier SYMBOL_ONLY +cpp:identifier DK_NO_DEFAULT cpp:identifier uast::asttags::NUM_AST_TAGS cpp:identifier uast::Function::DEFAULT_RETURN_INTENT cpp:identifier VisibilitySymbols::REGULAR_SCOPE diff --git a/frontend/include/chpl/framework/Context-detail.h b/frontend/include/chpl/framework/Context-detail.h index 66007ea90b69..b88223dfc8ac 100644 --- a/frontend/include/chpl/framework/Context-detail.h +++ b/frontend/include/chpl/framework/Context-detail.h @@ -444,12 +444,17 @@ struct QueryTimingStopwatch { typename Clock::time_point start_; QueryTimingStopwatch(bool enabled, F onExit) - : onExit_(onExit) { + : onExit_(std::move(onExit)) { if (enabled) { start_ = Clock::now(); } } + QueryTimingStopwatch(QueryTimingStopwatch&& rhs) = default; + QueryTimingStopwatch(const QueryTimingStopwatch& rhs) = delete; + QueryTimingStopwatch& operator=(const QueryTimingStopwatch& rhs) = delete; + QueryTimingStopwatch& operator=(QueryTimingStopwatch&& rhs) = default; + QueryTimingDuration elapsed() { auto stop = Clock::now(); return stop - start_; @@ -461,7 +466,7 @@ struct QueryTimingStopwatch { // Helper function to sort out the templates over lambda's template QueryTimingStopwatch makeQueryTimingStopwatch(bool enabled, F onExit) { - return QueryTimingStopwatch(enabled, onExit); + return QueryTimingStopwatch(enabled, std::move(onExit)); } inline auto diff --git a/frontend/include/chpl/framework/Context.h b/frontend/include/chpl/framework/Context.h index e6248c34a6a6..7b68949aeb66 100644 --- a/frontend/include/chpl/framework/Context.h +++ b/frontend/include/chpl/framework/Context.h @@ -203,10 +203,12 @@ class Context { RecomputeMarker(Context* context, bool isRecomputing) : context_(context), oldValue_(isRecomputing) { - std::swap(context_->isRecomputing, oldValue_); + if (context) std::swap(context_->isRecomputing, oldValue_); } public: + RecomputeMarker() : RecomputeMarker(nullptr, false) {} + RecomputeMarker(RecomputeMarker&& other) { *this = std::move(other); } @@ -1021,28 +1023,48 @@ class Context { const std::string& args, querydetail::QueryTimingDuration elapsed); + template + struct ReportOnExit { + using Stopwatch = querydetail::QueryTimingStopwatch; + Context* context = nullptr; + querydetail::QueryMapBase* base = nullptr; + const std::tuple* tupleOfArgs = nullptr; + bool enableQueryTiming = false; + size_t depth = 0; + bool enableQueryTimingTrace = false; + + ReportOnExit(const ReportOnExit& rhs) = delete; + ReportOnExit(ReportOnExit&& rhs) = default; + ReportOnExit& operator=(const ReportOnExit& rhs) = delete; + ReportOnExit& operator=(ReportOnExit&& rhs) = default; + + bool enabled() { return enableQueryTiming || enableQueryTimingTrace; } + + void operator()(Stopwatch& stopwatch) { + // Return if the map is empty (to allow for default-construction). + if (base == nullptr) return; + bool enabled = enableQueryTiming || enableQueryTimingTrace; + if (enabled) { + auto elapsed = stopwatch.elapsed(); + std::ostringstream oss; + if (tupleOfArgs) querydetail::queryArgsPrint(oss, *tupleOfArgs); + context->finishQueryStopwatch(base, depth, oss.str(), elapsed); + } + }; + }; + // Used in the in QUERY_BEGIN_TIMING macro. Creates a stopwatch that starts // timing if we are enabled. And then on scope exit we conditionally stop the // timing and add it to the total or log it. // Semi-public method because we only expect it to be used in the macro template auto makeQueryTimingStopwatch(querydetail::QueryMapBase* base, - const std::tuple& tupleOfArgs) { - size_t depth = queryStack.size(); - bool enabled = enableQueryTiming || enableQueryTimingTrace; - - return querydetail::makeQueryTimingStopwatch( - enabled, - // This lambda gets called when the stopwatch object (which lives on the - // stack of the query function) is destructed - [this, base, depth, enabled, &tupleOfArgs](auto& stopwatch) { - if (enabled) { - auto elapsed = stopwatch.elapsed(); - std::ostringstream oss; - querydetail::queryArgsPrint(oss, tupleOfArgs); - finishQueryStopwatch(base, depth, oss.str(), elapsed); - } - }); + const std::tuple& tupleOfArgs) { + ReportOnExit s { + this, base, &tupleOfArgs, enableQueryTiming, queryStack.size(), + enableQueryTimingTrace + }; + return querydetail::makeQueryTimingStopwatch(s.enabled(), std::move(s)); } /// \endcond }; diff --git a/frontend/include/chpl/framework/mark-functions.h b/frontend/include/chpl/framework/mark-functions.h index 7792482c4d2f..86ad105c9e5d 100644 --- a/frontend/include/chpl/framework/mark-functions.h +++ b/frontend/include/chpl/framework/mark-functions.h @@ -158,6 +158,11 @@ template struct mark> { mark_tuple_impl(context, keep, std::index_sequence_for()); } }; +template struct mark> { + void operator()(Context* context, const std::tuple& keep) const { + mark_tuple_impl(context, keep, std::index_sequence_for()); + } +}; /// \endcond diff --git a/frontend/include/chpl/framework/query-impl.h b/frontend/include/chpl/framework/query-impl.h index a134c29342e4..6b5e87449be5 100644 --- a/frontend/include/chpl/framework/query-impl.h +++ b/frontend/include/chpl/framework/query-impl.h @@ -594,11 +594,10 @@ Context::querySetterUpdateResult( } // end namespace chpl -#define QUERY_BEGIN_INNER(isInput, func, context, ...) \ +#define QUERY_BEGIN_INNER(isInput, func, funcName, context, ...) \ auto* BEGIN_QUERY_FUNCTION = func; \ Context* BEGIN_QUERY_CONTEXT = context; \ - const char* BEGIN_QUERY_FUNC_NAME = #func; \ - CHPL_ASSERT(0 == strcmp(BEGIN_QUERY_FUNC_NAME, __func__)); \ + const char* BEGIN_QUERY_FUNC_NAME = (funcName); \ auto BEGIN_QUERY_ARGS = std::make_tuple(__VA_ARGS__); \ auto BEGIN_QUERY_MAP = context->queryBeginGetMap(BEGIN_QUERY_FUNCTION, \ BEGIN_QUERY_ARGS, \ @@ -628,7 +627,6 @@ Context::querySetterUpdateResult( #endif - /** Use QUERY_BEGIN at the start of the implementation of a particular query. It checks to see if an earlier result can be used and in that event returns @@ -638,7 +636,7 @@ Context::querySetterUpdateResult( class Context, and then pass any arguments to the query. */ #define QUERY_BEGIN(func, context, ...) \ - QUERY_BEGIN_INNER(false, func, context, __VA_ARGS__); \ + QUERY_BEGIN_INNER(false, func, #func, context, __VA_ARGS__); \ if (QUERY_USE_SAVED()) { \ return QUERY_GET_SAVED(); \ } \ @@ -655,7 +653,7 @@ Context::querySetterUpdateResult( for input queries. */ #define QUERY_BEGIN_INPUT(func, context, ...) \ - QUERY_BEGIN_INNER(true, func, context, __VA_ARGS__) \ + QUERY_BEGIN_INNER(true, func, #func, context, __VA_ARGS__) \ if (QUERY_USE_SAVED()) { \ return QUERY_GET_SAVED(); \ } \ @@ -684,7 +682,6 @@ Context::querySetterUpdateResult( std::move(result), \ BEGIN_QUERY_FUNC_NAME)) - /** Use QUERY_STORE_RESULT to implement a setter for a non-input query. Arguments are: @@ -704,7 +701,6 @@ Context::querySetterUpdateResult( #func, \ false) - /** Use QUERY_STORE_INPUT_RESULT to implement a setter for an input query. This is especially useful for input queries (to e.g. set the file contents). diff --git a/frontend/include/chpl/parsing/FileContents.h b/frontend/include/chpl/parsing/FileContents.h index 9734862a9012..9c396e80f5e2 100644 --- a/frontend/include/chpl/parsing/FileContents.h +++ b/frontend/include/chpl/parsing/FileContents.h @@ -20,16 +20,17 @@ #ifndef CHPL_PARSING_FILE_CONTENTS_H #define CHPL_PARSING_FILE_CONTENTS_H -#include "chpl/framework/ErrorMessage.h" #include "chpl/framework/update-functions.h" #include "chpl/framework/stringify-functions.h" -#include "chpl/framework/ErrorBase.h" #include namespace chpl { -namespace parsing { +// Forward declare the error class that can't be referenced until later. +class ErrorBase; + +namespace parsing { /** This class represents the result of reading a file. @@ -39,7 +40,7 @@ class FileContents { std::string text_; // TODO: it would be better to use the LLVM error handling strategy here, // instead of storing errors created via Context. - const ErrorBase* error_; + const ErrorBase* error_ = nullptr; public: /** Construct a FileContents containing empty text and no error */ @@ -72,9 +73,7 @@ class FileContents { static bool update(FileContents& keep, FileContents& addin) { return chpl::defaultUpdate(keep, addin); } - void mark(Context* context) const { - if (error_ != nullptr) error_->mark(context); - } + void mark(Context* context) const; }; diff --git a/frontend/include/chpl/parsing/parsing-queries.h b/frontend/include/chpl/parsing/parsing-queries.h index 05084273b84f..75be77d154b5 100644 --- a/frontend/include/chpl/parsing/parsing-queries.h +++ b/frontend/include/chpl/parsing/parsing-queries.h @@ -538,6 +538,11 @@ bool idIsField(Context* context, ID id); */ const ID& idToParentId(Context* context, ID id); +/** + Returns the parent function ID given an ID. + */ +ID idToParentFunctionId(Context* context, ID id); + /** Returns the parent AST node given an AST node */ diff --git a/frontend/include/chpl/resolution/ResolutionContext.h b/frontend/include/chpl/resolution/ResolutionContext.h new file mode 100644 index 000000000000..c6ed9b3b74d8 --- /dev/null +++ b/frontend/include/chpl/resolution/ResolutionContext.h @@ -0,0 +1,660 @@ +/* + * Copyright 2021-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_RESOLUTION_RESOLUTION_CONTEXT_H +#define CHPL_RESOLUTION_RESOLUTION_CONTEXT_H + +#include "chpl/framework/Context.h" +#include "chpl/framework/ID.h" +#include "chpl/framework/query-impl.h" +#include "chpl/parsing/parsing-queries.h" + +#include +#include +#include + +namespace chpl { +namespace resolution { + +// Forward declare some types we reference but can't include yet, as their +// declarations may depend on this file. +struct Resolver; +class ResolutionResultByPostorderID; +class ResolvedFunction; +class TypedFnSignature; +class UntypedFnSignature; +class MatchingIdsWithName; + +/** + This class is used to manage stack frames that may be necessary while + resolving. Any resolution function that may require stack frames will + take a 'ResolutionContext*' as its first argument instead of 'Context*'. + + In the vast majority of cases an end-user of the API can call such a + function using the following sort of pattern: + + ResolutionContext rcval(context); + auto crr = resolveCall(&rcval, ...); + + The 'ResolutionContext' consists of a series of 'frames' that roughly + correspond to typical stack frames. There are two types of frame, + _stable_ frames and _unstable_ frames. Unstable frames are generally + created to wrap around a mutable 'Resolver*' instance. Stable frames + wrap around immutable query results such as 'const ResolvedFunction*'. + + At any point in time, a 'ResolutionContext*' is either stable or unstable. + It is unstable if it has one or more unstable frames. + + When the 'ResolutionContext*' is unstable, any 'CHPL_RESOUTION_QUERY...' + will consult the passed in query arguments in order to determine if the + 'Context*' query cache can be used. If the arguments may refer to a + nested function, then the computed result is not cached in the global + query cache. +*/ +class ResolutionContext { + private: + // This class is used to implement a cache for stored results. + class StoredResultBase { + public: + StoredResultBase() = default; + virtual ~StoredResultBase() = default; + virtual void* get() { return nullptr; } + virtual bool operator==(const StoredResultBase& rhs) const = 0; + virtual size_t hash() const = 0; + struct OwnedKeyByValEquals { + bool operator()(const owned& lhs, + const owned& rhs) const { + return *lhs == *rhs; + } + }; + struct OwnedKeyByValHash { + size_t operator()(const owned& x) const { + return x->hash(); + } + }; + }; + + // This class is used to implement a cache for stored results. + template + class StoredResult final : public StoredResultBase { + T t_; + public: + StoredResult(T&& t) : t_(std::move(t)) {} + virtual ~StoredResult() = default; + virtual void* get() override { return &t_; } + virtual size_t hash() const override { return chpl::hash(t_); } + virtual bool operator==(const StoredResultBase& rhs) const override { + auto rptr = ((const StoredResult*) &rhs); + return this->t_ == rptr->t_; + } + }; + + public: + + /** This class represents a single 'frame' for a symbol. It is currently + a thin wrapper around the internal 'Resolver' type. Generally, a + frame is pushed every time specific resolvers are created, and popped + when they are destroyed. */ + class Frame { + public: + enum Kind { FUNCTION, MODULE, UNKNOWN }; + + private: + friend class ResolutionContext; + using StoreSlot = owned; + using StoreHash = StoredResultBase::OwnedKeyByValHash; + using StoreEqual = StoredResultBase::OwnedKeyByValEquals; + using Store = std::unordered_set; + static constexpr int64_t BASE_FRAME_INDEX = -1; + static const ID EMPTY_AST_ID; + + Resolver* rv_ = nullptr; + const ResolvedFunction* rf_ = nullptr; + int64_t index_ = BASE_FRAME_INDEX; + Store cachedResults_; + Kind kind_ = UNKNOWN; + + Frame() = default; + Frame(Resolver* rv, Kind kind, int64_t index) + : rv_(rv), index_(index), kind_(kind) { + } + Frame(const ResolvedFunction* rf, int64_t index) + : rf_(rf), index_(index), kind_(FUNCTION) { + } + + public: + ~Frame() = default; + Frame(const Frame& rhs) = delete; + Frame(Frame&& rhs) = default; + Frame& operator=(Frame&& rhs) = default; + Frame& operator=(const Frame& rhs) = delete; + + const Frame* parent(const ResolutionContext* rc) const { + if ((index_ - 1) < 0) return nullptr; + return &rc->frames_[index_ - 1]; + } + Frame* parent(ResolutionContext* rc) { + if ((index_ - 1) < 0) return nullptr; + return &rc->frames_[index_ - 1]; + } + + Resolver* rv() { return rv_; } + const ResolvedFunction* rf() { return rf_; } + + bool isEmpty() { return !rv() && !rf(); } + const ID& id() const; + const TypedFnSignature* signature() const; + const ResolutionResultByPostorderID* resolutionById() const; + bool isUnstable() const; + + template + const T& cache(T t) { + auto v = toOwned(new StoredResult(std::move(t))); + auto it = cachedResults_.insert(std::move(v)).first; + StoredResultBase* base = it->get(); + CHPL_ASSERT(base); + auto ptr = static_cast(base->get()); + return *ptr; + } + }; + + private: + friend struct Resolver; + + Context* context_ = nullptr; + std::vector frames_; + int numUnstableFrames_ = 0; + Frame baseFrame_; + + const Frame* pushFrame(const ResolvedFunction* rf); + const Frame* pushFrame(Resolver* rv, Frame::Kind kind); + void popFrame(const ResolvedFunction* rf); + void popFrame(Resolver* rv); + + Frame* lastFrameMutable() { + return frames_.empty() ? nullptr : &frames_.back(); + } + Frame& lastFrameOrBaseMutable() { + return frames_.empty() ? baseFrame_ : frames_.back(); + } + + template + Frame* findFrameMatchingMutable(F predicate) { + for (auto f = lastFrameMutable(); f; f = f->parent(this)) { + if (f && predicate(*f)) return f; + } + return nullptr; + } + + Resolver* findParentResolverFor(const TypedFnSignature* sig); + + public: + /** Create an empty 'ResolutionContext'. */ + explicit ResolutionContext(Context* context) : context_(context) {} + ResolutionContext(const ResolutionContext& rhs) = delete; + ResolutionContext(ResolutionContext&& rhs) = default; + ~ResolutionContext() = default; + + /** Get the underlying compiler context. */ + Context* context() const { return context_; } + + /** If 'true', then this 'ResolutionContext' has no state available. */ + bool isEmpty() const { return frames_.empty(); } + + /** If 'true', then this contains one or more unstable frames. */ + bool isUnstable() const { return numUnstableFrames_ != 0; } + + /** Get the last frame from the frame stack. */ + const Frame* lastFrame() const { + return frames_.empty() ? nullptr : &frames_.back(); + } + + /** Get the frame stack. */ + const std::vector& frames() const { return frames_; } + + /** Get the base frame. It is not on the frame stack. */ + const Frame& baseFrame() const { return baseFrame_; } + + /** Find the first stack frame matching a predicate in LIFO order. */ + template + const Frame* findFrameMatching(F predicate) const { + for (auto f = lastFrame(); f; f = f->parent(this)) { + if (f && predicate(*f)) return f; + } + return nullptr; + } + + /** Find the first stack frame containing _or_ matching an id. */ + const Frame* findFrameWithId(const ID& id) const { + return findFrameMatching([&](auto& f) { + return f.id() == id || f.id().contains(id); + }); + } + + /** Determine if the global query cache stored in the 'Context*' can + be used. If this 'ResolutionContext*' is not unstable, then the + global query cache can always be used. + + If not, then consider 't'. In general, if 't' is a type that + refers to a nested function, conservatively return 'false'. */ + template + bool canUseGlobalCacheConsidering(const T& t) const { + if (!isUnstable()) return true; + return ResolutionContext::canUseGlobalCache(context_, t); + } + template + bool canUseGlobalCacheConsidering(const T* t) const { + return canUseGlobalCacheConsidering(*t); + } + template + bool canUseGlobalCacheConsidering(const std::tuple& ts) { + return std::apply([=](auto&... x) { + return (canUseGlobalCacheConsidering(x) && ...); + }, ts); + } + + // This overload takes at least two items to differentiate itself from + // the single argument overloads above (it is possible for the parameter + // pack to be empty). + template + bool canUseGlobalCacheConsidering(const T1& t1, const T2& t2, + const Ts& ...ts) { + if (isEmpty() || !isUnstable()) return true; + auto tup = std::forward_as_tuple(t1, t2, ts...); + return canUseGlobalCacheConsidering(tup); + } + + /** Can the global query cache can be invoked considering only 't'? + By default, assume it can be. */ + template + static bool canUseGlobalCache(Context* context, const T& t) { + return true; + } + template + static bool canUseGlobalCache(Context* context, const T*& t) { + if (t == nullptr) return true; + return canUseGlobalCache(context, *t); + } + template + static bool canUseGlobalCache(Context* context, const T* const& t) { + if (t == nullptr) return true; + return canUseGlobalCache(context, *t); + } + template + static bool canUseGlobalCache(Context* context, const std::vector& v) { + for (auto& x : v) if (!canUseGlobalCache(context, x)) return false; + return true; + } + + // Concrete overloads for types commonly encountered in resolution queries. + static bool + canUseGlobalCache(Context* context, const UntypedFnSignature& t); + static bool + canUseGlobalCache(Context* context, const TypedFnSignature& t); + static bool + canUseGlobalCache(Context* context, const ID& t); + static bool + canUseGlobalCache(Context* context, const MatchingIdsWithName& ids); + + // + // Forward declare nested structs that are used to implement query classes. + // These are defined later in the file. + // --- + + template + struct GlobalQueryWrapper; + + template + class GlobalQuery; + + template + struct CanUseGlobalCache; + + template + struct UnstableCache; + + template + struct GlobalComputeSetup; + + template + class Query; + + /** Called to construct an instance of a 'ResolutionContext::Query'. + These implement the 'CHPL_RESOLUTION_QUERY...' family of + macros defined below. */ + template + auto createQueryClass(const char* name, InvokeArgs&&... args) { + using InvokeRet = decltype(F(this, args...)); + using Q = ResolutionContext::Query; + return Q(this, name, std::forward(args)...); + } +}; + +/** This struct forms an adapter between queries that work with the + 'ResolutionContext*' and traditional queries that work with the + 'Context*' (the global query cache). + + The function that contains the user's query code is the template + value 'F'. The function stored in the global query cache is + 'query', defined below. The only time 'query' should be called is + when the 'Context*' is recomputing queries after bumping the + revision. In that case, 'query' creates an empty 'ResolutionContext' + and uses it to call into 'F'. +*/ +template +struct ResolutionContext::GlobalQueryWrapper { + static const RetByVal& query(Context* context, ArgsByValue... args) { + ResolutionContext rcval(context); + return F(&rcval, std::move(args)...); + } +}; + +// TODO: Consider moving all global queries to use this implementation. +/** This is a class-based wrapper around global context queries. */ +template +class ResolutionContext::GlobalQuery { + static auto getStopwatchType() { + // These values are invalid, but that's OK, we just need their types. + Context* context = nullptr; + querydetail::QueryMapBase* base = nullptr; + ArgsByValueTuple* ap = nullptr; + return context->makeQueryTimingStopwatch(base, *ap); + } + + public: + using Ret = InvokeRet; + using RetNoRef = typename std::remove_reference::type; + using RetByVal = typename std::remove_const::type; + template + using Value = std::decay_t; + using InvokeArgsTuple = std::tuple; + using ArgsByValueTuple = std::tuple...>; + using Wrapper = GlobalQueryWrapper...>; + + #if CHPL_QUERY_TIMING_AND_TRACE_ENABLED + static constexpr bool STOPWATCH_IS_ACTIVE = true; + #else + static constexpr bool STOPWATCH_IS_ACTIVE = false; + #endif + + private: + // Exposing the implementation details of 'QUERY_BEGIN', 'QUERY_END'. + using QueryMap = querydetail::QueryMap...>; + using QueryRet = querydetail::QueryMapResult...>; + using RecomputeMarker = Context::RecomputeMarker; + using Stopwatch = decltype(getStopwatchType()); + + static constexpr bool RETURNS_CONST = std::is_const::value; + static constexpr bool RETURNS_REF = std::is_reference::value; + static constexpr auto QUERY = Wrapper::query; + + // Assert early that the user-called function is returning by 'const&'. + static_assert(RETURNS_CONST, "Query needs to return by 'const'!"); + static_assert(RETURNS_REF, "Query needs to return by '&'!"); + + Context* context_ = nullptr; + const char* name_ = nullptr; + + // References to the arguments as they were passed in. + InvokeArgsTuple args_; + + // TODO: Right now we need to copy in 'args_' by value to pass them + // to the 'context->query...' methods. Can this pack be removed in + // the future? + ArgsByValueTuple ap_ = args_; + + // This state is used to mimick 'QUERY_BEGIN' and 'QUERY_END'. + RecomputeMarker recomputeMarker_; + QueryMap* beginMap_ = nullptr; + const QueryRet* beginRet_ = nullptr; + Stopwatch stopwatch_; + bool isInput_ = false; + + public: + GlobalQuery(Context* context, const char* name, InvokeArgs&&... args) + : context_(context), + name_(name), + args_({std::forward(args)...}), + stopwatch_(context_->makeQueryTimingStopwatch(nullptr, ap_)) { + CHPL_ASSERT(context_ != nullptr); + } + + GlobalQuery(const GlobalQuery& rhs) = delete; + GlobalQuery(GlobalQuery&& rhs) = default; + + const InvokeArgsTuple& args() const { return args_; } + + const RetByVal* begin() { + // Fetch/emplace the map entries, which indicates the query is running. + beginMap_ = context_->queryBeginGetMap(QUERY, ap_, name_, isInput_); + beginRet_ = context_->queryBeginGetResult(beginMap_, ap_); + + // Exit early if we've found a result for this revision. + if (context_->queryUseSaved(QUERY, beginRet_, name_)) { + auto ret = &context_->queryGetSaved(beginRet_); + beginMap_ = nullptr; + beginRet_ = nullptr; + return ret; + } + + // Otherwise we are computing, so set the recompute marker. + const bool isRecomputing = false; + auto activeRecomputeMarker = context_->markRecomputing(isRecomputing); + std::swap(recomputeMarker_, activeRecomputeMarker); + + // Set the stopwatch if it is compile-time enabled. + if constexpr (STOPWATCH_IS_ACTIVE) { + context_->queryBeginTrace(name_, ap_); + stopwatch_ = context_->makeQueryTimingStopwatch(beginMap_, ap_); + } + + return nullptr; + } + + const RetByVal& end(RetByVal x) { + // Restoring the marker also clears it. + recomputeMarker_.restore(); + + // Pass in the result and indicate the query has ended. + auto& ret = context_->queryEnd(QUERY, beginMap_, beginRet_, ap_, + std::move(x), + name_); + beginMap_ = nullptr; + beginRet_ = nullptr; + + return ret; + } + + void inactiveStore(RetByVal x) { + context_->querySetterUpdateResult(QUERY, ap_, std::move(x), + name_, + isInput_); + } + + bool isRunning() { + return context_->isQueryRunning(QUERY, ap_); + } + + bool hasCurrentResult() { + return context_->hasCurrentResultForQuery(QUERY, ap_); + } +}; + +template +struct ResolutionContext::GlobalComputeSetup { + /** Called before the query is run, returns 'true' if prep was done. */ + bool enter(ResolutionContext* rc, const InvokeArgsTuple& args) { + return false; + } + /** Called before the query returns, only if 'enter' returned 'true'. */ + void leave(ResolutionContext* rc, const InvokeArgsTuple& args) { + } +}; + +template +struct ResolutionContext::CanUseGlobalCache { + /** Consider 'args' and determine if the global query cache can be used. */ + bool operator()(ResolutionContext* rc, const InvokeArgsTuple& args) { + return std::apply([&](auto... xs) { + return rc->canUseGlobalCacheConsidering(xs...); + }, args); + } +}; + +/** This struct can be specialized to allow queries to specify how computed + results are cached when the 'ResolutionContext*' is unstable. */ +template +struct ResolutionContext::UnstableCache { + + /** If the 'ResolutionContext' is unstable and the global query cache + cannot be used, determine if a value stored in stack frames can be + reused for the given input. By default, no lookup occurs. */ + const RetByVal* + fetchOrNull(ResolutionContext* rc, const InvokeArgsTuple& args) { + (void) rc; + (void) args; + return nullptr; + } + + /** Implements the 'caching' of unstable query computations by storing + them into a 'ResolutionContext::Frame'. If no frame is suitable the + base frame may be used (which has a lifetime as long as the RC). + By default, use the closest frame. */ + const RetByVal& + store(ResolutionContext* rc, RetByVal x, const InvokeArgsTuple& args) { + Frame& f = rc->lastFrameOrBaseMutable(); + return f.cache(std::move(x)); + } +}; + +/** This class represents an instance of a query created by the macro + 'CHPL_RESOLUTION_QUERY_BEGIN...'. A resolution query is itself a + wrapper around a global query (cached by the 'Context*'), which is + represented by the 'GlobalQuery' type. */ +template +class ResolutionContext::Query { + // TODO: Refactor this into the more general notion of a "global query" + // (e.g., the 'GlobalQuery' defined above) that can be augmented with + // additional state or subclassed. + // + // NOTE: The 'T' suffixes to prevent a GCC error message. + using GlobalQueryT = GlobalQuery; + using RetByVal = typename GlobalQueryT::RetByVal; + using InvokeArgsTuple = typename GlobalQueryT::InvokeArgsTuple; + using UnstableCacheT = UnstableCache; + using CanUseGlobalCacheT = CanUseGlobalCache; + using GlobalComputeSetupT = GlobalComputeSetup; + + ResolutionContext* rc_ = nullptr; + bool canUseGlobalCache_ = false; + bool didGlobalSetupOccur_ = false; + GlobalComputeSetupT globalSetup_; + GlobalQueryT global_; + + public: + Query(ResolutionContext* rc, const char* name, InvokeArgs&&... args) + : rc_(rc), global_(rc->context(), name, args... ) { + CHPL_ASSERT(rc_ && context()); + canUseGlobalCache_ = CanUseGlobalCacheT{}(rc, global_.args()); + } + Query(const Query& rhs) = delete; + Query(Query&& rhs) = default; + + Context* context() { return rc_->context(); } + + bool canUseGlobalCache() { return canUseGlobalCache_; } + + const RetByVal* begin() { + // TODO: Use specialization to eliminate this branch? + if (canUseGlobalCache()) { + if (auto ret = global_.begin()) return ret; + didGlobalSetupOccur_ = globalSetup_.enter(rc_, global_.args()); + return nullptr; + } else { + UnstableCacheT uc; + return uc.fetchOrNull(rc_, global_.args()); + } + } + + const RetByVal& end(RetByVal x) { + if (canUseGlobalCache()) { + auto& ret = global_.end(std::move(x)); + if (didGlobalSetupOccur_) { + globalSetup_.leave(rc_, global_.args()); + didGlobalSetupOccur_ = false; + } + return ret; + } else { + UnstableCacheT uc; + return uc.store(rc_, std::move(x), global_.args()); + } + } + + bool isGlobalQueryRunning() { + return global_.isRunning(); + } + + void inactiveStore(RetByVal x) { + if (canUseGlobalCache()) { + global_.inactiveStore(std::move(x)); + } else { + UnstableCacheT uc; + uc.store(rc_, std::move(x), global_.args()); + } + } +}; + +/** This macro can be used like 'QUERY_BEGIN', except it prevents the results + of a query from being cached in the context query cache if the computed + result could rely on state taken from the type 'ResolutionContext'. + + Queries guarded by this macro must always return by 'const&'. When a + result is not stored in the context query cache, it will be stored in + a temporary cache maintained by the innermost 'ResolutionContext::Frame' + instead. The lifetime of temporarily cached values is the lifetime of + the associated 'ResolutionContext::Frame'. +*/ +#define CHPL_RESOLUTION_QUERY_BEGIN(fn__, rc__, ...) \ + auto rcquery__ = rc__->createQueryClass(#fn__, __VA_ARGS__); \ + if (auto ptr__ = rcquery__.begin()) return *ptr__; + +/** This macro can be used like 'QUERY_END'. It always returns a reference, + and in the case that the context cache is not used then the returned + value is stored in the cache of a 'ResolutionContext' frame. */ +#define CHPL_RESOLUTION_QUERY_END(ret__) (rcquery__.end(std::move(ret__))) + +/** Within a 'CHPL_RESOLUTION_QUERY...' use this to get the query handle. */ +#define CHPL_RESOLUTION_REF_TO_CURRENT_QUERY_HANDLE() (rcquery__) + +/** Use this to store a result for any 'CHPL_RESOLUTION_QUERY...' query. */ +#define CHPL_RESOLUTION_QUERY_STORE_RESULT(fn__, rc__, x__, ...) do { \ + auto rcq__ = rc__->createQueryClass(#fn__, __VA_ARGS__); \ + rcq__.inactiveStore(std::move(x__)); \ + } while (0) + +/** Check if the global part of a 'CHPL_RESOLUTION_QUERY...' is running. */ +#define CHPL_RESOLUTION_IS_GLOBAL_QUERY_RUNNING(fn__, rc__, ...) ([&]() { \ + auto rcq__ = rc__->createQueryClass(#fn__, __VA_ARGS__); \ + return rcq__.isGlobalQueryRunning(); \ + }()) + +} // end namespace resolution +} // end namespace chpl + +#endif diff --git a/frontend/include/chpl/resolution/ResolvedVisitor.h b/frontend/include/chpl/resolution/ResolvedVisitor.h index 0d1cda181367..8d216d0845d0 100644 --- a/frontend/include/chpl/resolution/ResolvedVisitor.h +++ b/frontend/include/chpl/resolution/ResolvedVisitor.h @@ -49,7 +49,7 @@ static bool resolvedVisitorEnterFor(ResolvedVisitorImpl& v, // TODO: Should there be some kind of function the UserVisitor can // implement to observe a new iteration of the loop body? for (const auto& loopBody : resolvedLoop->loopBodies()) { - ResolvedVisitorImpl loopVis(v.context(), loop, + ResolvedVisitorImpl loopVis(v.rc(), loop, v.userVisitor(), loopBody); for (const AstNode* child : loop->children()) { @@ -159,7 +159,7 @@ static bool resolvedVisitorEnterAst(ResolvedVisitorImpl& v, template class ResolvedVisitor { using UserVisitorType = UV; - Context* context_ = nullptr; + ResolutionContext* rc_ = nullptr; const uast::AstNode* ast_ = nullptr; UV& userVisitor_; @@ -167,19 +167,22 @@ class ResolvedVisitor { const ResolutionResultByPostorderID& byPostorder_; public: - ResolvedVisitor(Context* context, + ResolvedVisitor(ResolutionContext* rc, const uast::AstNode* ast, UV& userVisitor, const ResolutionResultByPostorderID& byPostorder) - : context_(context), + : rc_(rc), ast_(ast), userVisitor_(userVisitor), byPostorder_(byPostorder) { } + /** Return the ResolutionContext used by this ResolvedVisitor */ + ResolutionContext* rc() const { return rc_; } + /** Return the context used by this ResolvedVisitor */ Context* context() const { - return context_; + return rc_->context(); } /** Return the uAST node being visited by this ResolvedVisitor */ const uast::AstNode* ast() const { @@ -250,7 +253,7 @@ class ResolvedVisitor { template class MutatingResolvedVisitor { using UserVisitorType = UV; - Context* context_ = nullptr; + ResolutionContext* rc_ = nullptr; const uast::AstNode* ast_ = nullptr; UV& userVisitor_; @@ -258,19 +261,22 @@ class MutatingResolvedVisitor { ResolutionResultByPostorderID& byPostorder_; public: - MutatingResolvedVisitor(Context* context, - const uast::AstNode* ast, - UV& userVisitor, - const ResolutionResultByPostorderID& byPostorder) - : context_(context), + MutatingResolvedVisitor(ResolutionContext* rc, + const uast::AstNode* ast, + UV& userVisitor, + const ResolutionResultByPostorderID& byPostorder) + : rc_(rc), ast_(ast), userVisitor_(userVisitor), byPostorder_(const_cast(byPostorder)) { } + /** Return the ResolutionContext used by this ResolvedVisitor */ + ResolutionContext* rc() const { return rc_; } + /** Return the context used by this ResolvedVisitor */ Context* context() const { - return context_; + return rc_->context();; } /** Return the uAST node being visited by this ResolvedVisitor */ const uast::AstNode* ast() const { diff --git a/frontend/include/chpl/resolution/resolution-queries.h b/frontend/include/chpl/resolution/resolution-queries.h index 160bf8cb099c..9882afdf8f5f 100644 --- a/frontend/include/chpl/resolution/resolution-queries.h +++ b/frontend/include/chpl/resolution/resolution-queries.h @@ -111,16 +111,16 @@ ID lookupEnumElementByNumericValue(Context* context, The TypedFnSignature will represent generic and potentially unknown types if the function is generic. */ -const TypedFnSignature* -typedSignatureInitial(Context* context, - const UntypedFnSignature* untypedSig); +const TypedFnSignature* const& +typedSignatureInitial(ResolutionContext* rc, const UntypedFnSignature* untyped); /** Compute a initial TypedFnSignature for an ID. The TypedFnSignature will represent generic and potentially unknown types if the function is generic. */ -const TypedFnSignature* typedSignatureInitialForId(Context* context, ID id); +const TypedFnSignature* +typedSignatureInitialForId(ResolutionContext* rc, ID id); /** Returns a Type that represents the initial type provided by a TypeDecl @@ -262,7 +262,7 @@ const TypedFnSignature* typeConstructorInitial(Context* context, * a CallInfo describing the types at the call site, and * a point-of-instantiation scope representing the POI scope of the call */ -ApplicabilityResult instantiateSignature(Context* context, +ApplicabilityResult instantiateSignature(ResolutionContext* rc, const TypedFnSignature* sig, const CallInfo& call, const PoiScope* poiScope); @@ -271,29 +271,17 @@ ApplicabilityResult instantiateSignature(Context* context, Compute a ResolvedFunction given a TypedFnSignature. Checks the generic cache for potential for reuse. When reuse occurs, the ResolvedFunction might point to a different TypedFnSignature. - - This function will resolve a nested function if it does not refer to - any outer variables. */ -const ResolvedFunction* resolveFunction(Context* context, +const ResolvedFunction* resolveFunction(ResolutionContext* rc, const TypedFnSignature* sig, const PoiScope* poiScope); -/** - Compute a ResolvedFunction given a TypedFnSignature for an initializer. - The difference between this and 'resolveFunction' is that it is - possible for the type of the receiver to still be generic (as the - initializer body must be resolved before the concrete type is known). -*/ -const ResolvedFunction* resolveInitializer(Context* context, - const TypedFnSignature* sig, - const PoiScope* poiScope); - /** Helper to resolve a concrete function using the above queries. Will return `nullptr` if the function is generic or has a `where false`. */ -const ResolvedFunction* resolveConcreteFunction(Context* context, ID id); +const ResolvedFunction* +resolveConcreteFunction(Context* context, ID id); /** Compute a ResolvedFunction given a TypedFnSignature, but don't @@ -301,12 +289,6 @@ const ResolvedFunction* resolveConcreteFunction(Context* context, ID id); */ const ResolvedFunction* scopeResolveFunction(Context* context, ID id); -/** - Compute the set of outer variables referenced by this function. Will return - 'nullptr' if there are no outer variables. - */ -const OuterVariables* computeOuterVariables(Context* context, ID id); - /* * Scope-resolve an AggregateDecl's fields, along with their type expressions * and initialization expressions. @@ -320,15 +302,6 @@ const ResolutionResultByPostorderID& scopeResolveAggregate(Context* context, const ResolutionResultByPostorderID& scopeResolveEnum(Context* context, ID id); -/** - Returns the ResolvedFunction called by a particular - ResolvedExpression, if there was exactly one candidate. - Otherwise, it returns nullptr. - - This function does not handle return intent overloading. - */ -const ResolvedFunction* resolveOnlyCandidate(Context* context, - const ResolvedExpression& r); /** Compute the return/yield type for a function. @@ -337,9 +310,9 @@ const ResolvedFunction* resolveOnlyCandidate(Context* context, the return type is explicitly declared. We probably still want to compute the value in such cases, though. */ -const types::QualifiedType& returnType(Context* context, - const TypedFnSignature* sig, - const PoiScope* poiScope); +types::QualifiedType returnType(ResolutionContext* rc, + const TypedFnSignature* sig, + const PoiScope* poiScope); /** Compute the types for any generic 'out' formal types after instantiation @@ -354,7 +327,7 @@ const types::QualifiedType& returnType(Context* context, The returned TypedFnSignature* will have the inferred out formal types. */ -const TypedFnSignature* inferOutFormals(Context* context, +const TypedFnSignature* inferOutFormals(ResolutionContext* rc, const TypedFnSignature* sig, const PoiScope* poiScope); @@ -365,7 +338,7 @@ const TypedFnSignature* inferOutFormals(Context* context, 'nullptr'. In that case, the caller is responsible for attempting this again later once the current set of recursive functions is resolved. */ -const TypedFnSignature* inferRefMaybeConstFormals(Context* context, +const TypedFnSignature* inferRefMaybeConstFormals(ResolutionContext* rc, const TypedFnSignature* sig, const PoiScope* poiScope); @@ -376,7 +349,7 @@ const TypedFnSignature* inferRefMaybeConstFormals(Context* context, candidate functions from a list of visible functions. */ const CandidatesAndForwardingInfo& -filterCandidatesInitial(Context* context, +filterCandidatesInitial(ResolutionContext* rc, MatchingIdsWithName lst, CallInfo call); @@ -390,7 +363,7 @@ filterCandidatesInitial(Context* context, */ void -filterCandidatesInstantiating(Context* context, +filterCandidatesInstantiating(ResolutionContext* rc, const CandidatesAndForwardingInfo& lst, const CallInfo& call, const Scope* inScope, @@ -407,11 +380,11 @@ filterCandidatesInstantiating(Context* context, 'resolveCallInMethod' should be used instead when resolving a non-method call within a method. */ -CallResolutionResult resolveCall(Context* context, +CallResolutionResult resolveCall(ResolutionContext* rc, const uast::Call* call, const CallInfo& ci, const CallScopeInfo& inScopes, - std::vector* rejected = nullptr); + std::vector* rejected=nullptr); /** Similar to resolveCall, but handles the implicit scope provided by a method. @@ -421,12 +394,12 @@ CallResolutionResult resolveCall(Context* context, If implicitReceiver.type() == nullptr, it will be ignored. */ -CallResolutionResult resolveCallInMethod(Context* context, +CallResolutionResult resolveCallInMethod(ResolutionContext* rc, const uast::Call* call, const CallInfo& ci, const CallScopeInfo& inScopes, types::QualifiedType implicitReceiver, - std::vector* rejected = nullptr); + std::vector* rejected=nullptr); /** Given a CallInfo representing a call, a Scope representing the diff --git a/frontend/include/chpl/resolution/resolution-types.h b/frontend/include/chpl/resolution/resolution-types.h index 2091523c721f..d8c043adaa89 100644 --- a/frontend/include/chpl/resolution/resolution-types.h +++ b/frontend/include/chpl/resolution/resolution-types.h @@ -20,7 +20,9 @@ #ifndef CHPL_RESOLUTION_RESOLUTION_TYPES_H #define CHPL_RESOLUTION_RESOLUTION_TYPES_H +#include "chpl/framework/query-impl.h" #include "chpl/framework/UniqueString.h" +#include "chpl/resolution/ResolutionContext.h" #include "chpl/resolution/scope-types.h" #include "chpl/types/CompositeType.h" #include "chpl/types/EnumType.h" @@ -31,14 +33,18 @@ #include "chpl/uast/For.h" #include "chpl/uast/Function.h" #include "chpl/util/bitmap.h" +#include "chpl/util/hash.h" #include "chpl/util/memory.h" #include +#include #include namespace chpl { namespace resolution { +using SubstitutionsMap = types::CompositeType::SubstitutionsMap; + /** In some situations, we may decide not to resolve a call. This could @@ -376,85 +382,44 @@ class UntypedFnSignature { }; /** - This type represents the outer variables used in a function. It stores - the variables and all their mentions in lexical order. It presents the - concept of a 'reaching variable', which is a reference to an outer - variable that is not defined in the symbol's immediate parent. + This type stores the outer variables used by a function. For each mention + of an outer variable, it keeps track of the outer variable's ID and type. */ -// TODO: We can drop some of this state if we decide we don't care about -// preserving lexical ordering or mentions at all (not 100% sure yet). class OuterVariables { + public: + using QualifiedType = types::QualifiedType; + using TargetAndType = std::pair; + private: + std::map targetIdToType_; + std::map mentionIdToTargetId_; - // Record all outer variables used in lexical order. - std::vector variables_; - - // Record all mentions of variables in lexical order. A variable may have - // zero mentions if it was only ever referenced by a child function. In - // this case, we still record the variable so that we can know to propagate - // it into our parent's state. - std::vector mentions_; - - using VarAndMentionIndices = std::pair>; - using IdToVarAndMentionIndices = std::unordered_map; - - // Enables lookup of variables and their mentions given just an ID. The - // first part of the pair is the index of the variable, and the second - // component is the list of mention indices. - IdToVarAndMentionIndices idToVarAndMentionIndices_; - - // The number of outer variables that are defined in distant (not our - // immediate) parents. Only variables defined by a function's most - // immediate parents need to be recorded into its 'TypedFnSignature'. - int numReachingVariables_ = 0; - - // The function that owns this instance. - ID symbol_; - - // The immediate parent of 'symbol_'. So that we can detect if a variable - // is 'reaching' without needing the compiler context. - ID parent_; - - template - static inline bool inBounds(const std::vector v, size_t idx) { - return 0 <= idx && idx < v.size(); - } - -public: - OuterVariables(Context* context, ID symbol) - : symbol_(std::move(symbol)), - parent_(symbol_.parentSymbolId(context)) { - } - + public: + OuterVariables() = default; ~OuterVariables() = default; - bool operator==(const OuterVariables& other) const { - return variables_ == other.variables_ && - mentions_ == other.mentions_ && - idToVarAndMentionIndices_ == other.idToVarAndMentionIndices_ && - numReachingVariables_ == other.numReachingVariables_ && - symbol_ == other.symbol_ && - parent_ == other.parent_; + bool operator==(const OuterVariables& rhs) const { + return targetIdToType_ == rhs.targetIdToType_ && + mentionIdToTargetId_ == rhs.mentionIdToTargetId_; } - bool operator!=(const OuterVariables& other) const { - return !(*this == other); + bool operator!=(const OuterVariables& rhs) const { + return !(*this == rhs); } - void swap(OuterVariables& other) { - std::swap(variables_, other.variables_); - std::swap(mentions_, other.mentions_); - std::swap(idToVarAndMentionIndices_, other.idToVarAndMentionIndices_); - std::swap(numReachingVariables_, other.numReachingVariables_); - std::swap(symbol_, other.symbol_); - std::swap(parent_, other.parent_); + void swap(OuterVariables& rhs) { + std::swap (targetIdToType_, rhs.targetIdToType_); + std::swap (mentionIdToTargetId_, rhs.mentionIdToTargetId_); } void mark(Context* context) const { - for (auto& v : variables_) v.mark(context); - for (auto& id : mentions_) id.mark(context); - for (auto& p : idToVarAndMentionIndices_) p.first.mark(context); - symbol_.mark(context); - parent_.mark(context); + for (auto& p : targetIdToType_) { + p.first.mark(context); + p.second.mark(context); + } + for (auto& p : mentionIdToTargetId_) { + p.first.mark(context); + p.second.mark(context); + } } static inline bool update(owned& keep, @@ -462,89 +427,40 @@ class OuterVariables { return defaultUpdateOwned(keep, addin); } - // Mutating method used to build up state. - void add(Context* context, ID mention, ID var); - - /** Returns 'true' if there are no outer variables. */ - bool isEmpty() const { return numVariables() == 0; } - - /** The total number of outer variables. */ - int numVariables() const { return variables_.size(); } - - /** The number of outer variables declared in our immediate parent. */ - int numImmediateVariables() const { - return numVariables() - numReachingVariables_; - } - - /** The number of outer variables declared in our non-immediate parents. */ - int numReachingVariables() const { return numReachingVariables_; } - - /** The number of outer variable mentions in this symbol's body. */ - int numMentions() const { return mentions_.size(); } - - /** Get the number of mentions for 'var' in this symbol. */ - int numMentions(const ID& var) const { - auto it = idToVarAndMentionIndices_.find(var); - return it != idToVarAndMentionIndices_.end() - ? it->second.second.size() - : 0; - } - - /** Returns 'true' if there is at least one mention of 'var'. */ - bool mentions(const ID& var) const { return numMentions(var) > 0; } - - /** Returns 'true' if this contains an entry for 'var'. */ - bool contains(const ID& var) const { - return idToVarAndMentionIndices_.find(var) != - idToVarAndMentionIndices_.end(); - } - - /** Get the i'th outer variable or the empty ID if 'idx' was out of bounds. */ - ID variable(size_t idx) const { - return inBounds(variables_, idx) ? variables_[idx] : ID(); + size_t hash() const { + return chpl::hash(targetIdToType_, mentionIdToTargetId_); } - /** A reaching variable is declared in a non-immediate parent(s). */ - bool isReachingVariable(const ID& var) const { - auto it = idToVarAndMentionIndices_.find(var); - if (it != idToVarAndMentionIndices_.end()) { - auto& var = variables_[it->second.first]; - return !parent_.contains(var); + void stringify(std::ostream& ss, chpl::StringifyKind stringKind) const { + for (auto& p : targetIdToType_) { + p.first.stringify(ss, stringKind); + ss << " : "; + p.second.stringify(ss, stringKind); } - return false; } - /** A reaching variable is declared in a non-immediate parent(s). */ - bool isReachingVariable(size_t idx) const { - if (auto id = variable(idx)) return isReachingVariable(id); - return false; + /** Mutating method used to build up state. If either the mention or + target IDs are empty then nothing happens. */ + void add(const ID& mention, const ID& target, const QualifiedType& qt) { + if (!mention || !target) return; + auto p1 = std::make_pair(mention, target); + if (!mentionIdToTargetId_.insert(std::move(p1)).second) return; + auto p2 = std::make_pair(target, qt); + targetIdToType_.insert(std::move(p2)); } - /** Get the i'th mention in this function. */ - ID mention(size_t idx) const { - return inBounds(mentions_, idx) ? mentions_[idx] : ID(); + /** For a given mention, return the target ID and type if it exists. */ + const TargetAndType* targetAndTypeOrNull(const ID& mention) const { + auto it = mentionIdToTargetId_.find(mention); + if (it == mentionIdToTargetId_.end()) return nullptr; + return &*targetIdToType_.find(it->second); } - /** Get the i'th mention for 'var' within this function, or the empty ID. */ - ID mention(const ID& var, size_t idx) const { - auto it = idToVarAndMentionIndices_.find(var); - if (it == idToVarAndMentionIndices_.end()) return {}; - return inBounds(it->second.second, idx) - ? mentions_[it->second.second[idx]] - : ID(); + bool isEmpty() const { + return mentionIdToTargetId_.empty(); } - - /** Get the first mention of 'var', or the empty ID. */ - ID firstMention(const ID& var) const { return mention(var, 0); } - - /** Get the ID of the symbol this instance was created for. */ - const ID& symbol() const { return symbol_; } - - /** Get the ID of the owning symbol's parent. */ - const ID& parent() const { return parent_; } }; -/** CallInfoActual */ class CallInfoActual { private: types::QualifiedType type_; @@ -750,7 +666,6 @@ class CallInfo { /// \endcond DO_NOT_DOCUMENT }; - using PoiCallIdFnIds = std::set>; using PoiRecursiveCalls = std::set>; @@ -760,6 +675,10 @@ using PoiRecursiveCalls = std::set; private: // is this PoiInfo for a function that has been resolved, or // for a function we are about to resolve? @@ -787,8 +706,10 @@ class PoiInfo { PoiRecursiveCalls recursiveFnsUsed_; public: - // default construct a PoiInfo - PoiInfo() { } + PoiInfo() = default; + PoiInfo(const PoiInfo& rhs) = default; + PoiInfo(PoiInfo&& rhs) = default; + PoiInfo& operator=(PoiInfo&& rhs) = default; // construct a PoiInfo for a not-yet-resolved instantiation PoiInfo(const PoiScope* poiScope) @@ -816,6 +737,9 @@ class PoiInfo { return recursiveFnsUsed_; } + PoiInfo::Trace createTraceFor(const TypedFnSignature* sig) const { + return { sig, poiFnIdsUsed_, recursiveFnsUsed_ }; + } void addIds(ID a, ID b) { poiFnIdsUsed_.emplace(a, b); @@ -940,6 +864,10 @@ class TypedFnSignature { // Which formal arguments were substituted when instantiating? Bitmap formalsInstantiated_; + // What are the the types of outer variables used to construct this? + // TODO: Can probably flatten into a vector. + OuterVariables outerVariables_; + TypedFnSignature(const UntypedFnSignature* untypedSignature, std::vector formalTypes, WhereClauseResult whereClauseResult, @@ -947,7 +875,8 @@ class TypedFnSignature { bool isRefinementOnly, const TypedFnSignature* instantiatedFrom, const TypedFnSignature* parentFn, - Bitmap formalsInstantiated) + Bitmap formalsInstantiated, + OuterVariables outerVariables) : untypedSignature_(untypedSignature), formalTypes_(std::move(formalTypes)), whereClauseResult_(whereClauseResult), @@ -955,7 +884,8 @@ class TypedFnSignature { isRefinementOnly_(isRefinementOnly), instantiatedFrom_(instantiatedFrom), parentFn_(parentFn), - formalsInstantiated_(std::move(formalsInstantiated)) { } + formalsInstantiated_(std::move(formalsInstantiated)), + outerVariables_(std::move(outerVariables)) { } static const owned& getTypedFnSignature(Context* context, @@ -966,7 +896,8 @@ class TypedFnSignature { bool isRefinementOnly, const TypedFnSignature* instantiatedFrom, const TypedFnSignature* parentFn, - Bitmap formalsInstantiated); + Bitmap formalsInstantiated, + OuterVariables outerVariables); /** If this is an iterator, set 'found' to a string representing its 'iterKind', or "" if it is a serial iterator. Returns 'true' only @@ -983,7 +914,8 @@ class TypedFnSignature { bool needsInstantiation, const TypedFnSignature* instantiatedFrom, const TypedFnSignature* parentFn, - Bitmap formalsInstantiated); + Bitmap formalsInstantiated, + OuterVariables outerVariables); /** Get the unique TypedFnSignature containing these components for a refinement where some types are inferred (e.g. generic 'out' @@ -994,7 +926,6 @@ class TypedFnSignature { std::vector formalTypes, const TypedFnSignature* inferredFrom); - bool operator==(const TypedFnSignature& other) const { return untypedSignature_ == other.untypedSignature_ && formalTypes_ == other.formalTypes_ && @@ -1003,7 +934,8 @@ class TypedFnSignature { isRefinementOnly_ == other.isRefinementOnly_ && instantiatedFrom_ == other.instantiatedFrom_ && parentFn_ == other.parentFn_ && - formalsInstantiated_ == other.formalsInstantiated_; + formalsInstantiated_ == other.formalsInstantiated_ && + outerVariables_ == other.outerVariables_; } bool operator!=(const TypedFnSignature& other) const { return !(*this == other); @@ -1020,6 +952,7 @@ class TypedFnSignature { context->markPointer(instantiatedFrom_); context->markPointer(parentFn_); (void) formalsInstantiated_; // nothing to mark + outerVariables_.mark(context); } void stringify(std::ostream& ss, chpl::StringifyKind stringKind) const; @@ -1035,7 +968,7 @@ class TypedFnSignature { return untypedSignature_; } - inline bool isCompilerGenerated() const { + bool isCompilerGenerated() const { return untyped()->isCompilerGenerated(); } @@ -1049,6 +982,24 @@ class TypedFnSignature { return needsInstantiation_; } + bool isMethod() const { return untyped()->isMethod(); } + + bool isInit() const { + return untyped()->name() == USTR("init") && isMethod(); + } + + bool isInitEquals() const { + return untyped()->name() == USTR("init=") && isMethod(); + } + + bool isInitializer() const { + return isInit() || isInitEquals(); + } + + bool isDeinit() const { + return untyped()->name() == USTR("deinit") && isMethod(); + } + /** If this TypedFnSignature represents the result of additional inference, return the most basic TypedFnSignature that was inferred from. */ @@ -1102,6 +1053,20 @@ class TypedFnSignature { return sig->parentFn_; } + bool isNestedFunction() const { + return parentFn() != nullptr; + } + + /** Return the outer variables that were used to type this signature. */ + const OuterVariables& outerVariables() const { + return outerVariables_; + } + + /** Returns true if this signature uses any outer variables. */ + bool usesOuterVariables() const { + return !outerVariables_.isEmpty(); + } + /** Returns the number of formals */ int numFormals() const { int ret = formalTypes_.size(); @@ -1118,10 +1083,6 @@ class TypedFnSignature { return formalTypes_[i]; } - bool isMethod() const { - return untypedSignature_->isMethod(); - } - bool isIterator() const { return untypedSignature_->isIterator(); } @@ -1735,7 +1696,8 @@ class MostSpecificCandidates { Adjust each candidate signature by inferring generic 'out' intent formals if there are any. */ - void inferOutFormals(Context* context, const PoiScope* instantiationPoiScope); + void inferOutFormals(ResolutionContext* rc, + const PoiScope* instantiationPoiScope); MostSpecificCandidate const* begin() const { return &candidates[0]; @@ -1974,13 +1936,9 @@ class CallScopeInfo { const PoiScope* poiScope() const { return poiScope_; } }; -class ResolvedParamLoop; - /** - This type represents an associated action (for use within a ResolvedExpression). - */ class AssociatedAction { public: @@ -2033,6 +1991,8 @@ class AssociatedAction { static const char* kindToString(Action a); }; +class ResolvedParamLoop; + /** This type represents a resolved expression. */ @@ -2256,8 +2216,22 @@ class ResolutionResultByPostorderID { /** This type represents a resolved function. + + When a function 'F' is resolved, any child functions that were resolved + as a result of resolving the body of F will be stored in this type as + well. For a generic child 'NF' of F, this does not mean that _all_ + instantiations of NF will be stored here (only instantiations made + for interior calls issued within F). */ class ResolvedFunction { + public: + using PoiTrace = PoiInfo::Trace; + using Child = owned; + using ChildPtr = const ResolvedFunction*; + using PoiTraceToChildMap = std::unordered_map; + using SigAndInfo = std::tuple; + using SigAndInfoToChildPtrMap = std::unordered_map; + private: const TypedFnSignature* signature_ = nullptr; @@ -2273,16 +2247,32 @@ class ResolvedFunction { // the return type computed for this function types::QualifiedType returnType_; + // These two maps attempt to mimick what is done to cache generic + // instantiations in the queries 'resolveFunctionByInfo' and + // 'resolveFunctionByPois'. The maps take the place of the query cache + // (since it cannot be used to store the results of resolving a nested + // function). + PoiTraceToChildMap poiTraceToChild_; + SigAndInfoToChildPtrMap sigAndInfoToChildPtr_; + public: ResolvedFunction(const TypedFnSignature *signature, uast::Function::ReturnIntent returnIntent, ResolutionResultByPostorderID resolutionById, PoiInfo poiInfo, - types::QualifiedType returnType) - : signature_(signature), returnIntent_(returnIntent), + types::QualifiedType returnType, + PoiTraceToChildMap poiTraceToChild, + SigAndInfoToChildPtrMap sigAndInfoToChildPtr) + : signature_(signature), + returnIntent_(returnIntent), resolutionById_(std::move(resolutionById)), poiInfo_(std::move(poiInfo)), - returnType_(std::move(returnType)) {} + returnType_(std::move(returnType)), + poiTraceToChild_(std::move(poiTraceToChild)), + sigAndInfoToChildPtr_(std::move(sigAndInfoToChildPtr)) {} + ~ResolvedFunction() = default; + ResolvedFunction(const ResolvedFunction& rhs) = delete; + ResolvedFunction(ResolvedFunction&& rhs) = default; /** The type signature */ const TypedFnSignature* signature() const { return signature_; } @@ -2308,7 +2298,9 @@ class ResolvedFunction { returnIntent_ == other.returnIntent_ && resolutionById_ == other.resolutionById_ && PoiInfo::updateEquals(poiInfo_, other.poiInfo_) && - returnType_ == other.returnType_; + returnType_ == other.returnType_ && + poiTraceToChild_ == other.poiTraceToChild_ && + sigAndInfoToChildPtr_ == other.sigAndInfoToChildPtr_; } bool operator!=(const ResolvedFunction& other) const { return !(*this == other); @@ -2319,6 +2311,8 @@ class ResolvedFunction { resolutionById_.swap(other.resolutionById_); poiInfo_.swap(other.poiInfo_); returnType_.swap(other.returnType_); + std::swap(poiTraceToChild_, other.poiTraceToChild_); + std::swap(sigAndInfoToChildPtr_, other.sigAndInfoToChildPtr_); } static bool update(owned& keep, owned& addin) { @@ -2329,6 +2323,28 @@ class ResolvedFunction { resolutionById_.mark(context); poiInfo_.mark(context); returnType_.mark(context); + for (auto& p : poiTraceToChild_) { + chpl::mark{}(context, p.first); + context->markPointer(p.second); + } + for (auto& p : sigAndInfoToChildPtr_) { + chpl::mark{}(context, p.first); + context->markPointer(p.second); + } + } + size_t hash() const { + // Skip 'resolutionById_' since it can be quite large. + std::ignore = resolutionById_; + size_t ret = chpl::hash(signature_, returnIntent_, poiInfo_, returnType_); + for (auto& p : poiTraceToChild_) { + ret = hash_combine(ret, chpl::hash(p.first)); + ret = hash_combine(ret, chpl::hash(p.second)); + } + for (auto& p : sigAndInfoToChildPtr_) { + ret = hash_combine(ret, chpl::hash(p.first)); + ret = hash_combine(ret, chpl::hash(p.second)); + } + return ret; } const ResolvedExpression& byId(const ID& id) const { @@ -2574,9 +2590,6 @@ class ResolvedParamLoop { } }; -/** See the documentation for types::CompositeType::SubstitutionsMap. */ -using SubstitutionsMap = types::CompositeType::SubstitutionsMap; - // Represents result info on either a type's copyability or assignability, from // ref and/or from const. struct CopyableAssignableInfo { @@ -2829,85 +2842,32 @@ template<> struct stringify namespace std { -template<> struct hash -{ - size_t operator()(const chpl::resolution::DefaultsPolicy& key) const { - return (size_t) key; - } -}; - -template<> struct hash -{ - size_t operator()(const chpl::resolution::UntypedFnSignature::FormalDetail& key) const { - return key.hash(); - } -}; - -template<> struct hash -{ - size_t operator()(const chpl::resolution::CallInfoActual& key) const { - return key.hash(); - } -}; - -template<> struct hash -{ - size_t operator()(const chpl::resolution::CallInfo& key) const { - return key.hash(); - } -}; - -template <> -struct hash { - size_t operator()( - const chpl::resolution::CandidatesAndForwardingInfo& key) const { - return key.hash(); - } -}; - -template<> struct hash -{ - size_t operator()(const chpl::resolution::PoiInfo& key) const { - return key.hash(); - } -}; - -template<> struct hash -{ - size_t operator()(const chpl::resolution::TypedFnSignature::WhereClauseResult& key) const { - return key; - } -}; - -template<> struct hash -{ - size_t operator()(const chpl::resolution::CandidateFailureReason& key) const { - return (size_t) key; - } -}; - -template<> struct hash -{ - size_t operator()(const chpl::resolution::PassingFailureReason& key) const { - return (size_t) key; - } -}; - -template<> struct hash -{ - size_t operator()(const chpl::resolution::FormalActual& key) const { - return key.hash(); - } -}; - -template<> struct hash -{ - size_t operator()(const chpl::resolution::FormalActualMap& key) const { - return key.hash(); - } -}; +// Stamp out a bunch of 'std::hash' specializations with simple bodies. +// The first arg is the type (without the namespace, and the second arg +// is the expression to return. +#define CHPL_DEFINE_STD_HASH_(T__, expr__) \ + template<> struct hash { \ + size_t operator()(const chpl::resolution::T__& key) const { \ + return (expr__); \ + } \ + } + +CHPL_DEFINE_STD_HASH_(DefaultsPolicy, ((size_t) key)); +CHPL_DEFINE_STD_HASH_(UntypedFnSignature::FormalDetail, (key.hash())); +CHPL_DEFINE_STD_HASH_(CallInfoActual, (key.hash())); +CHPL_DEFINE_STD_HASH_(CallInfo, (key.hash())); +CHPL_DEFINE_STD_HASH_(CandidatesAndForwardingInfo, (key.hash())); +CHPL_DEFINE_STD_HASH_(PoiInfo, (key.hash())); +CHPL_DEFINE_STD_HASH_(TypedFnSignature::WhereClauseResult, ((size_t) key)); +CHPL_DEFINE_STD_HASH_(CandidateFailureReason, ((size_t) key)); +CHPL_DEFINE_STD_HASH_(PassingFailureReason, ((size_t) key)); +CHPL_DEFINE_STD_HASH_(FormalActual, (key.hash())); +CHPL_DEFINE_STD_HASH_(FormalActualMap, (key.hash())); +CHPL_DEFINE_STD_HASH_(OuterVariables, (key.hash())); +CHPL_DEFINE_STD_HASH_(ApplicabilityResult, (key.hash())); +CHPL_DEFINE_STD_HASH_(ResolvedFunction, (key.hash())); +#undef CHPL_DEFINE_STD_HASH_ } // end namespace std - #endif diff --git a/frontend/include/chpl/util/hash.h b/frontend/include/chpl/util/hash.h index bbb5f23ccb37..d258f49a12fc 100644 --- a/frontend/include/chpl/util/hash.h +++ b/frontend/include/chpl/util/hash.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -109,7 +110,6 @@ static inline size_t hash(const std::tuple<>& tuple) { return 0; } -// Hash function for vector template inline size_t hashVector(const std::vector& key) { size_t ret = 0; @@ -119,7 +119,6 @@ inline size_t hashVector(const std::vector& key) { return ret; } -// Hash function for set template inline size_t hashSet(const std::set& key) { size_t ret = 0; @@ -138,7 +137,6 @@ inline size_t hashOwned(const chpl::owned& key) { return ret; } -// Hash function for pair template inline size_t hashPair(const std::pair& key) { size_t ret = 0; @@ -147,6 +145,18 @@ inline size_t hashPair(const std::pair& key) { return ret; } +template +inline size_t hashMap(const std::map& key) { + size_t ret = 0; + + // Just iterate and hash, relying on std::map being a sorted container. + for (auto& [k, v] : key) { + ret = hash_combine(ret, hash(k)); + ret = hash_combine(ret, hash(v)); + } + + return ret; +} namespace detail { @@ -162,6 +172,11 @@ template struct hasher> { return chpl::hashSet(key); } }; +template struct hasher> { + size_t operator()(const std::map& key) const { + return chpl::hashMap(key); + } +}; template struct hasher> { size_t operator()(const chpl::owned& key) const { return chpl::hashOwned(key); @@ -184,4 +199,18 @@ template struct hasher> { } // end namespace chpl +namespace std { + template struct hash> { + size_t operator()(const std::pair& key) const { + return chpl::hashPair(key); + } + }; + + template struct hash> { + size_t operator()(const std::tuple& key) const { + return chpl::hash(key); + } + }; +} // end namespace std + #endif diff --git a/frontend/lib/framework/ID.cpp b/frontend/lib/framework/ID.cpp index b8ae4dfa450b..45e29afeeb5b 100644 --- a/frontend/lib/framework/ID.cpp +++ b/frontend/lib/framework/ID.cpp @@ -20,6 +20,9 @@ #include "chpl/framework/ID.h" #include "chpl/framework/update-functions.h" +#include "chpl/resolution/scope-types.h" +#include "chpl/resolution/scope-queries.h" +#include "chpl/uast/AstTag.h" #include diff --git a/frontend/lib/libraries/LibraryFileWriter.cpp b/frontend/lib/libraries/LibraryFileWriter.cpp index 676a89d787b4..0b0dc0a850a1 100644 --- a/frontend/lib/libraries/LibraryFileWriter.cpp +++ b/frontend/lib/libraries/LibraryFileWriter.cpp @@ -21,10 +21,7 @@ #include "chpl/libraries/LibraryFileFormat.h" #include "chpl/parsing/parsing-queries.h" -#include "chpl/uast/Include.h" -#include "chpl/uast/MultiDecl.h" -#include "chpl/uast/NamedDecl.h" -#include "chpl/uast/TupleDecl.h" +#include "chpl/uast/all-uast.h" #include "chpl/util/filesystem.h" #include "chpl/util/version-info.h" diff --git a/frontend/lib/parsing/CMakeLists.txt b/frontend/lib/parsing/CMakeLists.txt index c1c9a5e81d12..c2d7f2014e3b 100644 --- a/frontend/lib/parsing/CMakeLists.txt +++ b/frontend/lib/parsing/CMakeLists.txt @@ -48,6 +48,7 @@ target_sources(ChplFrontend-obj Parser.cpp parser-error-classes-list.cpp parsing-queries.cpp + FileContents.cpp ) add_custom_target(parser diff --git a/frontend/lib/parsing/FileContents.cpp b/frontend/lib/parsing/FileContents.cpp new file mode 100644 index 000000000000..28d36f8d8bd4 --- /dev/null +++ b/frontend/lib/parsing/FileContents.cpp @@ -0,0 +1,34 @@ +/* + * Copyright 2021-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/parsing/FileContents.h" +#include "chpl/framework/ErrorBase.h" + + +namespace chpl { +namespace parsing { + + +void FileContents::mark(Context* context) const { + if (error_ != nullptr) error_->mark(context); +} + + +} // end namespace parsing +} // end namespace chpl diff --git a/frontend/lib/parsing/parsing-queries.cpp b/frontend/lib/parsing/parsing-queries.cpp index 3a5b88678637..3ed5fef434fc 100644 --- a/frontend/lib/parsing/parsing-queries.cpp +++ b/frontend/lib/parsing/parsing-queries.cpp @@ -1297,8 +1297,9 @@ bool idIsParenlessFunction(Context* context, ID id) { bool idIsNestedFunction(Context* context, ID id) { if (id.isEmpty() || !idIsFunction(context, id)) return false; - if (auto up = id.parentSymbolId(context)) { - return idIsFunction(context, up); + for (auto up = id.parentSymbolId(context); up; + up = up.parentSymbolId(context)) { + if (idIsFunction(context, up)) return true; } return false; } @@ -1453,6 +1454,16 @@ const ID& idToParentId(Context* context, ID id) { return QUERY_END(result); } +ID idToParentFunctionId(Context* context, ID id) { + if (id.isEmpty()) return {}; + for (auto up = id; up; up = up.parentSymbolId(context)) { + if (up == id) continue; + // Get the first parent function (a parent could be a record/class/etc). + if (parsing::idIsFunction(context, up)) return up; + } + return {}; +} + const uast::AstNode* parentAst(Context* context, const uast::AstNode* node) { if (node == nullptr) return nullptr; auto parentId = idToParentId(context, node->id()); diff --git a/frontend/lib/resolution/CMakeLists.txt b/frontend/lib/resolution/CMakeLists.txt index 445034c70826..0347fa9d8b15 100644 --- a/frontend/lib/resolution/CMakeLists.txt +++ b/frontend/lib/resolution/CMakeLists.txt @@ -30,6 +30,7 @@ target_sources(ChplFrontend-obj intents.cpp maybe-const.cpp prims.cpp + ResolutionContext.cpp resolution-error-classes-list.cpp resolution-queries.cpp resolution-types.cpp diff --git a/frontend/lib/resolution/InitResolver.cpp b/frontend/lib/resolution/InitResolver.cpp index 755d007efba7..b33e4237cab6 100644 --- a/frontend/lib/resolution/InitResolver.cpp +++ b/frontend/lib/resolution/InitResolver.cpp @@ -413,7 +413,8 @@ InitResolver::computeTypedSignature(const Type* newRecvType) { /* needsInstantiation */ false, tfs->instantiatedFrom(), tfs->parentFn(), - formalsInstantiated); + formalsInstantiated, + /* outerVariables */ {}); return ret; } diff --git a/frontend/lib/resolution/InitResolver.h b/frontend/lib/resolution/InitResolver.h index ab39033d1232..7eedbf11869e 100644 --- a/frontend/lib/resolution/InitResolver.h +++ b/frontend/lib/resolution/InitResolver.h @@ -20,6 +20,7 @@ #ifndef CHPL_LIB_RESOLUTION_INIT_RESOLVER_H #define CHPL_LIB_RESOLUTION_INIT_RESOLVER_H +#include "chpl/framework/ErrorBase.h" #include "chpl/parsing/parsing-queries.h" #include "chpl/resolution/resolution-queries.h" #include "chpl/resolution/resolution-types.h" diff --git a/frontend/lib/resolution/ResolutionContext.cpp b/frontend/lib/resolution/ResolutionContext.cpp new file mode 100644 index 000000000000..79935aa110e6 --- /dev/null +++ b/frontend/lib/resolution/ResolutionContext.cpp @@ -0,0 +1,119 @@ +/* + * Copyright 2021-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/resolution/ResolutionContext.h" +#include "chpl/resolution/resolution-types.h" +#include "Resolver.h" + +namespace chpl { +namespace resolution { + +const ID ResolutionContext::Frame::EMPTY_AST_ID = ID(); + +Resolver* +ResolutionContext::findParentResolverFor(const TypedFnSignature* sig) { + if (!sig || !sig->parentFn()) return nullptr; + auto fptr = findFrameMatchingMutable([=](auto& f) { + return !f.isEmpty() && f.isUnstable() && + f.signature() == sig->parentFn() && + f.rv() != nullptr; + }); + return fptr ? fptr->rv() : nullptr; +} + +bool ResolutionContext:: +canUseGlobalCache(Context* context, const UntypedFnSignature& t) { + return canUseGlobalCache(context, t.id()); +} + +bool ResolutionContext:: +canUseGlobalCache(Context* context, const TypedFnSignature& t) { + return !t.isNestedFunction(); +} + +bool ResolutionContext:: +canUseGlobalCache(Context* context, const ID& t) { + return !parsing::idIsNestedFunction(context, t); +} + +bool ResolutionContext:: +canUseGlobalCache(Context* context, const MatchingIdsWithName& ids) { + for (auto& x : ids) if (!canUseGlobalCache(context, x)) return false; + return true; +} + +const ID& ResolutionContext::Frame::id() const { + if (auto ast = rv_->symbol) return ast->id(); + return EMPTY_AST_ID; +} + +const TypedFnSignature* ResolutionContext::Frame::signature() const { + if (kind_ == ResolutionContext::Frame::FUNCTION && rv_) { + return rv_->typedSignature; + } + return nullptr; +} + +const ResolutionResultByPostorderID* +ResolutionContext::Frame::resolutionById() const { + return rv_ ? &rv_->byPostorder : nullptr; +} + +const ResolutionContext::Frame* ResolutionContext:: +pushFrame(Resolver* rv, ResolutionContext::Frame::Kind kind) { + int64_t index = (int64_t) frames_.size(); + frames_.push_back({rv, kind, index}); + auto ret = lastFrame(); + if (ret->isUnstable()) numUnstableFrames_++; + return ret; +} + +const ResolutionContext::Frame* ResolutionContext:: +pushFrame(const ResolvedFunction* rf) { + int64_t index = (int64_t) frames_.size(); + frames_.push_back({rf, index}); + auto ret = lastFrame(); + CHPL_ASSERT(!ret->isUnstable()); + return ret; +} + +bool ResolutionContext::Frame::isUnstable() const { + return rv_ != nullptr; +} + +void ResolutionContext::popFrame(Resolver* rv) { + CHPL_ASSERT(!frames_.empty() && "Frame stack underflow!"); + CHPL_ASSERT(frames_.back().rv() == rv); + + if (frames_.empty()) return; + if (frames_.back().isUnstable()) numUnstableFrames_--; + frames_.pop_back(); +} + +void ResolutionContext::popFrame(const ResolvedFunction* rf) { + CHPL_ASSERT(!frames_.empty() && "Frame stack underflow!"); + CHPL_ASSERT(frames_.back().rf() == rf); + + if (frames_.empty()) return; + CHPL_ASSERT(!frames_.back().isUnstable()); + frames_.pop_back(); +} + +} // end namespace resolution +} // end namespace chpl diff --git a/frontend/lib/resolution/Resolver.cpp b/frontend/lib/resolution/Resolver.cpp index 0d172f0d94e0..60e5e88828f7 100644 --- a/frontend/lib/resolution/Resolver.cpp +++ b/frontend/lib/resolution/Resolver.cpp @@ -115,6 +115,28 @@ static IterDetails resolveIterDetails(Resolver& rv, const QualifiedType& leaderYieldType, int mask); +Resolver::~Resolver() { + if (didPushFrame) { + CHPL_ASSERT(rc != &emptyResolutionContext); + rc->popFrame(this); + } +} + +const PoiScope* +Resolver::poiScopeOrNull(Context* context, + const TypedFnSignature* sig, + const Scope* inScope, + const PoiScope* inPoiScope) { + const PoiScope* ret = nullptr; + for (auto up = sig; up; up = up->parentFn()) { + if (up->instantiatedFrom()) { + ret = pointOfInstantiationScope(context, inScope, inPoiScope); + break; + } + } + return ret; +} + static QualifiedType::Kind qualifiedTypeKindForId(Context* context, ID id) { if (parsing::idIsParenlessFunction(context, id)) return QualifiedType::PARENLESS_FUNCTION; @@ -201,12 +223,15 @@ Resolver::ParenlessOverloadInfo::fromMatchingIds(Context* context, } Resolver -Resolver::createForModuleStmt(Context* context, const Module* mod, +Resolver::createForModuleStmt(ResolutionContext* rc, const Module* mod, const AstNode* modStmt, ResolutionResultByPostorderID& byId) { - auto ret = Resolver(context, mod, byId, nullptr); + auto ret = Resolver(rc->context(), mod, byId, nullptr); ret.curStmt = modStmt; ret.byPostorder.setupForSymbol(mod); + ret.rc = rc; + rc->pushFrame(&ret, ResolutionContext::Frame::MODULE); + ret.didPushFrame = true; return ret; } @@ -223,14 +248,15 @@ Resolver::createForScopeResolvingModuleStmt( } Resolver -Resolver::createForInitialSignature(Context* context, const Function* fn, - ResolutionResultByPostorderID& byId) -{ - auto ret = Resolver(context, fn, byId, nullptr); +Resolver::createForInitialSignature(ResolutionContext* rc, const Function* fn, + ResolutionResultByPostorderID& byId) { + auto ret = Resolver(rc->context(), fn, byId, nullptr); ret.signatureOnly = true; ret.fnBody = fn->body(); ret.byPostorder.setupForSignature(fn); + ret.rc = rc; + if (fn->isMethod()) { fn->thisFormal()->traverse(ret); auto receiverType = ret.byPostorder.byAst(fn->thisFormal()).type(); @@ -265,15 +291,20 @@ Resolver::createForInstantiatedSignature(Context* context, } Resolver -Resolver::createForFunction(Context* context, +Resolver::createForFunction(ResolutionContext* rc, const Function* fn, const PoiScope* poiScope, const TypedFnSignature* typedFnSignature, ResolutionResultByPostorderID& byId) { - auto ret = Resolver(context, fn, byId, poiScope); + auto ret = Resolver(rc->context(), fn, byId, poiScope); ret.typedSignature = typedFnSignature; ret.signatureOnly = false; ret.fnBody = fn->body(); + ret.rc = rc; + + // Push a 'ResolutionContext::Frame' for the function we are resolving. + ret.rc->pushFrame(&ret, ResolutionContext::Frame::FUNCTION); + ret.didPushFrame = true; CHPL_ASSERT(typedFnSignature); CHPL_ASSERT(typedFnSignature->untyped()); @@ -310,27 +341,25 @@ Resolver::createForFunction(Context* context, } Resolver -Resolver::createForInitializer(Context* context, +Resolver::createForInitializer(ResolutionContext* rc, const uast::Function* fn, const PoiScope* poiScope, const TypedFnSignature* typedFnSignature, ResolutionResultByPostorderID& byPostorder) { - auto ret = createForFunction(context, fn, poiScope, typedFnSignature, + auto ret = createForFunction(rc, fn, poiScope, typedFnSignature, byPostorder); - ret.initResolver = InitResolver::create(context, ret, fn); + ret.initResolver = InitResolver::create(rc->context(), ret, fn); return ret; } Resolver Resolver::createForScopeResolvingFunction(Context* context, const Function* fn, - ResolutionResultByPostorderID& byId, - owned outerVars) { + ResolutionResultByPostorderID& byId) { auto ret = Resolver(context, fn, byId, nullptr); ret.typedSignature = nullptr; // re-set below ret.signatureOnly = true; // re-set below ret.scopeResolveOnly = true; - ret.outerVars = std::move(outerVars); ret.fnBody = fn->body(); ret.byPostorder.setupForFunction(fn); @@ -351,7 +380,8 @@ Resolver::createForScopeResolvingFunction(Context* context, /* needsInstantiation */ false, /* instantiatedFrom */ nullptr, /* parentFn */ nullptr, - /* formalsInstantiated */ Bitmap()); + /* formalsInstantiated */ Bitmap(), + /* outerVariables */ ret.outerVariables); ret.typedSignature = sig; ret.signatureOnly = false; @@ -550,41 +580,91 @@ types::QualifiedType Resolver::typeErr(const uast::AstNode* ast, return t; } -static bool isOuterVariable(Resolver* rv, ID target) { - if (target.isEmpty()) return false; +static bool +isOuterVariable(Resolver& rv, const Identifier* ident, const ID& target) { + if (!target || !ident || target.isSymbolDefiningScope()) return false; + const ID& mention = ident->id(); + + Context* context = rv.context; + ID targetParentSymbolId = target.parentSymbolId(context); + ID mentionParentSymbolId = mention.parentSymbolId(context); - // E.g., a function, or a class/record/union/enum. We don't need to track - // this, and shouldn't, because its current instantiation may not make any - // sense in this context. As well, these things have an "infinite lifetime", - // and are always reachable. - if (target.isSymbolDefiningScope()) return false; + // Need both mention and target parent IDs to make a decision. + if (!targetParentSymbolId || !mentionParentSymbolId) return false; + // No match if the target's parent symbol is what we're resolving. + if (rv.symbol && targetParentSymbolId == rv.symbol->id()) return false; - auto parentSymbolId = target.parentSymbolId(rv->context); + // Ditto for the mention. + if (mentionParentSymbolId == targetParentSymbolId) return false; - // No match if there is no parent or if the parent is the resolver symbol. - if (parentSymbolId.isEmpty()) return false; - if (rv->symbol && parentSymbolId == rv->symbol->id()) return false; + auto tag = parsing::idToTag(context, targetParentSymbolId); - switch (parsing::idToTag(rv->context, parentSymbolId)) { - case asttags::Function: return true; + if (tag == asttags::Function) return true; + if (tag == asttags::Module) { // Module-scope variables are not considered outer-variables. However, // variables declared in a module initializer statement can be, e.g., /** module M { - if someCondition { + // Nested block within a module. + { var someVar = 42; proc f() { writeln(someVar); } f(); } } */ - case asttags::Module: { - auto targetParentId = parsing::idToParentId(rv->context, target); - return parentSymbolId != targetParentId; - } break; - default: break; + // Check to see if the module is not the most immediate parent AST. + auto targetParentAstId = parsing::idToParentId(context, target); + return targetParentSymbolId != targetParentAstId; + } + + if (tag == asttags::Class || tag == asttags::Record || + tag == asttags::Union) { + // In this case the mention is of a field. It could possibly be an + // outer variable, because we could be reading a field value from a + // non-local receiver. E.g., + /* + record r { + type T; + proc foo() { + // Here 'T' is read from the implicit receiver of 'foo'. + proc bar(f: T) {} + var x: T; + bar(x); + } + } + */ + ID& sym = mentionParentSymbolId; + bool isMentionInNested = parsing::idIsNestedFunction(context, sym); + bool isMentionInMethod = parsing::idIsMethod(context, sym); + bool isTargetField = parsing::idIsField(context, target); + + // Not sure what else it could be in this case... + CHPL_ASSERT(isTargetField); + + if (isTargetField) { + + // The obvious case: we are a nested non-method function. + if (isMentionInNested && !isMentionInMethod) { + return true; + + } else if (isMentionInNested) { + // TODO: Is an ID check alone sufficient? Do we need to do a + // more sophisticated check in the case of e.g., recursion or + // overlapping instantiations? + if (auto t = rv.methodReceiverType().type()) { + if (auto ct = t->toCompositeType()) { + return ct->id() != targetParentSymbolId; + } + } else if (!rv.scopeResolveOnly) { + CHPL_UNIMPL("detecting if field use in nested method is outer " + "variable without 'methodReceiverType()'"); + return false; + } + } + } } return false; @@ -1719,14 +1799,13 @@ void Resolver::handleResolvedCallPrintCandidates(ResolvedExpression& r, // The call isn't ambiguous; it might be that we rejected all the candidates // that we encountered. Re-run resolution, providing a 'rejected' vector // this time to preserve the list of rejected candidates. - std::vector rejected; - if (wasCallGenerated) { std::ignore = resolveGeneratedCall(context, call, ci, inScopes, &rejected); } else { - std::ignore = resolveCallInMethod(context, call, ci, inScopes, - receiverType, &rejected); + std::ignore = resolveCallInMethod(rc, call, ci, inScopes, + receiverType, + &rejected); } if (!rejected.empty()) { @@ -2243,6 +2322,34 @@ bool Resolver::resolveSpecialCall(const Call* call) { return false; } +static QualifiedType +lookupFieldType(Resolver& rv, const CompositeType* ct, const ID& idField) { + if (!ct || !idField) return {}; + + auto newDefaultsPolicy = rv.defaultsPolicy; + if (newDefaultsPolicy == DefaultsPolicy::USE_DEFAULTS_OTHER_FIELDS && + ct == rv.inCompositeType) { + // The USE_DEFAULTS_OTHER_FIELDS policy is supposed to make + // the Resolver act as if it was running with IGNORE_DEFAULTS + // at first, but then switch to USE_DEFAULTS for all other fields + // of the type being resolved. This branch implements the switch: + // if we're moving on to resolving another field, and if this + // field is from the current type, we resolve that field with + // USE_DEFAULTS. + newDefaultsPolicy = DefaultsPolicy::USE_DEFAULTS; + } + // if it is recursive within the current class/record, we can + // call resolveField. + auto& rf = resolveFieldDecl(rv.context, ct, idField, newDefaultsPolicy); + + // find the field that matches + for (int i = 0; i < rf.numFields(); i++) { + if (rf.fieldDeclId(i) == idField) return rf.fieldType(i); + } + + return {}; +} + QualifiedType Resolver::typeForId(const ID& id, bool localGenericToUnknown) { if (scopeResolveOnly) { auto kind = qualifiedTypeKindForId(context, id); @@ -2352,10 +2459,8 @@ QualifiedType Resolver::typeForId(const ID& id, bool localGenericToUnknown) { // Figure out what ID is contained within so we can use the // appropriate query. ID parentId = id.parentSymbolId(context); - auto parentTag = asttags::AST_TAG_UNKNOWN; - if (!parentId.isEmpty()) { - parentTag = parsing::idToTag(context, parentId); - } + auto parentTag = parentId ? parsing::idToTag(context, parentId) + : asttags::AST_TAG_UNKNOWN; if (asttags::isModule(parentTag)) { // If the id is contained within a module, use typeForModuleLevelSymbol. @@ -2397,33 +2502,7 @@ QualifiedType Resolver::typeForId(const ID& id, bool localGenericToUnknown) { } } - if (ct) { - auto newDefaultsPolicy = defaultsPolicy; - if (defaultsPolicy == DefaultsPolicy::USE_DEFAULTS_OTHER_FIELDS && - ct == inCompositeType) { - // The USE_DEFAULTS_OTHER_FIELDS policy is supposed to make - // the Resolver act as if it was running with IGNORE_DEFAULTS - // at first, but then switch to USE_DEFAULTS for all other fields - // of the type being resolved. This branch implements the switch: - // if we're moving on to resolving another field, and if this - // field is from the current type, we resolve that field with - // USE_DEFAULTS. - newDefaultsPolicy = DefaultsPolicy::USE_DEFAULTS; - } - // if it is recursive within the current class/record, we can - // call resolveField. - const ResolvedFields& resolvedFields = - resolveFieldDecl(context, ct, id, newDefaultsPolicy); - // find the field that matches - int nFields = resolvedFields.numFields(); - for (int i = 0; i < nFields; i++) { - if (resolvedFields.fieldDeclId(i) == id) { - return resolvedFields.fieldType(i); - } - } - - CHPL_ASSERT(false && "could not find resolved field"); - } + if (ct) return lookupFieldType(*this, ct, id); // Otherwise it is a case not handled yet // TODO: handle outer function variables @@ -2768,65 +2847,98 @@ MatchingIdsWithName Resolver::lookupIdentifier( return m; } -void Resolver::validateAndSetToId(ResolvedExpression& r, - const AstNode* node, - const ID& toId) { - r.setToId(toId); - if (toId.isEmpty()) return; - if (toId.isFabricatedId()) return; - - // Validate the newly set to ID. - auto idTag = parsing::idToTag(context, toId); +static bool +checkForErrorModuleAsVariable(Context* context, const AstNode* node, + const ID& target) { + auto targetTag = parsing::idToTag(context, target); // It shouldn't refer to a module unless the node is an identifier in one of // the places where module references are allowed (e.g. imports). - if (asttags::isModule(idTag)) { - auto parentId = parsing::idToParentId(context, node->id()); - if (!parentId.isEmpty()) { - auto parentTag = parsing::idToTag(context, parentId); - if (asttags::isUse(parentTag) || asttags::isImport(parentTag) || - asttags::isAs(parentTag) || asttags::isVisibilityClause(parentTag) || - asttags::isDot(parentTag)) { + if (asttags::isModule(targetTag)) { + if (auto nodeParentId = parsing::idToParentId(context, node->id())) { + auto nodeParentTag = parsing::idToTag(context, nodeParentId); + if (asttags::isUse(nodeParentTag) || + asttags::isImport(nodeParentTag) || + asttags::isAs(nodeParentTag) || + asttags::isVisibilityClause(nodeParentTag) || + asttags::isDot(nodeParentTag)) { // OK } else { - auto toAst = parsing::idToAst(context, toId); - auto mod = toAst->toModule(); - auto parentAst = parsing::idToAst(context, parentId); - CHPL_REPORT(context, ModuleAsVariable, node, parentAst, mod); - r.setToId(ID()); // clear + auto targetAst = parsing::idToAst(context, target); + auto mod = targetAst->toModule(); + auto nodeParentAst = parsing::idToAst(context, nodeParentId); + CHPL_REPORT(context, ModuleAsVariable, node, nodeParentAst, mod); + return true; } } } + return false; +} + +static bool +checkForErrorNestedClassFieldRef(Context* context, const AstNode* node, + const ID& target) { + auto targetTag = parsing::idToTag(context, target); // If we're in a nested class, it shouldn't refer to an outer class' field. - auto parentId = - Builder::astTagIndicatesNewIdScope(idTag) ? toId : toId.parentSymbolId(context); - auto parentTag = parsing::idToTag(context, parentId); - if (asttags::isAggregateDecl(parentTag) && - parentId.contains(node->id()) && - parentId != toId /* It's okay to refer to the record itself */) { + auto targetParentId = !Builder::astTagIndicatesNewIdScope(targetTag) + ? target.parentSymbolId(context) + : target; + auto targetParentTag = parsing::idToTag(context, targetParentId); + + if (asttags::isAggregateDecl(targetParentTag) && + targetParentId.contains(node->id()) && + /* It's okay to refer to the record itself */ + targetParentId != target) { + // Referring to a field of a class that's surrounding the current node. // Loop upwards looking for a composite type. auto searchId = node->id().parentSymbolId(context); - while (!searchId.isEmpty()) { - if (searchId == parentId) { + while (searchId) { + if (searchId == targetParentId) { // We found the aggregate type in which the to-ID is declared, // so there's no nested class issues. break; } else if (asttags::isTypeDecl(parsing::idToTag(context, searchId))) { - auto parentAst = parsing::idToAst(context, parentId); + auto targetParentAst = parsing::idToAst(context, targetParentId); auto searchAst = parsing::idToAst(context, searchId); auto searchAD = searchAst->toTypeDecl(); // It's an error! - CHPL_REPORT(context, NestedClassFieldRef, parentAst->toTypeDecl(), - searchAD, node, toId); - break; + CHPL_REPORT(context, NestedClassFieldRef, + targetParentAst->toTypeDecl(), + searchAD, node, target); + return true; } // Move on to the surrounding ID. searchId = searchId.parentSymbolId(context); } } + return false; +} + +static const bool& +checkForIdentifierTargetErrorsQuery(Context* context, ID nodeId, ID targetId) { + QUERY_BEGIN(checkForIdentifierTargetErrorsQuery, context, nodeId, targetId); + bool ret = false; + + auto nodeAst = parsing::idToAst(context, nodeId); + + // Use bitwise-OR here to avoid short-circuiting. + ret |= checkForErrorModuleAsVariable(context, nodeAst, targetId); + ret |= checkForErrorNestedClassFieldRef(context, nodeAst, targetId); + + return QUERY_END(ret); +} + +void Resolver::validateAndSetToId(ResolvedExpression& r, + const AstNode* node, + const ID& toId) { + r.setToId(toId); + if (!toId || toId.isFabricatedId()) return; + auto error = checkForIdentifierTargetErrorsQuery(context, node->id(), toId); + // If there was an error, clear the target to prevent false lookups. + if (error) r.setToId(ID()); } void Resolver::validateAndSetMostSpecific(ResolvedExpression& r, @@ -2983,6 +3095,54 @@ void Resolver::tryResolveParenlessCall(const ParenlessOverloadInfo& info, } } +bool Resolver::lookupOuterVariable(QualifiedType& out, + const Identifier* ident, + const ID& target) { + if (!isOuterVariable(*this, ident, target)) return false; + auto& mention = ident->id(); + + QualifiedType type; + bool isFieldAccess = parsing::idIsField(context, target); + + // Use the cached result if it exists. + if (auto p = outerVariables.targetAndTypeOrNull(mention)) { + type = p->second; + + } else if (isFieldAccess) { + // If the target ID is a field, we have to walk up parent frames + // until we find a method with a matching receiver, and then use it + // to look up the field's type. + for (auto f = rc->lastFrame(); f; f = f->parent(rc)) { + auto sig = f->signature(); + if (!sig || !sig->isMethod()) continue; + + auto t = sig->formalType(0).type(); + auto ct = t ? t->toCompositeType() : nullptr; + + // TODO: What if an ID check alone is not sufficient? If I cannot + // lookup using field ID alone, then I cannot cache with field ID. + if (ct && ct->id() == target.parentSymbolId(context)) { + type = lookupFieldType(*this, ct, target); + outerVariables.add(mention, target, type); + break; + } + } + + // Otherwise, it's a variable, so walk up parent frames and look up + // the variable's type using the resolution results. + } else if (ID parentFn = parsing::idToParentFunctionId(context, target)) { + if (auto f = rc->findFrameWithId(target)) { + type = f->resolutionById()->byId(target).type(); + outerVariables.add(mention, target, type); + } + } + + // Write out the type we found (or an unset type, potentially). + out = type; + + return true; +} + void Resolver::resolveIdentifier(const Identifier* ident) { ResolvedExpression& result = byPostorder.byAst(ident); @@ -3113,19 +3273,17 @@ void Resolver::resolveIdentifier(const Identifier* ident) { return; } - // use the type established at declaration/initialization, - // but for things with generic type, use unknown. - type = typeForId(id, /*localGenericToUnknown*/ true); + // Attempt to lookup an outer variable. + if (!lookupOuterVariable(type, ident, id)) { - maybeEmitWarningsForId(this, type, ident, id); - - // Record uses of outer variables. - if (isOuterVariable(this, id) && outerVars) { - const ID& mention = ident->id(); - const ID& var = id; - outerVars->add(context, mention, var); + // Otherwise, use the type established at declaration/initialization, + // but for things with generic type, use unknown. + bool localGenericToUnknown = true; + type = typeForId(id, localGenericToUnknown); } + maybeEmitWarningsForId(this, type, ident, id); + if (type.kind() == QualifiedType::TYPE) { // now, for a type that is generic with defaults, // compute the default version when needed. e.g. @@ -3342,33 +3500,9 @@ void Resolver::exit(const NamedDecl* decl) { genericReceiverOverrideStack.pop_back(); } - // We are resolving a symbol with a different path (e.g., a Function or - // a CompositeType declaration). In most cases we do not try to resolve - // in this traversal. However, if we are a nested function and the child - // is also a nested function, we need to check and potentially propagate - // their outer variable set into our own. + // Stop resolving if the declaration introduces a new path component. auto idChild = decl->id(); if (idChild.isSymbolDefiningScope()) { - if (this->symbol != nullptr && - parsing::idIsNestedFunction(context, this->symbol->id()) && - parsing::idIsNestedFunction(context, idChild) && - outerVars.get()) { - if (auto ovs = computeOuterVariables(context, idChild)) { - for (int i = 0; i < ovs->numVariables(); i++) { - - // If the variable is reaching in the child function, it means it - // was defined in one of _our_ parent(s). So we need to track it. - if (ovs->isReachingVariable(i)) { - ID var = ovs->variable(i); - - // Mentions from child functions are not recorded in the parent - // function's info, so just use the first (as a convenience). - ID mention = ovs->firstMention(var); - outerVars->add(context, mention, var); - } - } - } - } return; } @@ -3669,6 +3803,89 @@ static const Type* getGenericType(Context* context, const Type* recv) { return gen; } +static SkipCallResolutionReason +shouldSkipCallResolution(Resolver* rv, const uast::Call* call, + std::vector actualAsts, + ID moduleScopeId, + const CallInfo& ci) { + Context* context = rv->context; + SkipCallResolutionReason skip = NONE; + auto& byPostorder = rv->byPostorder; + + if (call->isTuple()) return skip; + + int actualIdx = 0; + for (const auto& actual : ci.actuals()) { + ID toId; // does the actual refer directly to a particular variable? + const AstNode* actualAst = actualAsts[actualIdx]; + if (actualAst != nullptr && byPostorder.hasAst(actualAst)) { + toId = byPostorder.byAst(actualAst).toId(); + } + QualifiedType qt = actual.type(); + const Type* t = qt.type(); + + auto formalAst = toId.isEmpty() ? nullptr : parsing::idToAst(context, toId); + bool isNonOutFormal = formalAst != nullptr && + formalAst->isFormal() && + formalAst->toFormal()->intent() != Formal::Intent::OUT; + + if (t != nullptr && t->isErroneousType()) { + // always skip if there is an ErroneousType + skip = ERRONEOUS_ACT; + } else if (!toId.isEmpty() && !isNonOutFormal && + qt.kind() != QualifiedType::PARAM && + qt.kind() != QualifiedType::TYPE && + qt.isRef() == false) { + // don't skip because it could be initialized with 'out' intent, + // but not for non-out formals because they can't be split-initialized. + } else if (actualAst->isTypeQuery() && ci.calledType().isType()) { + // don't skip for type queries in type constructors + } else { + if (qt.isParam() && qt.param() == nullptr) { + skip = UNKNOWN_PARAM; + } else if (qt.isUnknown()) { + skip = UNKNOWN_ACT; + } else if (t != nullptr && qt.kind() != QualifiedType::INIT_RECEIVER) { + // 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())); + if (qt.isType() && isBuiltinGeneric && rv->substitutions == nullptr) { + skip = GENERIC_TYPE; + } else if (!qt.isType() && g != Type::CONCRETE) { + skip = GENERIC_VALUE; + } + } + } + + // Don't skip for type constructors, except due to unknown params. + if (skip != UNKNOWN_PARAM && ci.calledType().isType()) { + skip = NONE; + } + + // Do not skip primitive calls that accept a generic type, since they + // may be valid. + if (skip == GENERIC_TYPE && call->toPrimCall()) { + skip = NONE; + } + + if (skip) { + break; + } + actualIdx++; + } + + // Don't try to resolve calls to '=' until later + if (ci.isOpCall() && ci.name() == USTR("=")) { + skip = OTHER_REASON; + } + + return skip; +} + void Resolver::handleCallExpr(const uast::Call* call) { if (scopeResolveOnly) { return; @@ -3704,83 +3921,9 @@ void Resolver::handleCallExpr(const uast::Call* call) { CallScopeInfo::forNormalCall(scope, poiScope) : CallScopeInfo::forQualifiedCall(context, moduleScopeId, scope, poiScope); - // With some exceptions (see below), don't try to resolve a call that accepts: - SkipCallResolutionReason skip = NONE; - // EXCEPT, to handle split-init with an 'out' formal, - // the actual argument can have unknown / generic type if it - // refers directly to a particular variable. - // EXCEPT, type construction can work with unknown or generic types - // EXCEPT, tuple type construction also works with unknown/generic types - - if (!call->isTuple()) { - int actualIdx = 0; - for (const auto& actual : ci.actuals()) { - ID toId; // does the actual refer directly to a particular variable? - const AstNode* actualAst = actualAsts[actualIdx]; - if (actualAst != nullptr && byPostorder.hasAst(actualAst)) { - toId = byPostorder.byAst(actualAst).toId(); - } - QualifiedType qt = actual.type(); - const Type* t = qt.type(); - - const AstNode* formalAst = toId.isEmpty() ? nullptr : parsing::idToAst(context, toId); - bool isNonOutFormal = formalAst != nullptr && - formalAst->isFormal() && - formalAst->toFormal()->intent() != Formal::Intent::OUT; - - if (t != nullptr && t->isErroneousType()) { - // always skip if there is an ErroneousType - skip = ERRONEOUS_ACT; - } else if (!toId.isEmpty() && !isNonOutFormal && - qt.kind() != QualifiedType::PARAM && - qt.kind() != QualifiedType::TYPE && - qt.isRef() == false) { - // don't skip because it could be initialized with 'out' intent, - // but not for non-out formals because they can't be split-initialized. - } else if (actualAst->isTypeQuery() && ci.calledType().isType()) { - // don't skip for type queries in type constructors - } else { - if (qt.isParam() && qt.param() == nullptr) { - skip = UNKNOWN_PARAM; - } else if (qt.isUnknown()) { - skip = UNKNOWN_ACT; - } else if (t != nullptr && qt.kind() != QualifiedType::INIT_RECEIVER) { - // 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())); - if (qt.isType() && isBuiltinGeneric && substitutions == nullptr) { - skip = GENERIC_TYPE; - } else if (!qt.isType() && g != Type::CONCRETE) { - skip = GENERIC_VALUE; - } - } - } - - // Don't skip for type constructors, except due to unknown params. - if (skip != UNKNOWN_PARAM && ci.calledType().isType()) { - skip = NONE; - } - - // Do not skip primitive calls that accept a generic type, since they - // may be valid. - if (skip == GENERIC_TYPE && call->toPrimCall()) { - skip = NONE; - } - - if (skip) { - break; - } - actualIdx++; - } - } - // Don't try to resolve calls to '=' until later - if (ci.isOpCall() && ci.name() == USTR("=")) { - skip = OTHER_REASON; - } + auto skip = shouldSkipCallResolution(this, call, actualAsts, + moduleScopeId, + ci); if (!skip) { QualifiedType receiverType = methodReceiverType(); @@ -3802,8 +3945,10 @@ void Resolver::handleCallExpr(const uast::Call* call) { receiverType = QualifiedType(QualifiedType::INIT_RECEIVER, gen); } - CallResolutionResult c - = resolveCallInMethod(context, call, ci, inScopes, receiverType); + std::vector* rejected = nullptr; + auto c = resolveCallInMethod(rc, call, ci, inScopes, + receiverType, + rejected); // save the most specific candidates in the resolution result for the id ResolvedExpression& r = byPostorder.byAst(call); @@ -3955,7 +4100,7 @@ void Resolver::exit(const Dot* dot) { } if (dot->field() == USTR("type")) { - const Type* receiverType; + const Type* receiverType = nullptr; ResolvedExpression& r = byPostorder.byAst(dot); if (receiver.type().type() != nullptr) { diff --git a/frontend/lib/resolution/Resolver.h b/frontend/lib/resolution/Resolver.h index ba51fada99b8..917f2d325e95 100644 --- a/frontend/lib/resolution/Resolver.h +++ b/frontend/lib/resolution/Resolver.h @@ -20,7 +20,9 @@ #ifndef CHPL_LIB_RESOLUTION_RESOLVER_H #define CHPL_LIB_RESOLUTION_RESOLVER_H +#include "chpl/framework/ErrorBase.h" #include "chpl/resolution/resolution-types.h" +#include "chpl/types/CompositeType.h" #include "chpl/uast/all-uast.h" #include "InitResolver.h" @@ -33,6 +35,8 @@ namespace resolution { struct Resolver { // types used below using ActionAndId = std::tuple; + using SubstitutionsMap = types::CompositeType::SubstitutionsMap; + using ReceiverScopesVec = SimpleMethodLookupHelper::ReceiverScopesVec; /** When looking up matches for a particular identifier, we might encounter @@ -84,6 +88,9 @@ struct Resolver { bool skipTypeQueries = false; // internal variables + ResolutionContext emptyResolutionContext; + ResolutionContext* rc = &emptyResolutionContext; + bool didPushFrame = false; std::vector declStack; std::vector scopeStack; std::vector tagTracker; @@ -108,10 +115,18 @@ struct Resolver { Resolver* parentResolver = nullptr; owned initResolver = nullptr; - owned outerVars; // results of the resolution process + // Map from outer variable mention to type and target ID. + OuterVariables outerVariables; + + // Storage for child functions resolved within this function. This models + // the generic cache implemented in the 'resolveFunctionByPois' and + // 'resolveFunctionByInfo' functions, but using resolver state. + ResolvedFunction::PoiTraceToChildMap poiTraceToChild; + ResolvedFunction::SigAndInfoToChildPtrMap sigAndInfoToChildPtr; + // the resolution results for the contained AstNodes ResolutionResultByPostorderID& byPostorder; @@ -135,19 +150,27 @@ struct Resolver { const uast::AstNode* symbol, ResolutionResultByPostorderID& byPostorder, const PoiScope* poiScope) - : context(context), symbol(symbol), + : context(context), + symbol(symbol), poiScope(poiScope), + emptyResolutionContext(context), byPostorder(byPostorder), poiInfo(makePoiInfo(poiScope)) { - tagTracker.resize(uast::asttags::AstTag::NUM_AST_TAGS); enterScope(symbol); } public: + // Explicitly disable the copy constructor, since a lot of state in a + // resolver can't/shouldn't be copied, and this will ensure that never + // happens. + Resolver(const Resolver& rhs) = delete; + Resolver& operator=(const Resolver& rhs) = delete; + Resolver(Resolver&& rhs) = default; + ~Resolver(); // set up Resolver to resolve a Module static Resolver - createForModuleStmt(Context* context, const uast::Module* mod, + createForModuleStmt(ResolutionContext* rc, const uast::Module* mod, const uast::AstNode* modStmt, ResolutionResultByPostorderID& byPostorder); @@ -160,7 +183,8 @@ struct Resolver { // set up Resolver to resolve a potentially generic Function signature static Resolver - createForInitialSignature(Context* context, const uast::Function* fn, + createForInitialSignature(ResolutionContext* rc, + const uast::Function* fn, ResolutionResultByPostorderID& byPostorder); // set up Resolver to resolve an instantiation of a Function signature @@ -174,14 +198,14 @@ struct Resolver { // set up Resolver to resolve a Function body/return type after // instantiation (if any instantiation was needed) static Resolver - createForFunction(Context* context, + createForFunction(ResolutionContext* rc, const uast::Function* fn, const PoiScope* poiScope, const TypedFnSignature* typedFnSignature, ResolutionResultByPostorderID& byPostorder); static Resolver - createForInitializer(Context* context, + createForInitializer(ResolutionContext* rc, const uast::Function* fn, const PoiScope* poiScope, const TypedFnSignature* typedFnSignature, @@ -190,8 +214,7 @@ struct Resolver { // set up Resolver to scope resolve a Function static Resolver createForScopeResolvingFunction(Context* context, const uast::Function* fn, - ResolutionResultByPostorderID& byPostorder, - owned outerVars); + ResolutionResultByPostorderID& byPostorder); static Resolver createForScopeResolvingField(Context* context, const uast::AggregateDecl* ad, @@ -256,6 +279,12 @@ struct Resolver { const uast::For* loop, ResolutionResultByPostorderID& bodyResults); + static const PoiScope* + poiScopeOrNull(Context* context, + const TypedFnSignature* sig, + const Scope* inScope, + const PoiScope* inPoiScope); + /** During AST traversal, find the last called expression we entered. e.g., will return 'f' if we just entered 'f()'. @@ -552,6 +581,11 @@ struct Resolver { void resolveIdentifier(const uast::Identifier* ident); + /** Returns 'true' and sets 'out' if an outer var was found for 'target'. */ + bool lookupOuterVariable(types::QualifiedType& out, + const uast::Identifier* ident, + const ID& target); + /* Resolver keeps a stack of scopes and a stack of decls. enterScope and exitScope update those stacks. */ void enterScope(const uast::AstNode* ast); diff --git a/frontend/lib/resolution/VarScopeVisitor.cpp b/frontend/lib/resolution/VarScopeVisitor.cpp index 6b6926c6b6b5..b0d9d49b2c69 100644 --- a/frontend/lib/resolution/VarScopeVisitor.cpp +++ b/frontend/lib/resolution/VarScopeVisitor.cpp @@ -46,7 +46,8 @@ bool VarFrame::addToInitedVars(ID varId) { void VarScopeVisitor::process(const uast::AstNode* symbol, ResolutionResultByPostorderID& byPostorder) { - MutatingResolvedVisitor rv(context, + ResolutionContext rcval(context); + MutatingResolvedVisitor rv(&rcval, symbol, *this, byPostorder); diff --git a/frontend/lib/resolution/default-functions.cpp b/frontend/lib/resolution/default-functions.cpp index 90e361d60ad3..303419d36b68 100644 --- a/frontend/lib/resolution/default-functions.cpp +++ b/frontend/lib/resolution/default-functions.cpp @@ -96,7 +96,8 @@ areOverloadsPresentInDefiningScope(Context* context, if (auto fn = node->toFunction()) { if (fn->isMethod() || fn->kind() == Function::Kind::OPERATOR) { ResolutionResultByPostorderID r; - auto vis = Resolver::createForInitialSignature(context, fn, r); + ResolutionContext rcval(context); + auto vis = Resolver::createForInitialSignature(&rcval, fn, r); // use receiver for method, first formal for standalone operator auto checkFormal = (fn->isMethod() ? fn->thisFormal() : fn->formal(0)); @@ -332,7 +333,8 @@ generateInitSignature(Context* context, const CompositeType* inCompType) { needsInstantiation, /* instantiatedFrom */ nullptr, /* parentFn */ nullptr, - /* formalsInstantiated */ Bitmap()); + /* formalsInstantiated */ Bitmap(), + /* outerVariables */ {}); return ret; } @@ -380,7 +382,8 @@ generateInitCopySignature(Context* context, const CompositeType* inCompType) { /*needsInstantiation*/ false, /* instantiatedFrom */ nullptr, /* parentFn */ nullptr, - /* formalsInstantiated */ Bitmap()); + /* formalsInstantiated */ Bitmap(), + /* outerVariables */ {}); return ret; } @@ -415,7 +418,8 @@ generateDeinitSignature(Context* context, const CompositeType* inCompType) { /*needsInstantiation*/ false, /* instantiatedFrom */ nullptr, /* parentFn */ nullptr, - /* formalsInstantiated */ Bitmap()); + /* formalsInstantiated */ Bitmap(), + /* outerVariables */ {}); return ret; } @@ -479,7 +483,8 @@ generateDeSerialize(Context* context, const CompositeType* compType, /*needsInstantiation*/ false, /* instantiatedFrom */ nullptr, /* parentFn */ nullptr, - /* formalsInstantiated */ Bitmap()); + /* formalsInstantiated */ Bitmap(), + /* outerVariables */ {}); return ret; } @@ -519,7 +524,8 @@ generateDomainMethod(Context* context, /* needsInstantiation */ false, /* instantiatedFrom */ nullptr, /* parentFn */ nullptr, - /* formalsInstantiated */ Bitmap()); + /* formalsInstantiated */ Bitmap(), + /* outerVariables */ {}); return result; } @@ -558,7 +564,8 @@ generateArrayMethod(Context* context, /* needsInstantiation */ false, /* instantiatedFrom */ nullptr, /* parentFn */ nullptr, - /* formalsInstantiated */ Bitmap()); + /* formalsInstantiated */ Bitmap(), + /* outerVariables */ {}); return result; } @@ -596,7 +603,8 @@ generateTupleMethod(Context* context, /* needsInstantiation */ false, /* instantiatedFrom */ nullptr, /* parentFn */ nullptr, - /* formalsInstantiated */ Bitmap()); + /* formalsInstantiated */ Bitmap(), + /* outerVariables */ {}); return result; } @@ -649,7 +657,8 @@ fieldAccessorQuery(Context* context, /* needsInstantiation */ false, /* instantiatedFrom */ nullptr, /* parentFn */ nullptr, - /* formalsInstantiated */ Bitmap()); + /* formalsInstantiated */ Bitmap(), + /* outerVariables */ {}); return QUERY_END(result); } @@ -781,7 +790,8 @@ generateRecordBinaryOperator(Context* context, UniqueString op, needsInstantiation, /* instantiatedFrom */ nullptr, /* parentFn */ nullptr, - /* formalsInstantiated */ Bitmap()); + /* formalsInstantiated */ Bitmap(), + /* outerVariables */ {}); return ret; } @@ -839,7 +849,8 @@ generateCPtrMethod(Context* context, QualifiedType receiverType, /* needsInstantiation */ false, /* instantiatedFrom */ nullptr, /* parentFn */ nullptr, - /* formalsInstantiated */ Bitmap()); + /* formalsInstantiated */ Bitmap(), + /* outerVariables */ {}); return result; } @@ -879,7 +890,8 @@ generateEnumMethod(Context* context, /* needsInstantiation */ false, /* instantiatedFrom */ nullptr, /* parentFn */ nullptr, - /* formalsInstantiated */ Bitmap()); + /* formalsInstantiated */ Bitmap(), + /* outerVariables */ {}); } return result; @@ -1003,7 +1015,8 @@ generateToOrFromCastForEnum(Context* context, /* needsInstantiation */ true, /* instantiatedFrom */ nullptr, /* parentFn */ nullptr, - /* formalsInstantiated */ Bitmap()); + /* formalsInstantiated */ Bitmap(), + /* outerVariables */ {}); return ret; } @@ -1084,7 +1097,8 @@ getOrderToEnumFunction(Context* context, bool paramVersion, const EnumType* et) /* needsInstantiation */ true, /* instantiatedFrom */ nullptr, /* parentFn */ nullptr, - /* formalsInstantiated */ Bitmap()); + /* formalsInstantiated */ Bitmap(), + /* outerVariables */ {}); return QUERY_END(ret); } @@ -1125,7 +1139,8 @@ getEnumToOrderFunction(Context* context, bool paramVersion, const EnumType* et) /* needsInstantiation */ paramVersion, /* instantiatedFrom */ nullptr, /* parentFn */ nullptr, - /* formalsInstantiated */ Bitmap()); + /* formalsInstantiated */ Bitmap(), + /* outerVariables */ {}); return QUERY_END(ret); } diff --git a/frontend/lib/resolution/maybe-const.cpp b/frontend/lib/resolution/maybe-const.cpp index 5467df5a6b76..b90f99197d98 100644 --- a/frontend/lib/resolution/maybe-const.cpp +++ b/frontend/lib/resolution/maybe-const.cpp @@ -66,7 +66,8 @@ struct AdjustMaybeRefs { }; // inputs to the process - Context* context = nullptr; + ResolutionContext* rc = nullptr; + Context* context = rc ? rc->context() : nullptr; Resolver& resolver; // state @@ -75,8 +76,8 @@ struct AdjustMaybeRefs { std::vector exprStack; // methods - AdjustMaybeRefs(Context* context, Resolver& resolver) - : context(context), resolver(resolver) + AdjustMaybeRefs(ResolutionContext* rc, Resolver& resolver) + : rc(rc), resolver(resolver) { } void process(const uast::AstNode* symbol, @@ -105,7 +106,7 @@ struct AdjustMaybeRefs { void AdjustMaybeRefs::process(const uast::AstNode* symbol, ResolutionResultByPostorderID& byPostorder) { - MutatingResolvedVisitor rv(context, + MutatingResolvedVisitor rv(rc, symbol, *this, byPostorder); @@ -285,7 +286,7 @@ bool AdjustMaybeRefs::enter(const Call* ast, RV& rv) { // recompute the return type // (all that actually needs to change is the return intent) - re.setType(returnType(context, best.fn(), resolver.poiScope)); + re.setType(returnType(rc, best.fn(), resolver.poiScope)); } // there should be only one candidate at this point @@ -294,7 +295,13 @@ bool AdjustMaybeRefs::enter(const Call* ast, RV& rv) { // then, traverse nested call-expressions if (auto msc = candidates.only()) { auto fn = msc.fn(); - auto resolvedFn = inferRefMaybeConstFormals(context, fn, resolver.poiScope); + + // Recompute the instantiation scope that was used when resolving the call. + auto inScope = scopeForId(context, ast->id()); + auto inPoiScope = resolver.poiScope; + auto poiScope = Resolver::poiScopeOrNull(context, fn, inScope, inPoiScope); + + auto resolvedFn = inferRefMaybeConstFormals(rc, fn, poiScope); if (resolvedFn) { fn = resolvedFn; // use the version with ref-maybe-const formals, but @@ -373,7 +380,7 @@ void AdjustMaybeRefs::exit(const uast::AstNode* node, RV& rv) { } void adjustReturnIntentOverloadsAndMaybeConstRefs(Resolver& resolver) { - AdjustMaybeRefs uv(resolver.context, resolver); + AdjustMaybeRefs uv(resolver.rc, resolver); uv.process(resolver.symbol, resolver.byPostorder); } diff --git a/frontend/lib/resolution/prims.cpp b/frontend/lib/resolution/prims.cpp index 344751142cab..72b59f5df1a6 100644 --- a/frontend/lib/resolution/prims.cpp +++ b/frontend/lib/resolution/prims.cpp @@ -223,10 +223,12 @@ static QualifiedType primFieldByNum(Context* context, const CallInfo& ci) { return fields->fieldType(fieldNum - 1);; } -static QualifiedType primCallResolves(Context* context, const CallInfo &ci, +static QualifiedType primCallResolves(ResolutionContext* rc, + const CallInfo &ci, bool forMethod, bool resolveFn, const PrimCall* call, const CallScopeInfo& inScopes) { + Context* context = rc->context(); if ((forMethod && ci.numActuals() < 2) || (!forMethod && ci.numActuals() < 1)) { return QualifiedType(); @@ -270,7 +272,7 @@ static QualifiedType primCallResolves(Context* context, const CallInfo &ci, if (resolveFn) { // We did find a candidate; resolve the function body. auto bodyResult = context->runAndTrackErrors([&](Context* context) { - return resolveFunction(context, bestCandidate, inScopes.poiScope()); + return resolveFunction(rc, bestCandidate, inScopes.poiScope()); }); callAndFnResolved &= bodyResult.ranWithoutErrors(); } @@ -465,8 +467,9 @@ struct TestFunctionFinder { if (parsing::idIsInBundledModule(context, fn->id())) return false; // The function also needs to be concrete. + ResolutionContext rcval(context); const UntypedFnSignature* uSig = UntypedFnSignature::get(context, fn); - const TypedFnSignature* sig = typedSignatureInitial(context, uSig); + const TypedFnSignature* sig = typedSignatureInitial(&rcval, uSig); if (sig == nullptr || sig->needsInstantiation()) return false; if (canPass(context, testType, sig->formalType(0)).passes()) { @@ -1087,11 +1090,12 @@ primSimpleTypeName(Context* context, const CallInfo& ci) { return makeParamString(context, eval); } -CallResolutionResult resolvePrimCall(Context* context, +CallResolutionResult resolvePrimCall(ResolutionContext* rc, const PrimCall* call, const CallInfo& ci, const Scope* inScope, const PoiScope* inPoiScope) { + Context* context = rc->context(); bool allParam = true; for (const CallInfoActual& actual : ci.actuals()) { if (!actual.type().hasParamPtr()) { @@ -1249,7 +1253,7 @@ CallResolutionResult resolvePrimCall(Context* context, prim == PRIM_METHOD_CALL_AND_FN_RESOLVES; auto inScopes = CallScopeInfo::forNormalCall(inScope, inPoiScope); - type = primCallResolves(context, ci, forMethod, resolveFn, call, inScopes); + type = primCallResolves(rc, ci, forMethod, resolveFn, call, inScopes); } break; diff --git a/frontend/lib/resolution/prims.h b/frontend/lib/resolution/prims.h index da22b3b3e0f6..a9bf8be0dc0f 100644 --- a/frontend/lib/resolution/prims.h +++ b/frontend/lib/resolution/prims.h @@ -27,8 +27,7 @@ namespace chpl { namespace resolution { - -CallResolutionResult resolvePrimCall(Context* context, +CallResolutionResult resolvePrimCall(ResolutionContext* rc, const uast::PrimCall* call, const CallInfo& ci, const Scope* inScope, diff --git a/frontend/lib/resolution/resolution-queries.cpp b/frontend/lib/resolution/resolution-queries.cpp index ef9bafebd699..6d644df0519b 100644 --- a/frontend/lib/resolution/resolution-queries.cpp +++ b/frontend/lib/resolution/resolution-queries.cpp @@ -51,6 +51,46 @@ #include #include +namespace { + struct EvaluatedCandidates { + chpl::resolution::CandidatesAndForwardingInfo matching; + std::vector rejected; + bool evaluatedAnyNestedFunction = false; + + bool operator==(const EvaluatedCandidates& rhs) const { + return this->matching == rhs.matching && + this->rejected == rhs.rejected && + this->evaluatedAnyNestedFunction == rhs.evaluatedAnyNestedFunction; + } + bool operator!=(const EvaluatedCandidates& rhs) const { + return !(*this == rhs); + } + void swap(EvaluatedCandidates& rhs) { + matching.swap(rhs.matching); + std::swap(rejected, rhs.rejected); + std::swap(evaluatedAnyNestedFunction, rhs.evaluatedAnyNestedFunction); + } + static bool update(EvaluatedCandidates& keep, EvaluatedCandidates& addin) { + return chpl::defaultUpdate(keep, addin); + } + void mark(chpl::Context* context) const { + matching.mark(context); + chpl::mark{}(context, rejected); + } + size_t hash() const { + return chpl::hash(matching, rejected, evaluatedAnyNestedFunction); + } + }; +} + +namespace std { + template <> struct hash { + size_t operator()(const EvaluatedCandidates& key) const { + return key.hash(); + } + }; +} // end namespace std + namespace chpl { namespace resolution { @@ -68,13 +108,15 @@ const ResolutionResultByPostorderID& resolveModuleStmt(Context* context, CHPL_ASSERT(!id.isSymbolDefiningScope()); ResolutionResultByPostorderID result; + ResolutionContext rcval(context); + auto rc = &rcval; ID moduleId = parsing::idToParentId(context, id); auto moduleAst = parsing::idToAst(context, moduleId); if (const Module* mod = moduleAst->toModule()) { // Resolve just the requested statement auto modStmt = parsing::idToAst(context, id); - auto visitor = Resolver::createForModuleStmt(context, mod, modStmt, result); + auto visitor = Resolver::createForModuleStmt(rc, mod, modStmt, result); modStmt->traverse(visitor); if (auto rec = context->recoverFromSelfRecursion()) { @@ -131,6 +173,8 @@ const ResolutionResultByPostorderID& resolveModule(Context* context, ID id) { CHPL_ASSERT(ast != nullptr); ResolutionResultByPostorderID result; + ResolutionContext rcval(context); + auto rc = &rcval; if (ast != nullptr) { if (const Module* mod = ast->toModule()) { @@ -138,7 +182,7 @@ const ResolutionResultByPostorderID& resolveModule(Context* context, ID id) { auto modScope = scopeForId(context, mod->id()); emitMultipleDefinedSymbolErrors(context, modScope); - auto r = Resolver::createForModuleStmt(context, mod, nullptr, result); + auto r = Resolver::createForModuleStmt(rc, mod, nullptr, result); for (auto child: mod->children()) { if (child->isComment() || @@ -178,7 +222,7 @@ const ResolutionResultByPostorderID& resolveModule(Context* context, ID id) { } } } - checkThrows(context, result, mod); + checkThrows(rc, result, mod); callInitDeinit(r); } } @@ -471,20 +515,6 @@ static TypedFnSignature::WhereClauseResult whereClauseResult( return whereClauseResult; } -// Finds a parent function from a function ID -// Returns that parent function, or an empty ID if there was none. -static ID parentFunctionId(Context* context, ID functionId) { - ID parentSymId = functionId.parentSymbolId(context); - const Scope* parentScope = scopeForId(context, parentSymId); - for (const Scope* s = parentScope; s != nullptr; s = s->parentScope()) { - if (s->tag() == asttags::Function) { - return s->id(); - } - } - - return ID(); -} - static void checkForParenlessMethodFieldRedefinition(Context* context, const Function* fn, Resolver& visitor) { @@ -509,94 +539,120 @@ static void checkForParenlessMethodFieldRedefinition(Context* context, } } -static const TypedFnSignature* const& -typedSignatureInitialQuery(Context* context, - const UntypedFnSignature* untypedSig) { - QUERY_BEGIN(typedSignatureInitialQuery, context, untypedSig); - +static const TypedFnSignature* +typedSignatureInitialImpl(ResolutionContext* rc, + const UntypedFnSignature* untypedSig) { + Context* context = rc->context(); const TypedFnSignature* result = nullptr; const AstNode* ast = parsing::idToAst(context, untypedSig->id()); const Function* fn = ast->toFunction(); - if (fn != nullptr) { - // look at the parent scopes to find the parent function, if any - const UntypedFnSignature* parentFnUntyped = nullptr; - const TypedFnSignature* parentFnTyped = nullptr; - ID parentFnId = parentFunctionId(context, fn->id()); - if (!parentFnId.isEmpty()) { - auto parentAst = parsing::idToAst(context, parentFnId); - auto parentFn = parentAst->toFunction(); - parentFnUntyped = UntypedFnSignature::get(context, parentFn); - parentFnTyped = typedSignatureInitial(context, parentFnUntyped); + if (fn == nullptr) return nullptr; + + // Construct the parent function's signature. + // If the input is a nested function, then a frame for the parent signature + // may be present in the 'ResolutionContext'. If no frame is present, then + // attempt to call 'typedSignatureInitial' on the parent. This will give + // up if any parent is generic or if any parent contains outer variables. + const TypedFnSignature* parentSignature = nullptr; + if (ID parentFnId = parsing::idToParentFunctionId(context, fn->id())) { + if (auto frame = rc->findFrameWithId(parentFnId)) { + if (auto sig = frame->signature()) { + CHPL_ASSERT(sig->id() && sig->id().contains(fn->id())); + parentSignature = sig; + } + } else { + auto parentShape = UntypedFnSignature::get(context, parentFnId); + parentSignature = typedSignatureInitial(rc, parentShape); } - ResolutionResultByPostorderID r; - auto visitor = Resolver::createForInitialSignature(context, fn, r); - // visit the formals - for (auto formal : fn->formals()) { - formal->traverse(visitor); + // The parent signature must exist. + if (!parentSignature) return nullptr; + + // All parents need to be concrete for the signature to be evaluated. + for (auto up = parentSignature; up; up = up->parentFn()) { + if (up->needsInstantiation()) return nullptr; } - // do not visit the return type or function body + } - // now, construct a TypedFnSignature from the result - std::vector formalTypes = visitor.getFormalTypes(fn); - bool needsInstantiation = anyFormalNeedsInstantiation(context, formalTypes, - untypedSig, - nullptr); + ResolutionResultByPostorderID r; + auto visitor = Resolver::createForInitialSignature(rc, fn, r); - // visit the where clause, unless it needs to be instantiated, in - // which case we will visit the where clause when that happens - TypedFnSignature::WhereClauseResult whereResult = - TypedFnSignature::WHERE_NONE; - if (auto whereClause = fn->whereClause()) { - if (needsInstantiation) { - whereResult = TypedFnSignature::WHERE_TBD; - } else { - whereClause->traverse(visitor); - whereResult = whereClauseResult(context, fn, r, needsInstantiation); - } - } + // visit the formals, but not the return type or body + for (auto formal : fn->formals()) formal->traverse(visitor); - checkForParenlessMethodFieldRedefinition(context, fn, visitor); + // Give up if we could not type outer variables present in the signature. + if (parentSignature && rc->isEmpty() && !visitor.outerVariables.isEmpty()) { + return nullptr; + } - result = TypedFnSignature::get(context, - untypedSig, - std::move(formalTypes), - whereResult, - needsInstantiation, - /* instantiatedFrom */ nullptr, - /* parentFn */ parentFnTyped, - /* formalsInstantiated */ Bitmap()); + // now, construct a TypedFnSignature from the result + std::vector formalTypes = visitor.getFormalTypes(fn); + bool needsInstantiation = anyFormalNeedsInstantiation(context, formalTypes, + untypedSig, + nullptr); + + // visit the where clause, unless it needs to be instantiated, in + // which case we will visit the where clause when that happens + auto whereResult = TypedFnSignature::WHERE_NONE; + if (auto whereClause = fn->whereClause()) { + if (needsInstantiation) { + // Visit the where clause for generic nested functions just to collect + // outer variables. TODO: Wrap in speculative block? + if (parentSignature) whereClause->traverse(visitor); + whereResult = TypedFnSignature::WHERE_TBD; + } else { + whereClause->traverse(visitor); + whereResult = whereClauseResult(context, fn, r, needsInstantiation); + } + } + // Give up if we could not type outer variables used in the where clause. + if (parentSignature && rc->isEmpty() && !visitor.outerVariables.isEmpty()) { + return nullptr; } - return QUERY_END(result); -} + checkForParenlessMethodFieldRedefinition(context, fn, visitor); -const TypedFnSignature* -typedSignatureInitial(Context* context, - const UntypedFnSignature* untypedSig) { + result = TypedFnSignature::get(context, + untypedSig, + std::move(formalTypes), + whereResult, + needsInstantiation, + /* instantiatedFrom */ nullptr, + /* parentFn */ parentSignature, + /* formalsInstantiated */ Bitmap(), + std::move(visitor.outerVariables)); - auto ret = typedSignatureInitialQuery(context, untypedSig); // also check the signature at this point if it is concrete - if (ret != nullptr && !ret->needsInstantiation()) { - checkSignature(context, ret); + if (result != nullptr && !result->needsInstantiation()) { + checkSignature(context, result); } - return ret; + + return result; } -static const TypedFnSignature* const& -typedSignatureInitialForIdQuery(Context* context, ID id) { - QUERY_BEGIN(typedSignatureInitialForIdQuery, context, id); +const TypedFnSignature* const& +typedSignatureInitial(ResolutionContext* rc, + const UntypedFnSignature* untypedSig) { + CHPL_RESOLUTION_QUERY_BEGIN(typedSignatureInitial, rc, untypedSig); + auto ret = typedSignatureInitialImpl(rc, untypedSig); + return CHPL_RESOLUTION_QUERY_END(ret); +} +static const TypedFnSignature* const& +typedSignatureInitialForIdQuery(ResolutionContext* rc, ID id) { + CHPL_RESOLUTION_QUERY_BEGIN(typedSignatureInitialForIdQuery, rc, id); + Context* context = rc->context(); const UntypedFnSignature* uSig = UntypedFnSignature::get(context, id); - const TypedFnSignature* result = typedSignatureInitial(context, uSig); - - return QUERY_END(result); + const TypedFnSignature* ret = uSig ? typedSignatureInitial(rc, uSig) + : nullptr; + return CHPL_RESOLUTION_QUERY_END(ret); } -const TypedFnSignature* typedSignatureInitialForId(Context* context, ID id) { - return typedSignatureInitialForIdQuery(context, std::move(id)); +const TypedFnSignature* +typedSignatureInitialForId(ResolutionContext* rc, ID id) { + return typedSignatureInitialForIdQuery(rc, std::move(id)); } // initedInParent is true if the decl variable is inited due to a parent @@ -1587,7 +1643,8 @@ typeConstructorInitialQuery(Context* context, const Type* t) /* needsInstantiation */ true, /* instantiatedFrom */ nullptr, /* parentFn */ nullptr, - /* formalsInstantiated */ Bitmap()); + /* formalsInstantiated */ Bitmap(), + /* outerVariables */ {}); } return QUERY_END(result); @@ -2014,30 +2071,9 @@ static QualifiedType getProperFormalType(const ResolutionResultByPostorderID& r, return type; } -static bool isCallInfoForInitializer(const CallInfo& ci) { - if (ci.name() == USTR("init") || ci.name() == USTR("init=")) - if (ci.isMethodCall()) - return true; - return false; -} - -// TODO: Move these to the 'InitResolver' visitor. -static bool isTfsForInitializer(const TypedFnSignature* tfs) { - if (tfs->untyped()->name() == USTR("init") || - tfs->untyped()->name() == USTR("init=")) - if (tfs->untyped()->isMethod()) - return true; - return false; -} - -static bool ensureBodyIsResolved(Context* context, const CallInfo& ci, - const TypedFnSignature* tfs) { - if (tfs->untyped()->isCompilerGenerated()) return false; - if (isTfsForInitializer(tfs)) return true; - return false; -} - -ApplicabilityResult instantiateSignature(Context* context, +// TODO: We could remove the 'ResolutionContext' argument if we figure out +// a different way/decide not to resolve initializer bodies down below. +ApplicabilityResult instantiateSignature(ResolutionContext* rc, const TypedFnSignature* sig, const CallInfo& call, const PoiScope* poiScope) { @@ -2051,6 +2087,7 @@ ApplicabilityResult instantiateSignature(Context* context, CHPL_ASSERT(sig->needsInstantiation()); + Context* context = rc->context(); const UntypedFnSignature* untypedSignature = sig->untyped(); const AstNode* ast = nullptr; const Function* fn = nullptr; @@ -2064,12 +2101,17 @@ ApplicabilityResult instantiateSignature(Context* context, ed = ast->toEnum(); } - const TypedFnSignature* parentFnTyped = nullptr; - if (sig->parentFn()) { - if (nullptr != computeOuterVariables(context, sig->id())) { - CHPL_UNIMPL("generic child functions that refer to outer variables " - "are not yet supported"); - return ApplicabilityResult::failure(sig->id(), FAIL_CANDIDATE_OTHER); + // If we are instantiating a nested function, then its parents should + // already be fully instantiated, in order for assumptions made during + // resolution to hold (the expectation is that the bodies of nested + // functions are fully resolved from the outermost parent down). + const TypedFnSignature* parentSignature = sig->parentFn(); + if (parentSignature) { + for (auto up = parentSignature; up; up = up->parentFn()) { + CHPL_ASSERT(!up->needsInstantiation()); + if (up->needsInstantiation()) { + return ApplicabilityResult::failure(sig->id(), FAIL_CANDIDATE_OTHER); + } } } @@ -2100,6 +2142,9 @@ ApplicabilityResult instantiateSignature(Context* context, auto visitor = createResolverForAst(context, fn, ad, ed, substitutions, poiScope, r); + // TODO: Stop copying these back in. + visitor.outerVariables = sig->outerVariables(); + QualifiedType varArgType; for (const FormalActual& entry : faMap.byFormals()) { // Do not ignore substitutions initially @@ -2323,7 +2368,7 @@ ApplicabilityResult instantiateSignature(Context* context, // might end up creating substitutions when we resolve its body // and process assignments like `this.someType = bla`. So, do not // short-circuit in that case. - if (!isTfsForInitializer(sig)) { + if (!sig->isInitializer()) { return ApplicabilityResult::success(sig); } else { // normally we do this when we add a substitution, but we haven't @@ -2446,252 +2491,303 @@ ApplicabilityResult instantiateSignature(Context* context, where, needsInstantiation, /* instantiatedFrom */ sig, - /* parentFn */ parentFnTyped, - std::move(formalsInstantiated)); + /* parentFn */ parentSignature, + std::move(formalsInstantiated), + sig->outerVariables()); // May need to resolve the body at this point to compute final TFS. - if (ensureBodyIsResolved(context, call, result)) { - if (!result->untyped()->isCompilerGenerated()) { - if (isTfsForInitializer(result)) { - auto resolvedFn = resolveInitializer(context, result, poiScope); - auto newTfs = resolvedFn->signature(); - if (newTfs->needsInstantiation()) { - context->error(newTfs->id(), "Failure to resolve initializer"); - } else { - result = newTfs; - } - } else { - CHPL_ASSERT(false && "Not handled yet!"); - std::ignore = resolveFunction(context, result, poiScope); - } - } + if (result->isInitializer() && !result->isCompilerGenerated()) { + auto rf = resolveFunction(rc, result, poiScope); + result = rf->signature(); } return ApplicabilityResult::success(result); } -static const owned& -resolveFunctionByPoisQuery(Context* context, - const TypedFnSignature* sig, - PoiCallIdFnIds poiFnIdsUsed, - PoiRecursiveCalls recursiveFnsUsed) { - QUERY_BEGIN(resolveFunctionByPoisQuery, - context, sig, poiFnIdsUsed, recursiveFnsUsed); +// This implements the body of 'resolveFunctionByInfo'. It returns a new +// 'ResolvedFunction' and does not directly set any queries. +// +// It is possible for the signature recorded in the returned function to +// be different than the input signature. Right now this happens for +// initializers. +static owned +resolveFunctionByInfoImpl(ResolutionContext* rc, const TypedFnSignature* sig, + PoiInfo poiInfo) { + Context* context = rc->context(); + const UntypedFnSignature* untypedSignature = sig->untyped(); + auto& id = untypedSignature->id(); + const AstNode* ast = parsing::idToAst(context, id); + const Function* fn = ast->toFunction(); + const PoiScope* poiScope = poiInfo.poiScope(); + bool isInitializer = sig->isInitializer(); + PoiInfo resolvedPoiInfo; + ResolutionResultByPostorderID rr; - owned result; - // the actual value is set in resolveFunctionByInfoQuery after it is - // computed because computing it generates the poiFnIdsUsed which is - // part of the key for this query. - CHPL_ASSERT(false && "should not be reached"); + if (!fn->body() && !isInitializer) { + CHPL_ASSERT(false && "Should only be called on functions!"); + return nullptr; + } - return QUERY_END(result); -} + const TypedFnSignature* inputSig = sig; + const TypedFnSignature* finalSig = sig; -// TODO: remove this workaround now that the build uses -// -Wno-dangling-reference -static const owned& -resolveFunctionByPoisQueryWrapper(Context* context, - const TypedFnSignature* sig, - const PoiInfo& poiInfo) { - auto poiFnIdsUsedCopy = poiInfo.poiFnIdsUsed(); - auto recursiveFnsUsedCopy = poiInfo.recursiveFnsUsed(); - - return resolveFunctionByPoisQuery(context, sig, - std::move(poiFnIdsUsedCopy), - std::move(recursiveFnsUsedCopy)); + auto visitor = isInitializer + ? Resolver::createForInitializer(rc, fn, poiScope, inputSig, rr) + : Resolver::createForFunction(rc, fn, poiScope, inputSig, rr); + + if (isInitializer) { + CHPL_ASSERT(visitor.initResolver.get()); + auto qt = QualifiedType(QualifiedType::VAR, VoidType::get(context)); + visitor.returnType = std::move(qt); + } + + // Walk the body to resolve it. + if (fn->body()) fn->body()->traverse(visitor); + + // There were outer variables we could not type, so give up. + if (!visitor.outerVariables.isEmpty() && rc->isEmpty()) { + return nullptr; + } + + // then, compute the return type if it is not an initializer + if (!isInitializer) { + computeReturnType(visitor); + + // else, potentially write out a new initializer signature + } else if (visitor.initResolver) { + finalSig = visitor.initResolver->finalize(); + } + + // then, resolve '=' and add any copy init/deinit calls as needed + callInitDeinit(visitor); + + // then, handle return intent overloads and maybe-const formals + adjustReturnIntentOverloadsAndMaybeConstRefs(visitor); + + // check that throws are handled or forwarded + // TODO: Call for initializers as well, and remove checks in the resolver. + if (!isInitializer) checkThrows(rc, rr, fn); + + // TODO: can this be encapsulated in a method? + resolvedPoiInfo.swap(visitor.poiInfo); + resolvedPoiInfo.setResolved(true); + resolvedPoiInfo.setPoiScope(nullptr); + + CHPL_ASSERT(inputSig == finalSig || isInitializer); + + auto ret = toOwned(new ResolvedFunction(finalSig, + fn->returnIntent(), + std::move(rr), + std::move(resolvedPoiInfo), + std::move(visitor.returnType), + std::move(visitor.poiTraceToChild), + std::move(visitor.sigAndInfoToChildPtr))); + return ret; } +// Forward declaration so that that query storage actions can be specialized. static const ResolvedFunction* const& -resolveFunctionByInfoQuery(Context* context, +resolveFunctionByInfoQuery(ResolutionContext* rc, const TypedFnSignature* sig, - PoiInfo poiInfo) { - QUERY_BEGIN(resolveFunctionByInfoQuery, context, sig, poiInfo); + PoiInfo poiInfo); - const UntypedFnSignature* untypedSignature = sig->untyped(); - const AstNode* ast = parsing::idToAst(context, untypedSignature->id()); - const Function* fn = ast->toFunction(); +static const owned& +resolveFunctionByPoisQuery(ResolutionContext* rc, PoiInfo::Trace poiTrace); - const PoiScope* poiScope = poiInfo.poiScope(); +// TODO: This can be used later to implement the "rewinding" necessary to +// resolve an exterior call to a nested function. Right now it is not +// clear if that will ever be possible outside of the API, so it remains +// unimplemented (but this specialization will stay as a reminder). +// +// Right now, this specialization just performs the default behavior. +// +template struct +ResolutionContext::GlobalComputeSetup { + bool enter(ResolutionContext* rc, const Args& args) { + return false; + } - PoiInfo resolvedPoiInfo; + void leave(ResolutionContext* rc, const Args& args) {} +}; - // Note that in this case the AST for the function can be nullptr. - if (isTfsForInitializer(sig)) { - auto retType = QualifiedType(QualifiedType::VAR, VoidType::get(context)); +// Implement unstable caching for 'resolveFunctionByInfoQuery' by looking +// for an appropriate PoiInfo/function stored within the parent's Resolver. +template struct +ResolutionContext::UnstableCache { - ResolutionResultByPostorderID resolutionById; - auto visitor = Resolver::createForInitializer(context, fn, poiScope, - sig, - resolutionById); - CHPL_ASSERT(visitor.initResolver.get()); - if (fn) { - fn->body()->traverse(visitor); - // then, set the return type - visitor.returnType = retType; - // then, resolve '=' and add any copy init/deinit calls as needed - callInitDeinit(visitor); - // then, handle return intent overloads and maybe-const formals - adjustReturnIntentOverloadsAndMaybeConstRefs(visitor); - } - - auto newTfsForInitializer = visitor.initResolver->finalize(); - - // TODO: can this be encapsulated in a method? - resolvedPoiInfo.swap(visitor.poiInfo); - resolvedPoiInfo.setResolved(true); - resolvedPoiInfo.setPoiScope(nullptr); - - // If we resolved an initializer, then we started with a function - // signature that might have needed instantiation for the receiver. - // We need to communicate to the query framework that the new TFS - // does not need to have its corresponding function resolved. - if (newTfsForInitializer != sig) { - auto resolutionByIdCopy = resolutionById; - auto resolvedInit = toOwned(new ResolvedFunction(newTfsForInitializer, - fn->returnIntent(), - std::move(resolutionByIdCopy), - resolvedPoiInfo, - visitor.returnType)); - QUERY_STORE_RESULT(resolveFunctionByPoisQuery, - context, - resolvedInit, - newTfsForInitializer, - resolvedPoiInfo.poiFnIdsUsed(), - resolvedPoiInfo.recursiveFnsUsed()); - auto& saved = - resolveFunctionByPoisQueryWrapper(context, newTfsForInitializer, - resolvedPoiInfo); - const ResolvedFunction* resultInit = saved.get(); - QUERY_STORE_RESULT(resolveFunctionByInfoQuery, - context, - resultInit, - newTfsForInitializer, - poiInfo); - } - - // If we resolved an initializer, the result should point to the - // final, fully instantiated TFS that was created (if there is - // one). In other cases, we just use the input signature. - auto finalTfs = newTfsForInitializer ? newTfsForInitializer : sig; - - owned resolved - = toOwned(new ResolvedFunction(finalTfs, fn->returnIntent(), - std::move(resolutionById), - resolvedPoiInfo, - visitor.returnType)); - - // Store the result in the query under the POIs used. - // If there was already a value for this revision, this - // call will not update it. (If it did, that could lead to - // memory errors). - QUERY_STORE_RESULT(resolveFunctionByPoisQuery, - context, - resolved, - sig, - resolvedPoiInfo.poiFnIdsUsed(), - resolvedPoiInfo.recursiveFnsUsed()); - - // On this path we are just resolving a normal function. - } else if (fn) { - ResolutionResultByPostorderID resolutionById; - auto visitor = Resolver::createForFunction(context, fn, poiScope, sig, - resolutionById); - - if (fn->body()) { - fn->body()->traverse(visitor); - } - - // then, compute the return type - computeReturnType(visitor); + const T* fetchOrNull(ResolutionContext* rc, const Args& args) { + static_assert(std::tuple_size_v == 2); + auto sig = std::get<0>(args); - // then, resolve '=' and add any copy init/deinit calls as needed - callInitDeinit(visitor); - - // then, handle return intent overloads and maybe-const formals - adjustReturnIntentOverloadsAndMaybeConstRefs(visitor); - - // check that throws are handled or forwarded - checkThrows(context, resolutionById, fn); - - // TODO: can this be encapsulated in a method? - resolvedPoiInfo.swap(visitor.poiInfo); - resolvedPoiInfo.setResolved(true); - resolvedPoiInfo.setPoiScope(nullptr); - - owned resolved - = toOwned(new ResolvedFunction(sig, fn->returnIntent(), - std::move(resolutionById), - resolvedPoiInfo, - visitor.returnType)); - - // Store the result in the query under the POIs used. - // If there was already a value for this revision, this - // call will not update it. (If it did, that could lead to - // memory errors). - QUERY_STORE_RESULT(resolveFunctionByPoisQuery, - context, - resolved, - sig, - resolvedPoiInfo.poiFnIdsUsed(), - resolvedPoiInfo.recursiveFnsUsed()); + if (auto rv = rc->findParentResolverFor(sig)) { + auto& m = rv->sigAndInfoToChildPtr; + ResolvedFunction::SigAndInfo k = { sig, std::get<1>(args) }; + auto it = m.find(k); + if (it == m.end()) return nullptr; + return &it->second; + } - } else { - CHPL_ASSERT(false && "this query should be called on Functions"); + return nullptr; } - // Return the unique result from the query (that might have been saved above) - const owned& resolved = - resolveFunctionByPoisQueryWrapper(context, sig, resolvedPoiInfo); + const T& store(ResolutionContext* rc, T x, const Args& args) { + static_assert(std::tuple_size_v == 2); + auto sig = std::get<0>(args); - const ResolvedFunction* result = resolved.get(); + if (auto rv = rc->findParentResolverFor(sig)) { + auto& m = rv->sigAndInfoToChildPtr; + ResolvedFunction::SigAndInfo k = { sig, std::get<1>(args) }; + const ResolvedFunction* v = x; + auto check = m.insert(std::make_pair(std::move(k), v)); + CHPL_ASSERT(check.second); + return check.first->second; + } - return QUERY_END(result); + CHPL_ASSERT(false && "Should not reach here!"); + auto& f = x != nullptr ? rc->lastFrameOrBaseMutable() : rc->baseFrame_; + return f.cache(std::move(x)); + } +}; + +// Implement unstable caching for 'resolveFunctionByPoisQuery' by looking +// for an appropriate POI trace/function within the parent's Resolver. +template struct +ResolutionContext::UnstableCache { + + const T* fetchOrNull(ResolutionContext* rc, const Args& args) { + static_assert(std::tuple_size_v == 1); + auto& poiTrace = std::get<0>(args); + auto sig = std::get<0>(poiTrace); + + if (auto rv = rc->findParentResolverFor(sig)) { + auto& m = rv->poiTraceToChild; + auto it = m.find(poiTrace); + if (it != m.end()) return &it->second; + } + + return nullptr; + } + + const T& store(ResolutionContext* rc, T x, const Args& args) { + static_assert(std::tuple_size_v == 1); + auto& poiTrace = std::get<0>(args); + auto sig = std::get<0>(poiTrace); + + if (auto rv = rc->findParentResolverFor(sig)) { + auto& m = rv->poiTraceToChild; + auto p = std::make_pair(poiTrace, std::move(x)); + auto check = m.insert(std::move(p)); + CHPL_ASSERT(check.second); + return check.first->second; + } + + CHPL_ASSERT(false && "Should not reach here!"); + auto& f = x != nullptr ? rc->lastFrameOrBaseMutable() : rc->baseFrame_; + return f.cache(std::move(x)); + } +}; + +static const owned& +resolveFunctionByPoisQuery(ResolutionContext* rc, PoiInfo::Trace poiTrace) { + CHPL_RESOLUTION_QUERY_BEGIN(resolveFunctionByPoisQuery, rc, poiTrace); + + auto& rcq = CHPL_RESOLUTION_REF_TO_CURRENT_QUERY_HANDLE(); + if (rcq.canUseGlobalCache()) { + // We should not have made it this far if we can recall from the global + // context query cache. The query guard should have returned early. + // + // The actual value is set in 'resolveFunctionByInfoQuery' after it is + // computed, because resolving the function's body generates the POI + // trace, which is the key for this query. + CHPL_ASSERT(false && "Should be set in 'resolveFunctionByInfoQuery'!"); + } + + // Just return an empty pointer, otherwise. + owned ret; + return CHPL_RESOLUTION_QUERY_END(ret); } -const ResolvedFunction* resolveInitializer(Context* context, - const TypedFnSignature* sig, - const PoiScope* poiScope) { - bool isAcceptable = isTfsForInitializer(sig); - if (!isAcceptable) { - CHPL_ASSERT(false && "Should only be called for initializers"); +static const ResolvedFunction* const& +resolveFunctionByInfoQuery(ResolutionContext* rc, + const TypedFnSignature* sig, + PoiInfo poiInfo) { + CHPL_RESOLUTION_QUERY_BEGIN(resolveFunctionByInfoQuery, rc, sig, poiInfo); + + // Call the implementation which resolves the function body. + auto resolved = resolveFunctionByInfoImpl(rc, sig, std::move(poiInfo)); + + // The final signature should only differ for initializers. + auto finalSig = resolved->signature(); + CHPL_ASSERT(finalSig == sig || sig->isInitializer()); + + // Make a POI trace for use with the generic cache. + auto resolvedPoiTrace = resolved->poiInfo().createTraceFor(sig); + + // Try to store in the generic cache. + CHPL_RESOLUTION_QUERY_STORE_RESULT(resolveFunctionByPoisQuery, rc, + std::move(resolved), + resolvedPoiTrace); + + // Initializer signatures can potentially require their body to be + // resolved in order to complete instantiation. This means that the + // 'ResolvedFunction' can have a different signature than what you + // started with (the receiver or a dependent type could instantiate). + // + // The user could pass in the fully resolved initializer signature + // in a subsequent call, and we don't want to resolve the function + // body again. So record a second entry for 'resolveFunctionByInfoQuery' + // which links the fully resolved signature to the result. + if (finalSig != sig) { + auto& saved = resolveFunctionByPoisQuery(rc, resolvedPoiTrace); + CHPL_RESOLUTION_QUERY_STORE_RESULT(resolveFunctionByInfoQuery, rc, + saved.get(), + finalSig, + poiInfo); } - // construct the PoiInfo for this case - auto poiInfo = PoiInfo(poiScope); + // Return the unique result from the query (that might have been saved + // above - if it was not saved, then we are reusing a cached result, + // which means the input and final signatures can differ. + auto& saved = resolveFunctionByPoisQuery(rc, resolvedPoiTrace); + auto ret = saved.get(); - // lookup in the map using this PoiInfo - return resolveFunctionByInfoQuery(context, sig, std::move(poiInfo)); + return CHPL_RESOLUTION_QUERY_END(ret); } -static const ResolvedFunction* helpResolveFunction(Context* context, - const TypedFnSignature* sig, - const PoiScope* poiScope, - bool skipIfRunning) { +static const ResolvedFunction* +helpResolveFunction(ResolutionContext* rc, const TypedFnSignature* sig, + const PoiScope* poiScope, + bool skipIfRunning) { // Forget about any inferred signature (to avoid resolving the // same function twice when working with inferred 'out' formals) sig = sig->inferredFrom(); - // this should only be applied to concrete fns or instantiations - CHPL_ASSERT(!sig->needsInstantiation()); + if (!sig->isInitializer() && sig->needsInstantiation()) { + CHPL_ASSERT(false && "Should only be called on concrete or fully " + "instantiated functions"); + return nullptr; + } // construct the PoiInfo for this case auto poiInfo = PoiInfo(poiScope); if (skipIfRunning) { - if (context->isQueryRunning(resolveFunctionByInfoQuery, - std::make_tuple(sig, poiInfo))) { + constexpr auto f = resolveFunctionByInfoQuery; + if (CHPL_RESOLUTION_IS_GLOBAL_QUERY_RUNNING(f, rc, sig, poiInfo)) { return nullptr; } } // lookup in the map using this PoiInfo - return resolveFunctionByInfoQuery(context, sig, std::move(poiInfo)); + return resolveFunctionByInfoQuery(rc, sig, std::move(poiInfo)); } -const TypedFnSignature* inferRefMaybeConstFormals(Context* context, - const TypedFnSignature* sig, - const PoiScope* poiScope) { +const TypedFnSignature* +inferRefMaybeConstFormals(ResolutionContext* rc, + const TypedFnSignature* sig, + const PoiScope* poiScope) { + Context* context = rc->context(); if (sig == nullptr) { return nullptr; } @@ -2711,9 +2807,8 @@ const TypedFnSignature* inferRefMaybeConstFormals(Context* context, return sig; } - // otherwise, try to resolve the body of the function - const ResolvedFunction* rFn = - helpResolveFunction(context, sig, poiScope, /* skipIfRunning */ true); + const bool skipIfRunning = true; + auto rFn = helpResolveFunction(rc, sig, poiScope, skipIfRunning); if (rFn == nullptr) return nullptr; // give up if it would be a recursive query invocation @@ -2738,46 +2833,36 @@ const TypedFnSignature* inferRefMaybeConstFormals(Context* context, return result; } -const ResolvedFunction* resolveFunction(Context* context, +const ResolvedFunction* resolveFunction(ResolutionContext* rc, const TypedFnSignature* sig, const PoiScope* poiScope) { - // If there were outer variables, then do not bother trying to resolve. - if (computeOuterVariables(context, sig->id())) return nullptr; - return helpResolveFunction(context, sig, poiScope, /* skipIfRunning */ false); + bool skipIfRunning = false; + return helpResolveFunction(rc, sig, poiScope, skipIfRunning); } const ResolvedFunction* resolveConcreteFunction(Context* context, ID id) { if (id.isEmpty()) return nullptr; - // If there were outer variables, then do not bother trying to resolve. - if (computeOuterVariables(context, id)) return nullptr; - - const TypedFnSignature* sig = typedSignatureInitialForId(context, id); + ResolutionContext rcval(context); + auto rc = &rcval; + const UntypedFnSignature* uSig = UntypedFnSignature::get(context, id); + const TypedFnSignature* sig = typedSignatureInitial(rc, uSig); if (sig == nullptr || sig->needsInstantiation()) { return nullptr; } - auto whereFalse = - resolution::TypedFnSignature::WhereClauseResult::WHERE_FALSE; + auto whereFalse = TypedFnSignature::WhereClauseResult::WHERE_FALSE; if (sig->whereClauseResult() == whereFalse) { return nullptr; } - const ResolvedFunction* ret = resolveFunction(context, sig, nullptr); + const PoiScope* poiScope = nullptr; + auto ret = resolveFunction(&rcval, sig, poiScope); return ret; } -// This should be set when performing 'scopeResolveFunction', below. -static const owned& -computeOuterVariablesQuery(Context* context, ID id) { - QUERY_BEGIN(computeOuterVariablesQuery, context, id); - owned ret; - CHPL_ASSERT(false && "Should not be called directly!"); - return QUERY_END(ret); -} - static owned scopeResolveFunctionQueryBody(Context* context, ID id) { const AstNode* ast = parsing::idToAst(context, id); @@ -2786,12 +2871,10 @@ scopeResolveFunctionQueryBody(Context* context, ID id) { ResolutionResultByPostorderID resolutionById; const TypedFnSignature* sig = nullptr; owned result; - owned outerVars = toOwned(new OuterVariables(context, id)); if (fn) { auto visitor = - Resolver::createForScopeResolvingFunction(context, fn, resolutionById, - std::move(outerVars)); + Resolver::createForScopeResolvingFunction(context, fn, resolutionById); // visit the children of fn to scope resolve // (visiting the children because visiting a function will not @@ -2803,21 +2886,13 @@ scopeResolveFunctionQueryBody(Context* context, ID id) { checkForParenlessMethodFieldRedefinition(context, fn, visitor); sig = visitor.typedSignature; - - if (!visitor.outerVars->isEmpty()) { - std::swap(outerVars, visitor.outerVars); - } } - QUERY_STORE_RESULT(computeOuterVariablesQuery, - context, - std::move(outerVars), - id); - result = toOwned(new ResolvedFunction(sig, fn->returnIntent(), std::move(resolutionById), PoiInfo(), - QualifiedType())); + QualifiedType(), + {}, {})); return result; } @@ -2828,32 +2903,7 @@ scopeResolveFunctionQuery(Context* context, ID id) { return QUERY_END(ret); } -const OuterVariables* computeOuterVariables(Context* context, ID id) { - - // For now, preemptively return 'nullptr' if 'id' is not a function. - if (!parsing::idIsNestedFunction(context, id)) return nullptr; - - if (!context->hasCurrentResultForQuery(computeOuterVariablesQuery, { id })) { - if (!context->isQueryRunning(scopeResolveFunctionQuery, { id })) { - - // The 'computeOuterVariablesQuery' is set as a side effect of - // performing scope resolution, since both require a traversal. - std::ignore = scopeResolveFunction(context, id); - } else { - - // Just return 'nullptr', we have no results to use, yet. The caller - // can only be the Resolver set up for scope-resolve if this branch - // is happening. - return nullptr; - } - } - - // We should have a result at this point. - return computeOuterVariablesQuery(context, id).get(); -} - -const ResolvedFunction* scopeResolveFunction(Context* context, - ID id) { +const ResolvedFunction* scopeResolveFunction(Context* context, ID id) { if (id.isEmpty()) return nullptr; @@ -2909,19 +2959,6 @@ const ResolutionResultByPostorderID& scopeResolveEnum(Context* context, return QUERY_END(result); } - -const ResolvedFunction* resolveOnlyCandidate(Context* context, - const ResolvedExpression& r) { - auto msc = r.mostSpecific().only(); - if (!msc) return nullptr; - - const TypedFnSignature* sig = msc.fn(); - const PoiScope* poiScope = r.poiScope(); - - return resolveFunction(context, sig, poiScope); -} - - static bool isUntypedSignatureApplicable(Context* context, const UntypedFnSignature* ufs, @@ -3012,9 +3049,10 @@ isInitialTypedSignatureApplicable(Context* context, // returns nullptr if the candidate is not applicable, // or the result of typedSignatureInitial if it is. static ApplicabilityResult -doIsCandidateApplicableInitial(Context* context, - const IdAndFlags& candidate, - const CallInfo& ci) { +doIsCandidateApplicableInitial(ResolutionContext* rc, + const IdAndFlags& candidate, + const CallInfo& ci) { + Context* context = rc->context(); bool isParenlessFn = !candidate.isParenfulFunction(); bool isField = candidate.isMethodOrField() && !candidate.isMethod(); const ID& candidateId = candidate.id(); @@ -3075,7 +3113,7 @@ doIsCandidateApplicableInitial(Context* context, auto recv = ci.calledType(); auto fn = parsing::idToAst(context, candidateId)->toFunction(); ResolutionResultByPostorderID r; - auto vis = Resolver::createForInitialSignature(context, fn, r); + auto vis = Resolver::createForInitialSignature(rc, fn, r); fn->thisFormal()->traverse(vis); auto res = vis.byPostorder.byAst(fn->thisFormal()); @@ -3087,27 +3125,30 @@ doIsCandidateApplicableInitial(Context* context, } } - auto ret = typedSignatureInitialForId(context, candidateId); - auto ufs = ret->untyped(); - auto faMap = FormalActualMap(ufs, ci); + auto ret = typedSignatureInitialForId(rc, candidateId); + auto ufs = ret ? ret->untyped() : nullptr; if (!ret) { + // TODO: Can we reliably return something more specific here? + if (parsing::idIsNestedFunction(context, candidateId) && rc->isEmpty()) { + return ApplicabilityResult::failure(candidateId, /* TODO */ FAIL_CANDIDATE_OTHER); + } + return ApplicabilityResult::failure(candidateId, /* TODO */ FAIL_CANDIDATE_OTHER); } + auto faMap = FormalActualMap(ufs, ci); return isInitialTypedSignatureApplicable(context, ret, faMap, ci); } // returns nullptr if the candidate is not applicable, // or the result of an instantiated typedSignature if it is. static ApplicabilityResult -doIsCandidateApplicableInstantiating(Context* context, +doIsCandidateApplicableInstantiating(ResolutionContext* rc, const TypedFnSignature* typedSignature, const CallInfo& call, const PoiScope* poiScope) { - - auto instantiated = - instantiateSignature(context, typedSignature, call, poiScope); + auto instantiated = instantiateSignature(rc, typedSignature, call, poiScope); if (!instantiated.success()) return instantiated; @@ -3119,62 +3160,63 @@ doIsCandidateApplicableInstantiating(Context* context, return instantiated; } -static ApplicabilityResult const& -isCandidateApplicableInitialQuery(Context* context, - IdAndFlags candidateId, - CallInfo call) { +static const EvaluatedCandidates +filterCandidatesInitialGatherRejectedImpl(ResolutionContext* rc, + const MatchingIdsWithName& lst, + const CallInfo& call, + bool gatherRejected) { + Context* context = rc->context(); + EvaluatedCandidates ret; - QUERY_BEGIN(isCandidateApplicableInitialQuery, context, candidateId, call); + for (auto cur = lst.begin(); cur != lst.end(); ++cur) { + auto& idv = cur.curIdAndFlags(); + bool isNestedCandidate = parsing::idIsNestedFunction(context, idv.id()); - auto result = - doIsCandidateApplicableInitial(context, candidateId, call); + if (isNestedCandidate) ret.evaluatedAnyNestedFunction = true; + auto s = doIsCandidateApplicableInitial(rc, idv, call); - return QUERY_END(result); -} - -static const std::pair>& -filterCandidatesInitialGatherRejected(Context* context, - MatchingIdsWithName lst, - CallInfo call, bool gatherRejected) { - QUERY_BEGIN(filterCandidatesInitialGatherRejected, context, lst, call, gatherRejected); - - CandidatesAndForwardingInfo matching; - std::vector rejected; - - auto end = lst.end(); - for (auto cur = lst.begin(); cur != end; ++cur) { - const IdAndFlags& id = cur.curIdAndFlags(); - auto s = isCandidateApplicableInitialQuery(context, id, call); if (s.success()) { - matching.addCandidate(s.candidate()); + ret.matching.addCandidate(s.candidate()); } else if (gatherRejected) { - rejected.push_back(s); + ret.rejected.push_back(s); } } - auto result = std::make_pair(std::move(matching), std::move(rejected)); - return QUERY_END(result); + return ret; +} + +static const EvaluatedCandidates& +filterCandidatesInitialGatherRejectedQuery(ResolutionContext* rc, + MatchingIdsWithName lst, + CallInfo call, + bool gatherRejected) { + // TODO: The predicate always loops over the list (best case 2 passes). + CHPL_RESOLUTION_QUERY_BEGIN(filterCandidatesInitialGatherRejectedQuery, rc, + lst, call, gatherRejected); + auto ret = filterCandidatesInitialGatherRejectedImpl(rc, lst, call, + gatherRejected); + return CHPL_RESOLUTION_QUERY_END(ret); } const CandidatesAndForwardingInfo& -filterCandidatesInitial(Context* context, +filterCandidatesInitial(ResolutionContext* rc, MatchingIdsWithName lst, CallInfo call) { - auto& result = - filterCandidatesInitialGatherRejected(context, std::move(lst), - call, /* gatherRejected */ false); - return result.first; + constexpr auto query = filterCandidatesInitialGatherRejectedQuery; + bool gatherRejected = false; + auto& result = query(rc, std::move(lst), call, gatherRejected); + return result.matching; } void -filterCandidatesInstantiating(Context* context, +filterCandidatesInstantiating(ResolutionContext* rc, const CandidatesAndForwardingInfo& lst, const CallInfo& call, const Scope* inScope, const PoiScope* inPoiScope, CandidatesAndForwardingInfo& result, std::vector* rejected) { + Context* context = rc->context(); // Performance: Would it help to make this a query? // (I left it not as a query since it runs some other queries @@ -3189,9 +3231,7 @@ filterCandidatesInstantiating(Context* context, } auto instantiated = - doIsCandidateApplicableInstantiating(context, - typedSignature, - call, + doIsCandidateApplicableInstantiating(rc, typedSignature, call, instantiationPoiScope); if (instantiated.success()) { result.addCandidate(instantiated.candidate()); @@ -3206,11 +3246,10 @@ filterCandidatesInstantiating(Context* context, } static -void accumulatePoisUsedByResolvingBody(Context* context, +void accumulatePoisUsedByResolvingBody(ResolutionContext* rc, const TypedFnSignature* signature, const PoiScope* poiScope, PoiInfo& poiInfo) { - if (signature == nullptr) { return; } @@ -3226,8 +3265,8 @@ void accumulatePoisUsedByResolvingBody(Context* context, } // resolve the body, if it is not already being resolved - const ResolvedFunction* r = helpResolveFunction(context, signature, poiScope, - /* skipIfRunning */ true); + bool skipIfRunning = true; + auto r = helpResolveFunction(rc, signature, poiScope, skipIfRunning); if (r == nullptr) { // If it's a recursive call, track it in the PoiInfo poiInfo.accumulateRecursive(signature, poiScope); @@ -3725,6 +3764,9 @@ static bool resolveFnCallSpecialType(Context* context, return false; } + ResolutionContext rcval(context); + auto rc = &rcval; + // Types that can be computed without resolving other calls if (const Type* t = resolveBuiltinTypeCtor(context, call, ci)) { auto exprTypeOut = QualifiedType(QualifiedType::TYPE, t); @@ -3751,12 +3793,12 @@ static bool resolveFnCallSpecialType(Context* context, auto typeCtorName = UniqueString::get(context, "static_type"); auto ctorCall = CallInfo::createWithReceiver(ci, recv, typeCtorName); - result = resolveCall(context, call, ctorCall, inScopes); + result = resolveCall(rc, call, ctorCall, inScopes); return true; } else if (ci.name() == "atomic") { auto newName = UniqueString::get(context, "chpl__atomicType"); auto ctorCall = CallInfo::copyAndRename(ci, newName); - result = resolveCall(context, call, ctorCall, inScopes); + result = resolveCall(rc, call, ctorCall, inScopes); return true; } @@ -3772,6 +3814,8 @@ resolveFnCallForTypeCtor(Context* context, CandidatesAndForwardingInfo initialCandidates; CandidatesAndForwardingInfo candidates; + ResolutionContext rcval(context); + auto rc = &rcval; CHPL_ASSERT(ci.calledType().type() != nullptr); CHPL_ASSERT(!ci.calledType().type()->isUnknownType()); @@ -3781,10 +3825,7 @@ resolveFnCallForTypeCtor(Context* context, // TODO: do something for partial instantiation - filterCandidatesInstantiating(context, - initialCandidates, - ci, - inScope, + filterCandidatesInstantiating(rc, initialCandidates, ci, inScope, inPoiScope, candidates, /* rejected */ nullptr); @@ -3918,8 +3959,9 @@ considerCompilerGeneratedCandidates(Context* context, } // need to instantiate before storing + ResolutionContext rcval(context); auto poi = pointOfInstantiationScope(context, inScope, inPoiScope); - auto instantiated = doIsCandidateApplicableInstantiating(context, + auto instantiated = doIsCandidateApplicableInstantiating(&rcval, tfs, ci, poi); @@ -4099,7 +4141,7 @@ static void filterCandidatesLastResort( // indicates the actual type that is passed as the method receiver // when using forwarding. static void -gatherAndFilterCandidatesForwarding(Context* context, +gatherAndFilterCandidatesForwarding(ResolutionContext* rc, const AstNode* astForErr, const Call* call, const CallInfo& ci, @@ -4108,7 +4150,7 @@ gatherAndFilterCandidatesForwarding(Context* context, CandidatesAndForwardingInfo& poiCandidates, LastResortCandidateGroups& lrcGroups, std::vector* rejected) { - + Context* context = rc->context(); const Type* receiverType = ci.actual(0).type().type(); // Resolve the forwarding expression's types & decide if we @@ -4222,11 +4264,11 @@ gatherAndFilterCandidatesForwarding(Context* context, // filter without instantiating yet const auto& initialCandidates = - filterCandidatesInitial(context, std::move(v), fci); + filterCandidatesInitial(rc, std::move(v), fci); // find candidates, doing instantiation if necessary CandidatesAndForwardingInfo candidatesWithInstantiations; - filterCandidatesInstantiating(context, + filterCandidatesInstantiating(rc, initialCandidates, fci, inScopes.callScope(), @@ -4267,11 +4309,11 @@ gatherAndFilterCandidatesForwarding(Context* context, // filter without instantiating yet auto& initialCandidates = - filterCandidatesInitial(context, std::move(v), fci); + filterCandidatesInitial(rc, std::move(v), fci); // find candidates, doing instantiation if necessary CandidatesAndForwardingInfo candidatesWithInstantiations; - filterCandidatesInstantiating(context, + filterCandidatesInstantiating(rc, initialCandidates, fci, inScopes.callScope(), @@ -4299,8 +4341,7 @@ gatherAndFilterCandidatesForwarding(Context* context, if (fci.isMethodCall() && fci.numActuals() >= 1) { const Type* receiverType = fci.actual(0).type().type(); if (typeUsesForwarding(context, receiverType)) { - gatherAndFilterCandidatesForwarding(context, - astForErr, call, fci, + gatherAndFilterCandidatesForwarding(rc, astForErr, call, fci, inScopes, nonPoiCandidates, poiCandidates, @@ -4340,6 +4381,57 @@ static bool isInsideForwarding(Context* context, const Call* call) { return insideForwarding; } +static void doGatherCandidates(ResolutionContext* rc, + CandidatesAndForwardingInfo& outCandidates, + LastResortCandidateGroups& outLrcGroups, + CheckedScopes& outVisited, + size_t& outFirstPoiCandidateIdx, + const Call* call, + const CallInfo& ci, + const CallScopeInfo& inScopes, + std::vector* rejected, + const PoiScope* usePoiScope) { + Context* context = rc->context(); + auto lookupScope = usePoiScope ? usePoiScope->inScope() + : inScopes.lookupScope(); + + // First, lookup possible candidates in the relevant scope. + auto v = lookupCalledExpr(context, lookupScope, ci, outVisited); + bool gatherRejections = (rejected != nullptr); + + // Next, filter candidates against their initial type signatures. + constexpr auto filter = filterCandidatesInitialGatherRejectedQuery; + auto& initial = filter(rc, v, ci, gatherRejections); + const auto& initialCandidates = initial.matching; + const auto& initialRejections = initial.rejected; + + if (rejected != nullptr) { + rejected->insert(rejected->end(), + initialRejections.begin(), + initialRejections.end()); + } + + // Finally, filter with instantiation as necessary. + CandidatesAndForwardingInfo candidatesWithInstantiations; + filterCandidatesInstantiating(rc, + initialCandidates, + ci, + inScopes.callScope(), + inScopes.poiScope(), + candidatesWithInstantiations, + rejected); + + // filter out last resort candidates + CandidatesAndForwardingInfo lrcGroup; + filterCandidatesLastResort(context, candidatesWithInstantiations, + outCandidates, lrcGroup); + if (usePoiScope) { + outLrcGroups.addPoiCandidates(std::move(lrcGroup)); + } else { + outLrcGroups.addNonPoiCandidates(std::move(lrcGroup)); + } +} + // Returns candidates (including instantiating candidates) // for resolving CallInfo 'ci'. // @@ -4353,13 +4445,14 @@ static bool isInsideForwarding(Context* context, const Call* call) { // candidates and will indicate the actual type that is passed // to the 'this' receiver formal. static CandidatesAndForwardingInfo -gatherAndFilterCandidates(Context* context, +gatherAndFilterCandidates(ResolutionContext* rc, const AstNode* astForErr, const Call* call, const CallInfo& ci, const CallScopeInfo& inScopes, size_t& firstPoiCandidate, std::vector* rejected) { + Context* context = rc->context(); CandidatesAndForwardingInfo candidates; LastResortCandidateGroups lrcGroups; CheckedScopes visited; @@ -4378,41 +4471,11 @@ gatherAndFilterCandidates(Context* context, // don't worry about last resort for compiler generated candidates - // next, look for candidates without using POI. - { - // compute the potential functions that it could resolve to - auto v = lookupCalledExpr(context, inScopes.lookupScope(), ci, visited); - - // filter without instantiating yet - const auto& initialCandidatesAndRejections = - filterCandidatesInitialGatherRejected(context, std::move(v), ci, rejected != nullptr); - const auto& initialCandidates = initialCandidatesAndRejections.first; - const auto& initialRejections = initialCandidatesAndRejections.second; - - if (rejected != nullptr) { - rejected->insert(rejected->end(), - initialRejections.begin(), - initialRejections.end()); - } - - // find candidates, doing instantiation if necessary - CandidatesAndForwardingInfo candidatesWithInstantiations; - filterCandidatesInstantiating(context, - initialCandidates, - ci, - inScopes.callScope(), - inScopes.poiScope(), - candidatesWithInstantiations, - rejected); - - // filter out last resort candidates - CandidatesAndForwardingInfo lrcGroup; - filterCandidatesLastResort(context, candidatesWithInstantiations, - candidates, lrcGroup); - lrcGroups.addNonPoiCandidates(std::move(lrcGroup)); - } - - // next, look for candidates using POI + // look for candidates without using POI. + doGatherCandidates(rc, candidates, lrcGroups, visited, firstPoiCandidate, + call, ci, inScopes, rejected, nullptr); + + // next, look for candidates using POIs firstPoiCandidate = candidates.size(); for (const PoiScope* curPoi = inScopes.poiScope(); curPoi != nullptr; @@ -4423,36 +4486,8 @@ gatherAndFilterCandidates(Context* context, break; } - // compute the potential functions that it could resolve to - auto v = lookupCalledExpr(context, curPoi->inScope(), ci, visited); - - // filter without instantiating yet - const auto& initialCandidatesAndRejections = - filterCandidatesInitialGatherRejected(context, std::move(v), ci, rejected != nullptr); - const auto& initialCandidates = initialCandidatesAndRejections.first; - const auto& initialRejections = initialCandidatesAndRejections.second; - - if (rejected != nullptr) { - rejected->insert(rejected->end(), - initialRejections.begin(), - initialRejections.end()); - } - - // find candidates, doing instantiation if necessary - CandidatesAndForwardingInfo candidatesWithInstantiations; - filterCandidatesInstantiating(context, - initialCandidates, - ci, - inScopes.callScope(), - inScopes.poiScope(), - candidatesWithInstantiations, - rejected); - - // filter out last resort candidates - CandidatesAndForwardingInfo lrcGroup; - filterCandidatesLastResort(context, candidatesWithInstantiations, - candidates, lrcGroup); - lrcGroups.addPoiCandidates(std::move(lrcGroup)); + doGatherCandidates(rc, candidates, lrcGroups, visited, firstPoiCandidate, + call, ci, inScopes, rejected, curPoi); } // If no candidates were found and it's a method, try forwarding @@ -4484,7 +4519,7 @@ gatherAndFilterCandidates(Context* context, CandidatesAndForwardingInfo poiCandidates; gatherAndFilterCandidatesForwarding( - context, astForErr, call, ci, inScopes, nonPoiCandidates, + rc, astForErr, call, ci, inScopes, nonPoiCandidates, poiCandidates, lrcGroups.getForwardingGroups(), rejected); @@ -4545,40 +4580,42 @@ findMostSpecificAndCheck(Context* context, static MostSpecificCandidates -resolveFnCallFilterAndFindMostSpecific(Context* context, +resolveFnCallFilterAndFindMostSpecific(ResolutionContext* rc, const AstNode* astForErr, const Call* call, const CallInfo& ci, const CallScopeInfo& inScopes, PoiInfo& poiInfo, std::vector* rejected) { + Context* context = rc->context(); // search for candidates at each POI until we have found candidate(s) size_t firstPoiCandidate = 0; - CandidatesAndForwardingInfo candidates = gatherAndFilterCandidates( - context, astForErr, call, ci, inScopes, firstPoiCandidate, rejected); - + auto candidates = gatherAndFilterCandidates(rc, astForErr, call, ci, + inScopes, + firstPoiCandidate, + rejected); // * find most specific candidates / disambiguate // * check signatures // * gather POI info - - MostSpecificCandidates mostSpecific = - findMostSpecificAndCheck(context, candidates, firstPoiCandidate, call, ci, - inScopes.callScope(), inScopes.poiScope(), - poiInfo); + auto mostSpecific = + findMostSpecificAndCheck(context, candidates, firstPoiCandidate, call, ci, + inScopes.callScope(), inScopes.poiScope(), + poiInfo); return mostSpecific; } // call can be nullptr. in that event ci.name() will be used to find // what is called. -static -CallResolutionResult resolveFnCall(Context* context, - const AstNode* astForErr, - const Call* call, - const CallInfo& ci, - const CallScopeInfo& inScopes, - std::vector* rejected) { +static CallResolutionResult +resolveFnCall(ResolutionContext* rc, + const AstNode* astForErr, + const Call* call, + const CallInfo& ci, + const CallScopeInfo& inScopes, + std::vector* rejected) { + Context* context = rc->context(); PoiInfo poiInfo; MostSpecificCandidates mostSpecific; @@ -4596,7 +4633,8 @@ CallResolutionResult resolveFnCall(Context* context, // * filter and instantiate // * disambiguate // * note any most specific candidates from POI in poiInfo. - mostSpecific = resolveFnCallFilterAndFindMostSpecific(context, astForErr, call, ci, + mostSpecific = resolveFnCallFilterAndFindMostSpecific(rc, astForErr, + call, ci, inScopes, poiInfo, rejected); } @@ -4605,45 +4643,49 @@ CallResolutionResult resolveFnCall(Context* context, // figure out the poiScope to use const PoiScope* instantiationPoiScope = nullptr; - bool anyInstantiated = false; - for (const MostSpecificCandidate& candidate : mostSpecific) { - if (candidate && candidate.fn()->instantiatedFrom() != nullptr) { - anyInstantiated = true; - break; + if (candidate && candidate.fn()) { + instantiationPoiScope = + Resolver::poiScopeOrNull(context, candidate.fn(), + inScopes.callScope(), + inScopes.poiScope()); + if (instantiationPoiScope) break; } + if (instantiationPoiScope) break; } - if (anyInstantiated) { - instantiationPoiScope = - pointOfInstantiationScope(context, inScopes.callScope(), inScopes.poiScope()); + if (instantiationPoiScope) { poiInfo.setPoiScope(instantiationPoiScope); - for (const MostSpecificCandidate& candidate : mostSpecific) { if (candidate) { if (candidate.fn()->untyped()->idIsFunction()) { // note: following call returns early if candidate not instantiated - accumulatePoisUsedByResolvingBody(context, candidate.fn(), - instantiationPoiScope, poiInfo); + accumulatePoisUsedByResolvingBody(rc, candidate.fn(), + instantiationPoiScope, + poiInfo); } } } } // infer types of generic 'out' formals from function bodies - mostSpecific.inferOutFormals(context, instantiationPoiScope); + mostSpecific.inferOutFormals(rc, instantiationPoiScope); // Make sure that we are resolving initializer bodies even when the // signature is concrete, because there are semantic checks. - if (isCallInfoForInitializer(ci) && mostSpecific.numBest() == 1) { + bool isCallInfoForInit = (ci.name() == USTR("init") || + ci.name() == USTR("init=")) && + ci.isMethodCall(); + if (isCallInfoForInit && mostSpecific.numBest() == 1) { auto candidateFn = mostSpecific.only().fn(); - CHPL_ASSERT(isTfsForInitializer(candidateFn)); + CHPL_ASSERT(candidateFn->isInitializer()); // TODO: Can we move this into the 'InitVisitor'? - // Note: resolveInitializer is already called during instantiation + // Note: resolveFunction is already called on the initializer during + // instantiation if (!candidateFn->untyped()->isCompilerGenerated() && candidateFn->instantiatedFrom() == nullptr) { - std::ignore = resolveInitializer(context, candidateFn, inScopes.poiScope()); + std::ignore = resolveFunction(rc, candidateFn, inScopes.poiScope()); } } @@ -4652,7 +4694,8 @@ CallResolutionResult resolveFnCall(Context* context, bool retTypeSet = false; for (const MostSpecificCandidate& candidate : mostSpecific) { if (candidate.fn() != nullptr) { - QualifiedType t = returnType(context, candidate.fn(), instantiationPoiScope); + QualifiedType t = returnType(rc, candidate.fn(), + instantiationPoiScope); if (retTypeSet && retType.type() != t.type()) { context->error(candidate.fn(), nullptr, @@ -4746,11 +4789,12 @@ static bool shouldAttemptImplicitReceiver(const CallInfo& ci, ci.name() != USTR("unmanaged"); } -CallResolutionResult resolveCall(Context* context, +CallResolutionResult resolveCall(ResolutionContext* rc, const Call* call, const CallInfo& ci, const CallScopeInfo& inScopes, std::vector* rejected) { + Context* context = rc->context(); if (call->isFnCall() || call->isOpCall()) { // see if the call is handled directly by the compiler QualifiedType tmpRetType; @@ -4767,9 +4811,9 @@ CallResolutionResult resolveCall(Context* context, } // otherwise do regular call resolution - return resolveFnCall(context, call, call, ci, inScopes, rejected); + return resolveFnCall(rc, call, call, ci, inScopes, rejected); } else if (auto prim = call->toPrimCall()) { - return resolvePrimCall(context, prim, ci, inScopes.callScope(), inScopes.poiScope()); + return resolvePrimCall(rc, prim, ci, inScopes.callScope(), inScopes.poiScope()); } else if (auto tuple = call->toTuple()) { return resolveTupleExpr(context, tuple, ci, inScopes.callScope(), inScopes.poiScope()); } @@ -4787,26 +4831,26 @@ CallResolutionResult resolveCall(Context* context, return CallResolutionResult(emptyCandidates, emptyType, emptyPoi); } -CallResolutionResult resolveCallInMethod(Context* context, - const Call* call, - const CallInfo& ci, - const CallScopeInfo& inScopes, - QualifiedType implicitReceiver, - std::vector* rejected) { - +CallResolutionResult +resolveCallInMethod(ResolutionContext* rc, + const Call* call, + const CallInfo& ci, + const CallScopeInfo& inScopes, + QualifiedType implicitReceiver, + std::vector* rejected) { // If there is an implicit receiver and ci isn't written as a method, // construct a method call and use that instead. If that resolves, // it takes precedence over functions. if (shouldAttemptImplicitReceiver(ci, implicitReceiver)) { auto methodCi = CallInfo::createWithReceiver(ci, implicitReceiver); - auto ret = resolveCall(context, call, methodCi, inScopes, rejected); + auto ret = resolveCall(rc, call, methodCi, inScopes, rejected); if (ret.mostSpecific().foundCandidates()) { return ret; } } // otherwise, use normal resolution - return resolveCall(context, call, ci, inScopes, rejected); + return resolveCall(rc, call, ci, inScopes, rejected); } CallResolutionResult resolveGeneratedCall(Context* context, @@ -4820,7 +4864,9 @@ CallResolutionResult resolveGeneratedCall(Context* context, return CallResolutionResult(std::move(tmpRetType)); } // otherwise do regular call resolution - return resolveFnCall(context, astForErr, /* call */ nullptr, ci, inScopes, rejected); + const Call* call = nullptr; + ResolutionContext rcval(context); + return resolveFnCall(&rcval, astForErr, call, ci, inScopes, rejected); } CallResolutionResult diff --git a/frontend/lib/resolution/resolution-types.cpp b/frontend/lib/resolution/resolution-types.cpp index 47d238f2f6c7..1853c691c794 100644 --- a/frontend/lib/resolution/resolution-types.cpp +++ b/frontend/lib/resolution/resolution-types.cpp @@ -46,33 +46,6 @@ namespace resolution { using namespace uast; using namespace types; -void OuterVariables::add(Context* context, ID mention, ID var) { - ID mentionParent = mention.parentSymbolId(context); - ID symbolParent = symbol_.parentSymbolId(context); - ID varParent = var.parentSymbolId(context); - const bool isReachingUse = symbolParent != varParent; - const bool isChildUse = mentionParent != symbol_; - - CHPL_ASSERT(varParent != symbol_); - if (!isReachingUse) { - CHPL_ASSERT(mention && symbol_.contains(mention)); - } - - auto it = idToVarAndMentionIndices_.find(var); - if (it == idToVarAndMentionIndices_.end()) { - auto p = std::make_pair(variables_.size(), std::vector()); - it = idToVarAndMentionIndices_.emplace_hint(it, var, std::move(p)); - variables_.push_back(var); - if (isReachingUse) numReachingVariables_++; - } - - // Don't bother storing the mention for a child use. - if (!isChildUse) { - it->second.second.push_back(mentions_.size()); - mentions_.push_back(mention); - } -} - const owned& UntypedFnSignature::getUntypedFnSignature(Context* context, ID id, UniqueString name, @@ -870,11 +843,13 @@ TypedFnSignature::getTypedFnSignature(Context* context, bool isRefinementOnly, const TypedFnSignature* instantiatedFrom, const TypedFnSignature* parentFn, - Bitmap formalsInstantiated) { + Bitmap formalsInstantiated, + OuterVariables outerVariables) { QUERY_BEGIN(getTypedFnSignature, context, untypedSignature, formalTypes, whereClauseResult, needsInstantiation, isRefinementOnly, instantiatedFrom, parentFn, - formalsInstantiated); + formalsInstantiated, + outerVariables); auto result = toOwned(new TypedFnSignature(untypedSignature, std::move(formalTypes), @@ -883,7 +858,8 @@ TypedFnSignature::getTypedFnSignature(Context* context, isRefinementOnly, instantiatedFrom, parentFn, - std::move(formalsInstantiated))); + std::move(formalsInstantiated), + std::move(outerVariables))); return QUERY_END(result); } @@ -896,7 +872,8 @@ TypedFnSignature::get(Context* context, bool needsInstantiation, const TypedFnSignature* instantiatedFrom, const TypedFnSignature* parentFn, - Bitmap formalsInstantiated) { + Bitmap formalsInstantiated, + OuterVariables outerVariables) { return getTypedFnSignature(context, untypedSignature, std::move(formalTypes), whereClauseResult, @@ -904,7 +881,8 @@ TypedFnSignature::get(Context* context, /* isRefinementOnly */ false, instantiatedFrom, parentFn, - std::move(formalsInstantiated)).get(); + std::move(formalsInstantiated), + std::move(outerVariables)).get(); } const TypedFnSignature* @@ -920,7 +898,8 @@ TypedFnSignature::getInferred( /* isRefinementOnly */ true, inferredFrom->inferredFrom(), inferredFrom->parentFn(), - inferredFrom->formalsInstantiatedBitmap()).get(); + inferredFrom->formalsInstantiatedBitmap(), + inferredFrom->outerVariables()).get(); } @@ -1099,12 +1078,12 @@ void MostSpecificCandidate::stringify(std::ostream& ss, } void -MostSpecificCandidates::inferOutFormals(Context* context, +MostSpecificCandidates::inferOutFormals(ResolutionContext* rc, const PoiScope* instantiationPoiScope) { for (int i = 0; i < NUM_INTENTS; i++) { - MostSpecificCandidate& c = candidates[i]; - if (c) { - c.fn_ = chpl::resolution::inferOutFormals(context, c.fn(), instantiationPoiScope); + if (MostSpecificCandidate& c = candidates[i]) { + constexpr auto f = chpl::resolution::inferOutFormals; + c.fn_ = f(rc, c.fn(), instantiationPoiScope); } } } @@ -1261,11 +1240,8 @@ static const ID& methodReceiverTypeIdForMethodId(Context* context, } else { // Resolve the method receiver to an ID ResolutionResultByPostorderID r; - owned outerVars = - toOwned(new OuterVariables(context, methodId)); auto visitor = - Resolver::createForScopeResolvingFunction(context, fn, r, - std::move(outerVars)); + Resolver::createForScopeResolvingFunction(context, fn, r); const AstNode* typeExpr = nullptr; if (auto thisFormal = fn->thisFormal()) { @@ -1495,7 +1471,8 @@ TypedMethodLookupHelper::receiverScopes() const { bool TypedMethodLookupHelper::isReceiverApplicable(Context* context, const ID& methodId) const { - const TypedFnSignature* tfs = typedSignatureInitialForId(context, methodId); + ResolutionContext rcval(context); + const TypedFnSignature* tfs = typedSignatureInitialForId(&rcval, methodId); if (tfs && tfs->isMethod()) { QualifiedType methodRcvType = tfs->formalType(0); @@ -1552,8 +1529,8 @@ ReceiverScopeTypedHelper::methodLookupForMethodId(Context* context, if (resolvingMethodId_ == methodId) { return methodLookupForType(context, resolvingMethodReceiverType_); } else { - const TypedFnSignature* tfs = nullptr; - tfs = typedSignatureInitialForId(context, methodId); + ResolutionContext rcval(context); + const TypedFnSignature* tfs = typedSignatureInitialForId(&rcval, methodId); if (tfs && tfs->isMethod()) { QualifiedType rcvType = tfs->formalType(0); return methodLookupForType(context, rcvType); @@ -1563,7 +1540,6 @@ ReceiverScopeTypedHelper::methodLookupForMethodId(Context* context, return nullptr; } - IMPLEMENT_DUMP(PoiInfo); IMPLEMENT_DUMP(UntypedFnSignature); IMPLEMENT_DUMP(UntypedFnSignature::FormalDetail); diff --git a/frontend/lib/resolution/return-type-inference.cpp b/frontend/lib/resolution/return-type-inference.cpp index 9b3c48f00f7f..f9b767c53146 100644 --- a/frontend/lib/resolution/return-type-inference.cpp +++ b/frontend/lib/resolution/return-type-inference.cpp @@ -230,7 +230,8 @@ struct ReturnTypeInferrer { using RV = ResolvedVisitor; // input - Context* context; + ResolutionContext* rc; + Context* context = rc ? rc->context() : nullptr; const Function* fnAstForErr; Function::ReturnIntent returnIntent; Function::Kind functionKind; @@ -242,10 +243,10 @@ struct ReturnTypeInferrer { // output std::vector returnedTypes; - ReturnTypeInferrer(Context* context, + ReturnTypeInferrer(ResolutionContext* rc, const Function* fn, const Type* declaredReturnType) - : context(context), + : rc(rc), fnAstForErr(fn), returnIntent(fn->returnIntent()), functionKind(fn->kind()), @@ -291,7 +292,7 @@ struct ReturnTypeInferrer { void ReturnTypeInferrer::process(const uast::AstNode* symbol, ResolutionResultByPostorderID& byPostorder) { - ResolvedVisitor rv(context, symbol, *this, byPostorder); + ResolvedVisitor rv(rc, symbol, *this, byPostorder); symbol->traverse(rv); } @@ -1073,7 +1074,8 @@ static bool helpComputeReturnType(Context* context, // resolve the return type ResolutionResultByPostorderID resolutionById; - auto visitor = Resolver::createForFunction(context, fn, poiScope, sig, + ResolutionContext rc(context); + auto visitor = Resolver::createForFunction(&rc, fn, poiScope, sig, resolutionById); retType->traverse(visitor); result = resolutionById.byAst(retType).type(); @@ -1135,13 +1137,13 @@ static bool helpComputeReturnType(Context* context, return false; } -const QualifiedType& returnType(Context* context, - const TypedFnSignature* sig, - const PoiScope* poiScope) { - QUERY_BEGIN(returnType, context, sig, poiScope); +static const QualifiedType& returnTypeQuery(ResolutionContext* rc, + const TypedFnSignature* sig, + const PoiScope* poiScope) { + CHPL_RESOLUTION_QUERY_BEGIN(returnTypeQuery, rc, sig, poiScope); + Context* context = rc->context(); const UntypedFnSignature* untyped = sig->untyped(); - QualifiedType result; bool computed = helpComputeReturnType(context, sig, poiScope, result); @@ -1153,26 +1155,33 @@ const QualifiedType& returnType(Context* context, // resolve the function body // resolveFunction will arrange to call computeReturnType // and store the return type in the result. - if (auto rFn = resolveFunction(context, sig, poiScope)) { + if (auto rFn = resolveFunction(rc, sig, poiScope)) { result = rFn->returnType(); } } - return QUERY_END(result); + return CHPL_RESOLUTION_QUERY_END(result); +} + +QualifiedType returnType(ResolutionContext* rc, + const TypedFnSignature* sig, + const PoiScope* poiScope) { + return returnTypeQuery(rc, sig, poiScope); } static const TypedFnSignature* const& -inferOutFormalsQuery(Context* context, +inferOutFormalsQuery(ResolutionContext* rc, const TypedFnSignature* sig, const PoiScope* instantiationPoiScope) { - QUERY_BEGIN(inferOutFormalsQuery, context, sig, instantiationPoiScope); + CHPL_RESOLUTION_QUERY_BEGIN(inferOutFormalsQuery, rc, sig, + instantiationPoiScope); + Context* context = rc->context(); const UntypedFnSignature* untyped = sig->untyped(); - std::vector formalTypes; // resolve the function body - if (auto rFn = resolveFunction(context, sig, instantiationPoiScope)) { + if (auto rFn = resolveFunction(rc, sig, instantiationPoiScope)) { const ResolutionResultByPostorderID& rr = rFn->resolutionById(); int numFormals = sig->numFormals(); @@ -1191,10 +1200,10 @@ inferOutFormalsQuery(Context* context, std::move(formalTypes), sig); - return QUERY_END(result); + return CHPL_RESOLUTION_QUERY_END(result); } -const TypedFnSignature* inferOutFormals(Context* context, +const TypedFnSignature* inferOutFormals(ResolutionContext* rc, const TypedFnSignature* sig, const PoiScope* instantiationPoiScope) { if (sig == nullptr) { @@ -1215,14 +1224,13 @@ const TypedFnSignature* inferOutFormals(Context* context, // also just return 'sig' if the function needs instantiation; // in that case, we can't infer the 'out' formals by resolving the body. if (anyGenericOutFormals && !sig->needsInstantiation()) { - return inferOutFormalsQuery(context, sig, instantiationPoiScope); + return inferOutFormalsQuery(rc, sig, instantiationPoiScope); } else { return sig; } } void computeReturnType(Resolver& resolver) { - QualifiedType returnType; bool computed = helpComputeReturnType(resolver.context, resolver.typedSignature, @@ -1243,7 +1251,7 @@ void computeReturnType(Resolver& resolver) { // infer the return type if (fn->linkage() != Decl::EXTERN) { - auto v = ReturnTypeInferrer(resolver.context, fn, declaredReturnType); + auto v = ReturnTypeInferrer(resolver.rc, fn, declaredReturnType); v.process(fn->body(), resolver.byPostorder); resolver.returnType = v.returnedType(); } diff --git a/frontend/lib/resolution/scope-queries.cpp b/frontend/lib/resolution/scope-queries.cpp index 11cb3d48e81c..3852d09c0903 100644 --- a/frontend/lib/resolution/scope-queries.cpp +++ b/frontend/lib/resolution/scope-queries.cpp @@ -20,6 +20,7 @@ #include "chpl/resolution/scope-queries.h" #include "chpl/resolution/resolution-queries.h" +#include "chpl/framework/ErrorBase.h" #include "chpl/framework/ErrorMessage.h" #include "chpl/framework/global-strings.h" #include "chpl/framework/query-impl.h" diff --git a/frontend/lib/resolution/try-catch-analysis.cpp b/frontend/lib/resolution/try-catch-analysis.cpp index caf553bdceb8..96c0719758a4 100644 --- a/frontend/lib/resolution/try-catch-analysis.cpp +++ b/frontend/lib/resolution/try-catch-analysis.cpp @@ -53,7 +53,8 @@ struct TryCatchAnalyzer { using RV = ResolvedVisitor; // input - Context* context; + ResolutionContext* rc; + Context* context = rc ? rc->context() : nullptr; const AstNode* astForErr; ErrorCheckingMode mode; // need to keep a stack of try nodes and some meta info @@ -64,10 +65,10 @@ struct TryCatchAnalyzer { // maybe no output? std::vector returnedTypes; - TryCatchAnalyzer(Context* context, + TryCatchAnalyzer(ResolutionContext* rc, const AstNode* symbol, ErrorCheckingMode mode) - : context(context), + : rc(rc), astForErr(symbol), mode(mode) { } @@ -289,7 +290,7 @@ struct TryCatchAnalyzer { void TryCatchAnalyzer::process(const uast::AstNode* symbol, ResolutionResultByPostorderID& byPostorder) { - ResolvedVisitor rv(context, symbol, *this, byPostorder); + ResolvedVisitor rv(rc, symbol, *this, byPostorder); // Traverse formals and then the body. This is done here rather // than in enter(Function) because nested functions will have // 'process' called on them separately. @@ -341,11 +342,12 @@ struct TryCatchAnalyzer { return mode; } - void checkThrows(Context* context, ResolutionResultByPostorderID& result, + void checkThrows(ResolutionContext* rc, + ResolutionResultByPostorderID& result, const AstNode* symbol) { if (symbol->isFunction() || symbol->isModule()) { - auto mode = computeErrorCheckingMode(context, symbol); - auto v = TryCatchAnalyzer(context, symbol, mode); + auto mode = computeErrorCheckingMode(rc->context(), symbol); + auto v = TryCatchAnalyzer(rc, symbol, mode); v.process(symbol, result); } } diff --git a/frontend/lib/resolution/try-catch-analysis.h b/frontend/lib/resolution/try-catch-analysis.h index 268e8d802ef7..132933cb08c3 100644 --- a/frontend/lib/resolution/try-catch-analysis.h +++ b/frontend/lib/resolution/try-catch-analysis.h @@ -33,7 +33,8 @@ namespace resolution { struct Resolver; -void checkThrows(Context* context, ResolutionResultByPostorderID& result, +void checkThrows(ResolutionContext* rc, + ResolutionResultByPostorderID& result, const uast::AstNode* symbol); diff --git a/frontend/lib/types/EnumType.cpp b/frontend/lib/types/EnumType.cpp index c7e77f72fde8..52cd457f43c6 100644 --- a/frontend/lib/types/EnumType.cpp +++ b/frontend/lib/types/EnumType.cpp @@ -18,8 +18,10 @@ */ #include "chpl/types/EnumType.h" -#include "chpl/parsing/parsing-queries.h" #include "chpl/framework/query-impl.h" +#include "chpl/parsing/parsing-queries.h" +#include "chpl/uast/Enum.h" +#include "chpl/uast/EnumElement.h" namespace chpl { namespace types { diff --git a/frontend/lib/uast/post-parse-checks.cpp b/frontend/lib/uast/post-parse-checks.cpp index 5614433af12a..0b3c3048ea47 100644 --- a/frontend/lib/uast/post-parse-checks.cpp +++ b/frontend/lib/uast/post-parse-checks.cpp @@ -20,6 +20,7 @@ #include "chpl/uast/post-parse-checks.h" #include "chpl/framework/compiler-configuration.h" +#include "chpl/framework/ErrorBase.h" #include "chpl/framework/global-strings.h" #include "chpl/framework/query-impl.h" #include "chpl/parsing/parser-error.h" diff --git a/frontend/lib/util/clang-integration.cpp b/frontend/lib/util/clang-integration.cpp index c8e6a8a1ae96..e379d78d2497 100644 --- a/frontend/lib/util/clang-integration.cpp +++ b/frontend/lib/util/clang-integration.cpp @@ -19,6 +19,7 @@ #include "chpl/util/clang-integration.h" +#include "chpl/framework/ErrorBase.h" #include "chpl/framework/TemporaryFileResult.h" #include "chpl/framework/query-impl.h" #include "chpl/parsing/parsing-queries.h" diff --git a/frontend/test/ErrorGuard.h b/frontend/test/ErrorGuard.h index d33b9d6a43cf..bb5a1eea393b 100644 --- a/frontend/test/ErrorGuard.h +++ b/frontend/test/ErrorGuard.h @@ -118,6 +118,7 @@ class ErrorGuard { chpl::ErrorWriter::DETAILED, false); for (auto& err : this->errors()) err->write(ew); + std::cout.flush(); } /** The guard destructor will assert that no errors have occurred. */ diff --git a/frontend/test/framework/testQueryEquivalence.cpp b/frontend/test/framework/testQueryEquivalence.cpp index 98a76c49dee4..9fcf0b872b71 100644 --- a/frontend/test/framework/testQueryEquivalence.cpp +++ b/frontend/test/framework/testQueryEquivalence.cpp @@ -63,7 +63,8 @@ static MostSpecificCandidate const& mscQuery(Context* context) { /* needsInstantiation */ false, /* instantiatedFrom */ nullptr, /* parentFn */ nullptr, - /* instantiatedFormals */ Bitmap{}); + /* instantiatedFormals */ Bitmap{}, + {}); auto ci = CallInfo( /* name */ UniqueString::get(context, "foo"), diff --git a/frontend/test/resolution/testClasses.cpp b/frontend/test/resolution/testClasses.cpp index c539d25d66d1..fab0a1807036 100644 --- a/frontend/test/resolution/testClasses.cpp +++ b/frontend/test/resolution/testClasses.cpp @@ -65,8 +65,9 @@ static void test1() { auto methodU = UntypedFnSignature::get(context, method); auto functionU = UntypedFnSignature::get(context, function); - auto methodT = typedSignatureInitial(context, methodU); - auto functionT = typedSignatureInitial(context, functionU); + ResolutionContext rcval(context); + auto methodT = typedSignatureInitial(&rcval, methodU); + auto functionT = typedSignatureInitial(&rcval, functionU); auto it = initialTypeForTypeDecl(context, c->id()); assert(it); diff --git a/frontend/test/resolution/testErrors.cpp b/frontend/test/resolution/testErrors.cpp index 4798b66b77fc..c57421f6230d 100644 --- a/frontend/test/resolution/testErrors.cpp +++ b/frontend/test/resolution/testErrors.cpp @@ -43,16 +43,18 @@ computeAndPrintStuff(Context* context, const ResolvedFunction* inFn, std::set& calledFns, bool scopeResolveOnly) { - + ResolutionContext rcval(context); + auto rc = &rcval; // Scope resolve / resolve concrete functions before printing if (auto fn = ast->toFunction()) { if (scopeResolveOnly) { inFn = scopeResolveFunction(context, fn->id()); } else { auto untyped = UntypedFnSignature::get(context, fn); - auto typed = typedSignatureInitial(context, untyped); + + auto typed = typedSignatureInitial(rc, untyped); if (!typed->needsInstantiation()) { - inFn = resolveFunction(context, typed, nullptr); + inFn = resolveFunction(rc, typed, nullptr); } } } @@ -68,7 +70,7 @@ computeAndPrintStuff(Context* context, if (candidate) { auto sig = candidate.fn(); if (sig->untyped()->idIsFunction()) { - auto fn = resolveFunction(context, sig, r->poiScope()); + auto fn = resolveFunction(rc, sig, r->poiScope()); calledFns.insert(fn); } } diff --git a/frontend/test/resolution/testFunctionCalls.cpp b/frontend/test/resolution/testFunctionCalls.cpp index 376c69243a83..ec52e40c927f 100644 --- a/frontend/test/resolution/testFunctionCalls.cpp +++ b/frontend/test/resolution/testFunctionCalls.cpp @@ -141,6 +141,7 @@ static void test4() { { printf("part 1\n"); context->advanceToNextRevision(true); + ErrorGuard guard(context); const std::string program = R""""( @@ -154,6 +155,7 @@ static void test4() { )""""; auto qt = resolveTypeOfXInit(context, program); + for (auto& e : guard.errors()) std::cout << e->message() << std::endl; assert(qt.type() != nullptr); assert(qt.type()->isIntType()); diff --git a/frontend/test/resolution/testInitSemantics.cpp b/frontend/test/resolution/testInitSemantics.cpp index 14bb15ce7114..a822dcc6ec1d 100644 --- a/frontend/test/resolution/testInitSemantics.cpp +++ b/frontend/test/resolution/testInitSemantics.cpp @@ -1455,7 +1455,7 @@ static void testInitGenericAfterConcrete() { assert(t); assert(t->isUnknownType()); - assert(guard.errors().size() == 2); + assert(guard.errors().size() == 1); assert(guard.error(0)->message() == "unable to instantiate generic type from initializer"); assert(guard.realizeErrors()); diff --git a/frontend/test/resolution/testInteractive.cpp b/frontend/test/resolution/testInteractive.cpp index 0a6d1cf47c03..a04e7a7b9589 100644 --- a/frontend/test/resolution/testInteractive.cpp +++ b/frontend/test/resolution/testInteractive.cpp @@ -53,9 +53,10 @@ static const char* tagToString(const AstNode* ast) { } static const ResolvedExpression* -resolvedExpressionForAstInteractive(Context* context, const AstNode* ast, +resolvedExpressionForAstInteractive(ResolutionContext* rc, const AstNode* ast, const ResolvedFunction* inFn, bool scopeResolveOnly) { + Context* context = rc->context(); if (!(ast->isLoop() || ast->isBlock())) { // compute the parent module or function int postorder = ast->id().postOrderId(); @@ -81,9 +82,9 @@ resolvedExpressionForAstInteractive(Context* context, const AstNode* ast, auto rFn = scopeResolveFunction(context, parentFn->id()); return rFn->byAstOrNull(ast); } else { - auto typed = typedSignatureInitial(context, untyped); + auto typed = typedSignatureInitial(rc, untyped); if (typed != nullptr && !typed->needsInstantiation()) { - if (auto rFn = resolveFunction(context, typed, nullptr)) { + if (auto rFn = resolveFunction(rc, typed, nullptr)) { return rFn->byAstOrNull(ast); } } @@ -108,28 +109,29 @@ resolvedExpressionForAstInteractive(Context* context, const AstNode* ast, } static void -computeAndPrintStuff(Context* context, +computeAndPrintStuff(ResolutionContext* rc, const AstNode* ast, const ResolvedFunction* inFn, std::set& calledFns, bool scopeResolveOnly, int maxIdWidth, bool quiet) { + Context* context = rc->context(); // Scope resolve / resolve concrete functions before printing if (auto fn = ast->toFunction()) { if (scopeResolveOnly) { inFn = scopeResolveFunction(context, fn->id()); } else { auto untyped = UntypedFnSignature::get(context, fn); - auto typed = typedSignatureInitial(context, untyped); + auto typed = typedSignatureInitial(rc, untyped); if (typed != nullptr && !typed->needsInstantiation()) { - inFn = resolveFunction(context, typed, nullptr); + inFn = resolveFunction(rc, typed, nullptr); } } } for (const AstNode* child : ast->children()) { - computeAndPrintStuff(context, child, inFn, calledFns, + computeAndPrintStuff(rc, child, inFn, calledFns, scopeResolveOnly, maxIdWidth, quiet); if (!quiet) { if (child->isModule() || child->isFunction()) { @@ -140,14 +142,14 @@ computeAndPrintStuff(Context* context, int beforeCount = context->numQueriesRunThisRevision(); const ResolvedExpression* r = - resolvedExpressionForAstInteractive(context, ast, inFn, scopeResolveOnly); + resolvedExpressionForAstInteractive(rc, ast, inFn, scopeResolveOnly); int afterCount = context->numQueriesRunThisRevision(); if (r != nullptr) { for (const MostSpecificCandidate& candidate : r->mostSpecific()) { if (candidate) { auto sig = candidate.fn(); if (sig->untyped()->idIsFunction()) { - auto fn = resolveFunction(context, sig, r->poiScope()); + auto fn = resolveFunction(rc, sig, r->poiScope()); calledFns.insert(fn); } } @@ -156,7 +158,7 @@ computeAndPrintStuff(Context* context, auto sig = a.fn(); if (sig != nullptr) { if (sig->untyped()->idIsFunction()) { - auto fn = resolveFunction(context, sig, r->poiScope()); + auto fn = resolveFunction(rc, sig, r->poiScope()); calledFns.insert(fn); } } @@ -279,6 +281,8 @@ int main(int argc, char** argv) { Context context(config); Context* ctx = &context; context.setDetailedErrorOutput(!brief); + ResolutionContext rcval(ctx); + auto rc = &rcval; if (files.size() == 0) { usage(argc, argv); @@ -312,7 +316,7 @@ int main(int argc, char** argv) { } int maxIdWidth = mod->computeMaxIdStringWidth(); - computeAndPrintStuff(ctx, mod, nullptr, calledFns, + computeAndPrintStuff(rc, mod, nullptr, calledFns, scopeResolveOnly, maxIdWidth, quiet); if (!quiet) { printf("\n"); @@ -342,7 +346,7 @@ int main(int argc, char** argv) { auto ast = idToAst(ctx, sig->id()); auto fn = ast->toFunction(); auto uSig = UntypedFnSignature::get(ctx, fn); - auto initialType = typedSignatureInitial(ctx, uSig); + auto initialType = typedSignatureInitial(rc, uSig); int maxIdWidth = 0; if (!quiet) { printf("Instantiation of "); @@ -353,7 +357,7 @@ int main(int argc, char** argv) { printf("\n"); maxIdWidth = ast->computeMaxIdStringWidth(); } - computeAndPrintStuff(ctx, ast, calledFn, calledFns, + computeAndPrintStuff(rc, ast, calledFn, calledFns, scopeResolveOnly, maxIdWidth, quiet); if (!quiet) { printf("\n"); diff --git a/frontend/test/resolution/testLoopIndexVars.cpp b/frontend/test/resolution/testLoopIndexVars.cpp index 138b65b1b2b9..fd80f401fe55 100644 --- a/frontend/test/resolution/testLoopIndexVars.cpp +++ b/frontend/test/resolution/testLoopIndexVars.cpp @@ -315,6 +315,8 @@ static void testCForLoop() { Context ctx; Context* context = &ctx; ErrorGuard guard(context); + ResolutionContext rcval(context); + auto rc = &rcval; auto iterText = R""""( iter myIter() { @@ -340,7 +342,7 @@ static void testCForLoop() { assert(idx.type().type() == IntType::get(context, 0)); const TypedFnSignature* sig = rr.byAst(loop->iterand()).mostSpecific().only().fn(); - auto fn = resolveFunction(context, sig, nullptr); + auto fn = resolveFunction(rc, sig, nullptr); auto rf = fn->resolutionById(); auto whileLoop = m->stmt(0)->toFunction()->stmt(1)->toWhile(); auto cond = rf.byAst(whileLoop->condition()); @@ -352,7 +354,7 @@ static void testParamFor() { Context ctx; Context* context = &ctx; ErrorGuard guard(context); - + ResolutionContext rcval(context); // // Test iteration over an iterator call // @@ -369,7 +371,7 @@ static void testParamFor() { const ResolutionResultByPostorderID& rr = resolveModule(context, m->id()); ParamCollector pc; - ResolvedVisitor rv(context, m, pc, rr); + ResolvedVisitor rv(&rcval, m, pc, rr); m->traverse(rv); const ResolvedExpression& re = rr.byAst(m->stmt(0)); @@ -397,6 +399,8 @@ static void testNestedParamFor() { Context ctx; Context* context = &ctx; ErrorGuard guard(context); + ResolutionContext rcval(context); + auto rc = &rcval; auto loopText = R"""( var sum = 0; @@ -415,7 +419,7 @@ static void testNestedParamFor() { const ResolutionResultByPostorderID& rr = resolveModule(context, m->id()); ParamCollector pc; - ResolvedVisitor rv(context, m, pc, rr); + ResolvedVisitor rv(rc, m, pc, rr); m->traverse(rv); const ResolvedExpression& re = rr.byAst(m->stmt(0)); diff --git a/frontend/test/resolution/testMaybeConst.cpp b/frontend/test/resolution/testMaybeConst.cpp index 1e2e3562810f..1a0669dcdc72 100644 --- a/frontend/test/resolution/testMaybeConst.cpp +++ b/frontend/test/resolution/testMaybeConst.cpp @@ -43,6 +43,9 @@ testMaybeRef(const char* test, Context* context = &ctx; ErrorGuard guard(context); + ResolutionContext rcval(context); + auto rc = &rcval; + std::string testname = test; testname += ".chpl"; auto path = UniqueString::get(context, testname); @@ -67,7 +70,7 @@ testMaybeRef(const char* test, auto parentAst = idToAst(context, parentId); assert(parentAst && parentAst->isFunction()); const ResolvedFunction* r = resolveConcreteFunction(context, parentId); - auto sig = inferRefMaybeConstFormals(context, + auto sig = inferRefMaybeConstFormals(rc, r->signature(), /* poiScope */ nullptr); auto untyped = sig->untyped(); diff --git a/frontend/test/resolution/testMethodCalls.cpp b/frontend/test/resolution/testMethodCalls.cpp index fca3cea8df69..b6b0e1d2e574 100644 --- a/frontend/test/resolution/testMethodCalls.cpp +++ b/frontend/test/resolution/testMethodCalls.cpp @@ -592,6 +592,7 @@ static void test11() { assert(!guard.realizeErrors()); for (auto& [name, var] : vars) { + std::ignore = name; assert(var.type()); assert(var.type()->isIntType()); } diff --git a/frontend/test/resolution/testNestedFunctions.cpp b/frontend/test/resolution/testNestedFunctions.cpp index 46d6068b5d25..42a6ed6da3e6 100644 --- a/frontend/test/resolution/testNestedFunctions.cpp +++ b/frontend/test/resolution/testNestedFunctions.cpp @@ -38,166 +38,433 @@ turnOnWarnUnstable(Context* ctx) { return ctx; } +// This test demonstrates that it is safe to resolve a nested function like +// normal if the function does not refer to outer variables, regardless of +// whether or not the function is generic or concrete. static void test0(void) { Context context; Context* ctx = turnOnWarnUnstable(&context); ErrorGuard guard(ctx); - auto path = TEST_NAME(ctx); - std::cout << path.c_str() << std::endl; + // This snippet is a mockup of code from the internal modules. + std::string program = + R""""( + proc isEnumType(type t) param { + proc isEnumHelp(type t: enum) param do return true; + proc isEnumHelp(type t) param do return false; + return isEnumHelp(t); + } + + enum colors { red, green, blue } + + proc foo(arg) { + if isEnumType(arg.type) { + return 42; + } else { + return "hello"; + } + } + + var color = colors.red; + var x = foo(color); + )""""; - std::string contents = + auto qt = resolveQualifiedTypeOfX(ctx, program); + assert(!guard.realizeErrors()); + assert(qt.kind() == QualifiedType::VAR); + assert(qt.type() && qt.type()->isIntType()); +} + +// Very simple test exercising nested functions with outer variables. +static void test1(void) { + Context context; + Context* ctx = turnOnWarnUnstable(&context); + ErrorGuard guard(ctx); + + std::string program = R""""( - var global = 0; proc foo() { - var x = 0; var y = 0; var z = 0; + const a: real; + proc bar() { return a; } + return bar(); + } + var x = foo(); + )""""; + + auto qt = resolveQualifiedTypeOfX(ctx, program); + assert(!guard.realizeErrors()); + assert(qt.kind() == QualifiedType::VAR); + assert(qt.type() && qt.type()->isRealType()); +} + +// We should be able to call the sibling nested function 'ding' within the +// child nested function 'baz'. The nested functions use outer variables in +// their bodies _and_ their signatures. +static void test2(void) { + Context context; + Context* ctx = turnOnWarnUnstable(&context); + ErrorGuard guard(ctx); + + std::string program = + R""""( + proc foo(param x) { + param y = 16; + type z = x.type; + var a: int = 0; + + proc ding(b: z) { x; y; z; a; b; return b; } + proc bar() { - var a = 0; var b = 0; - x; y; z; - proc baz() { - a; b; x; y; z; global; - a; b; x; y; z; global; - return 0; + var b: z; + var c = "foo"; + + proc baz(param g: b.type, h: c.type) where y == 16 { + x; y; z; a; b; c; g; h; + return ding(b); } - return baz(); + + return baz(x, "foo"); } + return bar(); } + + enum e { a, b, c } + + var x0 = foo(8); + var x1 = foo(8.0); + var x2 = foo("8"); + var x3 = foo(e.b); )""""; - setFileText(ctx, path, contents); - - // Get the top module. - auto& br = parseAndReportErrors(ctx, path); - assert(br.numTopLevelExpressions() == 1); - auto mod = br.topLevelExpression(0)->toModule(); - assert(mod && mod->numStmts() == 2); - auto globalVar = mod->stmt(0)->toVariable(); - - // Get the 'foo' function and its declared variables. - auto fooFn = mod->stmt(1)->toFunction(); - assert(fooFn && fooFn->numStmts() == 5); - auto xVar = fooFn->stmt(0)->toVariable(); - auto yVar = fooFn->stmt(1)->toVariable(); - auto zVar = fooFn->stmt(2)->toVariable(); - assert(xVar && yVar && zVar); - - // Get the 'bar' function and its declared variables. - auto barFn = fooFn->stmt(3)->toFunction(); - assert(barFn && barFn->numStmts() == 7); - auto aVar = barFn->stmt(0)->toVariable(); - auto bVar = barFn->stmt(1)->toVariable(); - - // Get the 'baz' function. - auto bazFn = barFn->stmt(5)->toFunction(); - assert(bazFn && bazFn->numStmts() == 13); - - // The 'foo' function should have no outer variables. - assert(computeOuterVariables(ctx, fooFn->id()) == nullptr); - - auto ovsBarPtr = computeOuterVariables(ctx, barFn->id()); - assert(ovsBarPtr); - auto& ovsBar = *ovsBarPtr; - assert(ovsBar.numVariables() == 3); - assert(ovsBar.numMentions() == 3); - - assert(ovsBar.numMentions(xVar->id()) == 1); - assert(ovsBar.firstMention(xVar->id()) == barFn->stmt(2)->id()); - - assert(ovsBar.numMentions(yVar->id()) == 1); - assert(ovsBar.firstMention(yVar->id()) == barFn->stmt(3)->id()); - - assert(ovsBar.numMentions(zVar->id()) == 1); - assert(ovsBar.firstMention(zVar->id()) == barFn->stmt(4)->id()); - - // Not reaching as declared directly in parent. - assert(!ovsBar.isReachingVariable(xVar->id())); - assert(!ovsBar.isReachingVariable(yVar->id())); - assert(!ovsBar.isReachingVariable(zVar->id())); - - auto ovsBazPtr = computeOuterVariables(ctx, bazFn->id()); - assert(ovsBazPtr); - auto& ovsBaz = *ovsBazPtr; - assert(ovsBaz.numVariables() == 5); - assert(ovsBaz.numMentions() == 10); - - assert(ovsBaz.variable(0) == aVar->id()); - assert(ovsBaz.numMentions(aVar->id()) == 2); - assert(ovsBaz.mention(aVar->id(), 0) == bazFn->stmt(0)->id()); - assert(ovsBaz.mention(aVar->id(), 1) == bazFn->stmt(6)->id()); - - assert(ovsBaz.variable(1) == bVar->id()); - assert(ovsBaz.numMentions(bVar->id()) == 2); - assert(ovsBaz.mention(bVar->id(), 0) == bazFn->stmt(1)->id()); - assert(ovsBaz.mention(bVar->id(), 1) == bazFn->stmt(7)->id()); - - assert(ovsBaz.variable(2) == xVar->id()); - assert(ovsBaz.numMentions(xVar->id()) == 2); - assert(ovsBaz.mention(xVar->id(), 0) == bazFn->stmt(2)->id()); - assert(ovsBaz.mention(xVar->id(), 1) == bazFn->stmt(8)->id()); - - assert(ovsBaz.variable(3) == yVar->id()); - assert(ovsBaz.numMentions(yVar->id()) == 2); - assert(ovsBaz.mention(yVar->id(), 0) == bazFn->stmt(3)->id()); - assert(ovsBaz.mention(yVar->id(), 1) == bazFn->stmt(9)->id()); - - assert(ovsBaz.variable(4) == zVar->id()); - assert(ovsBaz.numMentions(zVar->id()) == 2); - assert(ovsBaz.mention(zVar->id(), 0) == bazFn->stmt(4)->id()); - assert(ovsBaz.mention(zVar->id(), 1) == bazFn->stmt(10)->id()); - - // Reaching because 'baz' has to reach across 'bar' into 'foo'. - assert(ovsBaz.isReachingVariable(xVar->id())); - assert(ovsBaz.isReachingVariable(yVar->id())); - assert(ovsBaz.isReachingVariable(zVar->id())); - - // Module variables are not recorded as outer variables at the moment. - assert(!ovsBaz.mentions(globalVar->id())); - assert(ovsBaz.numMentions(globalVar->id()) == 0); + auto m = resolveTypesOfVariables(ctx, program, { "x0", "x1", "x2", "x3" }); assert(!guard.realizeErrors()); + + auto& x0 = m["x0"]; + assert(x0.kind() == QualifiedType::VAR); + assert(x0.type() && x0.type()->isIntType()); + auto& x1 = m["x1"]; + assert(x1.kind() == QualifiedType::VAR); + assert(x1.type() && x1.type()->isRealType()); + auto& x2 = m["x2"]; + assert(x2.kind() == QualifiedType::VAR); + assert(x2.type() && x2.type()->isStringType()); + auto& x3 = m["x3"]; + assert(x3.kind() == QualifiedType::VAR); + assert(x3.type() && x3.type()->isEnumType()); } -// This test demonstrates that it is safe to resolve a nested function like -// normal if the function does not refer to outer variables, regardless of -// whether or not the function is generic or concrete. -static void test1(void) { +// This goofy test tries to make sure that nested functions in instantiations +// behave properly. The outermost function 'foo' has multiple instantiations, +// which 'baz' is indirectly dependent on (uses of the outer variable 'a'). +// Then 'baz' is used to instantiate the nested function 'bar'. +static void test3(void) { Context context; Context* ctx = turnOnWarnUnstable(&context); ErrorGuard guard(ctx); - auto path = TEST_NAME(ctx); - std::cout << path.c_str() << std::endl; + std::string program = + R""""( + proc foo(type a) { + const v0 = 0; + const v1 = 0.0; + const v2 = "0"; + const v3 = new shared C?(); + const v4 = new r(); + const v5 = e.a; + proc bar(param n) { + var tup: (v0.type, v1.type, v2.type, v3.type, v4.type, v5.type); + return tup[n]; + } + proc baz() { + if a == v0.type then return bar(0); + else if a == v1.type then return bar(1); + else if a == v2.type then return bar(2); + else if a == v3.type then return bar(3); + else if a == v4.type then return bar(4); + else if a == v5.type then return bar(5); + else return 8000; + } + return baz(); + } + enum e { a, b, c } + record r {} + class C {} + var x0 = foo(int); + var x1 = foo(real); + var x2 = foo(string); + var x3 = foo(shared C?); + var x4 = foo(r); + var x5 = foo(e); + )""""; + + auto m = resolveTypesOfVariables(ctx, program, { "x0", "x1", "x2", "x3", "x4", "x5"}); + assert(!guard.realizeErrors()); + auto& x0 = m["x0"]; + assert(x0.kind() == QualifiedType::VAR); + assert(x0.type() && x0.type()->isIntType()); + auto& x1 = m["x1"]; + assert(x1.kind() == QualifiedType::VAR); + assert(x1.type() && x1.type()->isRealType()); + auto& x2 = m["x2"]; + assert(x2.kind() == QualifiedType::VAR); + assert(x2.type() && x2.type()->isStringType()); + auto& x3 = m["x3"]; + assert(x3.kind() == QualifiedType::VAR); + assert(x3.type() && x3.type()->isClassType()); + auto& x4 = m["x4"]; + assert(x4.kind() == QualifiedType::VAR); + assert(x4.type() && x4.type()->isRecordType()); + auto& x5 = m["x5"]; + assert(x5.kind() == QualifiedType::VAR); + assert(x5.type() && x5.type()->isEnumType()); +} + +// Interaction between outer variables and generic constraints. +static void test4(void) { + Context context; + Context* ctx = turnOnWarnUnstable(&context); + ErrorGuard guard(ctx); - // This snippet is a mockup of code from the internal modules. std::string program = R""""( - proc isEnumType(type t) param { - proc isEnumHelp(type t: enum) param do return true; - proc isEnumHelp(type t) param do return false; - return isEnumHelp(t); + // Toggle to constrain. + proc foo(a, param x: bool) { + proc bar(b: if x then integral else ?) { return b; } + return bar(a); } + var x0 = foo(0, false); + var x1 = foo("0", false); + var x2 = foo(0, true); + var x3 = foo("0", true); + )""""; - enum colors { red, green, blue } + auto m = resolveTypesOfVariables(ctx, program, { "x0", "x1", "x2", "x3" }); + + assert(guard.numErrors() == 2); + auto& e = guard.errors(); + assert(e[0]->type() == chpl::NoMatchingCandidates); + assert(e[1]->message() == "Cannot establish type for call expression"); + guard.clearErrors(); + + auto& x0 = m["x0"]; + assert(x0.kind() == QualifiedType::VAR); + assert(x0.type() && x0.type()->isIntType()); + auto& x1 = m["x1"]; + assert(x1.kind() == QualifiedType::VAR); + assert(x1.type() && x1.type()->isStringType()); + auto& x2 = m["x2"]; + assert(x2.kind() == QualifiedType::VAR); + assert(x2.type() && x2.type()->isIntType()); + auto& x3 = m["x3"]; + assert(x3.kind() == QualifiedType::VAR); + assert(x3.type() && x3.type()->isErroneousType()); +} - proc foo(arg) { - if isEnumType(arg.type) { - return 42; - } else { - return "hello"; +// This is private issue #6022. It tests a case where a nested function uses +// a field accessible through a outer method's receiver. +static void test5(void) { + Context context; + Context* ctx = turnOnWarnUnstable(&context); + ErrorGuard guard(ctx); + + std::string program = + R""""( + record R { + type T; + var x : T; + + proc foobar() { + proc helper(arg: T) { // Error for 'T' ! + var y: x.type; + return y; + } + return helper(x); } } - var color = colors.red; - var x = foo(color); + var r : R(int); + var x = r.foobar(); )""""; auto qt = resolveQualifiedTypeOfX(ctx, program); + assert(!guard.realizeErrors()); + assert(qt.kind() == QualifiedType::VAR); + assert(qt.type() && qt.type()->isIntType()); +} + +// Same as test5 but with a nested method instead of a nested function. +static void test6(void) { + Context context; + Context* ctx = turnOnWarnUnstable(&context); + ErrorGuard guard(ctx); + + std::string program = + R""""( + record R { + type T; + var x : T; + + proc foobar() { + proc R.helper(arg: T) { var y: T; return y; } + return helper(x); + } + } + + var r : R(int); + var x = r.foobar(); + )""""; + + auto qt = resolveQualifiedTypeOfX(ctx, program); + assert(!guard.realizeErrors()); + assert(qt.kind() == QualifiedType::VAR); + assert(qt.type() && qt.type()->isIntType()); +} + +// This is not legal, but we should still perform the correct name resolution. +// TODO: Right now, mentions of 'T' in 'helper' are not resolved to the field. +/** +static void test7(void) { + Context context; + Context* ctx = turnOnWarnUnstable(&context); + ErrorGuard guard(ctx); + + std::string program = + R""""( + record R { + type T; + var x : T; + + proc foobar() { + record S { + proc helper(arg: T) { var y: T; return y; } + } + var v : S; + return v.helper(x); + } + } + + var r : R(int); + var x = r.foobar(); + )""""; + + auto qt = resolveQualifiedTypeOfX(ctx, program); + assert(guard.numErrors() == 2); + assert(guard.errors()[0]->type() == chpl::NestedClassFieldRef); + assert(guard.errors()[1]->type() == chpl::NestedClassFieldRef); + guard.clearErrors(); + assert(qt.kind() == QualifiedType::VAR); + assert(qt.type() && qt.type()->isIntType()); +} +*/ + +// TODO: It is illegal to use 'x' in the definition of 'S.y', but should +// this program still type as though it would work? Right now it can't +// be typed because the lookup fails. +/* +static void test8(void) { + Context context; + Context* ctx = turnOnWarnUnstable(&context); + ErrorGuard guard(ctx); + + std::string program = + R""""( + record R { + type T; + var x : T; + + proc foobar() { + record S { + var y: x.type; + proc helper() { return y; } + } + var v : S; + return v.helper(); + } + } + + var r : R(int); + var x = r.foobar(); + )""""; + + auto qt = resolveQualifiedTypeOfX(ctx, program); + assert(!guard.realizeErrors()); + assert(qt.kind() == QualifiedType::VAR); + assert(qt.type() && qt.type()->isIntType()); +} +*/ + +static void test9(void) { + Context context; + Context* ctx = turnOnWarnUnstable(&context); + ErrorGuard guard(ctx); + + std::string program = + R""""( + proc helper(type T) param: string { + return T:string + "_hello"; + } + + proc foo(type T) { + extern helper(T) proc foobar(obj: int): int; // should rename to "bool_hello" at codegen + var x: int; + return foobar(x); + } + + var x = foo(bool); + )""""; + + auto qt = resolveQualifiedTypeOfX(ctx, program); + assert(!guard.realizeErrors()); + assert(qt.kind() == QualifiedType::VAR); + assert(qt.type() && qt.type()->isIntType()); +} + +// This is private issue #6123. TODO: Error "type construction call expected". +/* +static void test10(void) { + Context context; + Context* ctx = turnOnWarnUnstable(&context); + ErrorGuard guard(ctx); + + std::string program = + R""""( + proc externT(type T) type { return int; } + + record R { + type valType; + var val : valType; + + proc foo() { + extern proc helper(arg: externT(valType)) : real; + + var dummy : int; + return helper(dummy); + } + } + + var r : R(int); + var x = r.foo(); + )""""; + + auto qt = resolveQualifiedTypeOfX(ctx, program); + assert(!guard.realizeErrors()); assert(qt.kind() == QualifiedType::VAR); assert(qt.type() && qt.type()->isIntType()); } +*/ int main() { test0(); test1(); + test2(); + test3(); + test4(); + test5(); + test6(); + // test7(); + // test8(); + test9(); + // test10(); return 0; } diff --git a/frontend/test/resolution/testParams.cpp b/frontend/test/resolution/testParams.cpp index f2e7e00e9fab..f98b1c207795 100644 --- a/frontend/test/resolution/testParams.cpp +++ b/frontend/test/resolution/testParams.cpp @@ -70,6 +70,8 @@ static void test4() { printf("test4\n"); Context ctx; Context* context = &ctx; + ResolutionContext rcval(context); + auto rc = &rcval; std::string program = R""""( enum myEnum { blue, @@ -123,7 +125,7 @@ static void test4() { assert(bestFn->formalType(0).param()->isEnumParam()); assert(bestFn->formalType(0).param()->toEnumParam()->value().id == greenEnum->id()); const ResolvedFunction* rfn = scopeResolveFunction(context, isBlueFn->id()); - const auto tsi = typedSignatureInitial(context, rfn->signature()->untyped()); + const auto tsi = typedSignatureInitial(rc, rfn->signature()->untyped()); assert(tsi->formalType(0).isParam()); } diff --git a/frontend/test/resolution/testTaskIntents.cpp b/frontend/test/resolution/testTaskIntents.cpp index 04f1a7dfe890..e3c380b5a90f 100644 --- a/frontend/test/resolution/testTaskIntents.cpp +++ b/frontend/test/resolution/testTaskIntents.cpp @@ -122,10 +122,10 @@ struct Collector { const ResolvedExpression& result = rv.byAst(call); if (result.mostSpecific().isEmpty() == false) { const TypedFnSignature* sig = result.mostSpecific().only().fn(); - auto fn = resolveFunction(rv.context(), sig, result.poiScope()); + auto fn = resolveFunction(rv.rc(), sig, result.poiScope()); - ResolvedVisitor newRV(rv.context(), nullptr, *this, fn->resolutionById()); - auto untyped = idToAst(rv.context(), sig->id()); + ResolvedVisitor newRV(rv.rc(), nullptr, *this, fn->resolutionById()); + auto untyped = idToAst(rv.rc()->context(), sig->id()); assert(untyped->id() == sig->id()); untyped->traverse(newRV); } @@ -195,14 +195,15 @@ static void printErrors(const ErrorGuard& guard) { } } -static Collector customHelper(std::string program, Context* context, Module* moduleOut = nullptr, bool fail = false) { +static Collector customHelper(std::string program, ResolutionContext* rc, Module* moduleOut = nullptr, bool fail = false) { + Context* context = rc->context(); ErrorGuard guard(context); const Module* m = parseModule(context, program.c_str()); const ResolutionResultByPostorderID& rr = resolveModule(context, m->id()); Collector pc; - ResolvedVisitor rv(context, m, pc, rr); + ResolvedVisitor rv(rc, m, pc, rr); m->traverse(rv); if (debug) { @@ -230,6 +231,8 @@ static Collector customHelper(std::string program, Context* context, Module* mod // helper for running task intent tests static void kindHelper(Qualifier kind, const std::string& constructName) { Context* context = getNewContext(); + ResolutionContext rcval(context); + auto rc = &rcval; std::string program; program += "var x = 0;\n"; @@ -243,7 +246,7 @@ static void kindHelper(Qualifier kind, const std::string& constructName) { program += " var y = x;\n"; program += "}\n"; - auto col = customHelper(program, context); + auto col = customHelper(program, rc); const auto intType = IntType::get(context, 0); // Test shadow variable type is as expected @@ -307,6 +310,9 @@ static void reduceHelper(const std::string& constructName) { assert(constructName == "forall" || constructName == "coforall"); Context* context = getNewContext(); + ResolutionContext rcval(context); + auto rc = &rcval; + // Very simple test focusing on scope resolution std::string program; program += R"""(operator +=(ref lhs: int, rhs: int) { @@ -320,7 +326,7 @@ var x = 0; x += 1; })"""; - auto col = customHelper(program, context); + auto col = customHelper(program, rc); // Test shadow variable type is as expected { diff --git a/frontend/test/resolution/testTuples.cpp b/frontend/test/resolution/testTuples.cpp index 0ca455dd0df3..71126136c36b 100644 --- a/frontend/test/resolution/testTuples.cpp +++ b/frontend/test/resolution/testTuples.cpp @@ -852,10 +852,11 @@ static const TypedFnSignature* test20Helper(Context* context, std::string progra auto x = M->stmt(M->numStmts()-1)->toVarLikeDecl(); auto call = x->initExpression()->toFnCall(); + ResolutionContext rcval(context); auto r = rr.byAstOrNull(call); auto candidate = r->mostSpecific().only(); auto sig = candidate.fn(); - sig = resolveFunction(context, sig, r->poiScope())->signature(); + sig = resolveFunction(&rcval, sig, r->poiScope())->signature(); return sig; } static void test20() { diff --git a/frontend/test/resolution/testVarArgs.cpp b/frontend/test/resolution/testVarArgs.cpp index b91bf2ca39b2..7433c7fea780 100644 --- a/frontend/test/resolution/testVarArgs.cpp +++ b/frontend/test/resolution/testVarArgs.cpp @@ -121,9 +121,9 @@ struct Collector { const ResolvedExpression& result = rv.byAst(call); if (result.mostSpecific().isEmpty() == false) { const TypedFnSignature* sig = result.mostSpecific().only().fn(); - auto fn = resolveFunction(rv.context(), sig, result.poiScope()); + auto fn = resolveFunction(rv.rc(), sig, result.poiScope()); - ResolvedVisitor newRV(rv.context(), nullptr, *this, fn->resolutionById()); + ResolvedVisitor newRV(rv.rc(), nullptr, *this, fn->resolutionById()); auto untyped = idToAst(rv.context(), sig->id()); assert(untyped->id() == sig->id()); untyped->traverse(newRV); @@ -508,12 +508,14 @@ static void testMatcher(Qualifier formalIntent, Context ctx; Context* context = &ctx; ErrorGuard guard(context); + ResolutionContext rcval(context); + auto rc = &rcval; const Module* m = parseModule(context, program.c_str()); const ResolutionResultByPostorderID& rr = resolveModule(context, m->id()); Collector col; - ResolvedVisitor rv(context, m, col, rr); + ResolvedVisitor rv(rc, m, col, rr); m->traverse(rv); if (guard.errors().size()) { @@ -536,12 +538,14 @@ customHelper(std::string program, bool fail = false, std::vector>* errorsOut=nullptr) { Context* context = getNewContext(); ErrorGuard guard(context); + ResolutionContext rcval(context); + auto rc = &rcval; const Module* m = parseModule(context, program.c_str()); const ResolutionResultByPostorderID& rr = resolveModule(context, m->id()); Collector pc; - ResolvedVisitor rv(context, m, pc, rr); + ResolvedVisitor rv(rc, m, pc, rr); m->traverse(rv); int numErrors = guard.numErrors(); diff --git a/frontend/test/test-resolution.cpp b/frontend/test/test-resolution.cpp index 241affd009a5..34d65ef63cd9 100644 --- a/frontend/test/test-resolution.cpp +++ b/frontend/test/test-resolution.cpp @@ -79,6 +79,10 @@ const ResolvedExpression* resolvedExpressionForAst(Context* context, const AstNode* ast, const ResolvedFunction* inFn, bool scopeResolveOnly) { + // TODO: Use 'inFn' to reconstruct the correct 'ResolutionContext' state. + ResolutionContext rcval(context); + auto rc = &rcval; + if (!(ast->isLoop() || ast->isBlock())) { // compute the parent module or function int postorder = ast->id().postOrderId(); @@ -104,9 +108,12 @@ resolvedExpressionForAst(Context* context, const AstNode* ast, auto rFn = scopeResolveFunction(context, parentFn->id()); return &rFn->resolutionById().byAst(ast); } else { - auto typed = typedSignatureInitial(context, untyped); + auto typed = typedSignatureInitial(rc, untyped); if (!typed->needsInstantiation()) { - auto rFn = resolveFunction(context, typed, nullptr); + // TODO: Detect if this is an interior call or not. + // If it's not, we can rewind frames. + ResolutionContext rcval(context); + auto rFn = resolveFunction(&rcval, typed, nullptr); return &rFn->resolutionById().byAst(ast); } } @@ -312,3 +319,16 @@ Context::Configuration getConfigWithHome() { return config; } + +const ResolvedFunction* resolveOnlyCandidate(Context* context, + const ResolvedExpression& r) { + ResolutionContext rcval(context); + auto rc = &rcval; + auto msc = r.mostSpecific().only(); + if (!msc) return nullptr; + + const TypedFnSignature* sig = msc.fn(); + const PoiScope* poiScope = r.poiScope(); + + return resolveFunction(rc, sig, poiScope); +} diff --git a/frontend/test/test-resolution.h b/frontend/test/test-resolution.h index 037c65449960..cff9427ceeed 100644 --- a/frontend/test/test-resolution.h +++ b/frontend/test/test-resolution.h @@ -79,4 +79,14 @@ QualifiedType getTypeForFirstStmt(Context* context, const std::string& program); Context::Configuration getConfigWithHome(); +/** + Returns the ResolvedFunction called by a particular + ResolvedExpression, if there was exactly one candidate. + Otherwise, it returns nullptr. + + This function does not handle return intent overloading. + */ +const ResolvedFunction* resolveOnlyCandidate(Context* context, + const ResolvedExpression& r); + #endif diff --git a/tools/chapel-py/src/method-tables/uast-methods.h b/tools/chapel-py/src/method-tables/uast-methods.h index be01f1e16efb..2d6b1aa6b004 100644 --- a/tools/chapel-py/src/method-tables/uast-methods.h +++ b/tools/chapel-py/src/method-tables/uast-methods.h @@ -91,7 +91,9 @@ CLASS_BEGIN(AstNode) std::optional(TypedSignatureObject*), auto sigObj = std::get<0>(args); - auto resolvedFn = resolution::resolveFunction(context, sigObj->value_.signature, sigObj->value_.poiScope); + // TODO: Might need to do frame rewinding here. + resolution::ResolutionContext rcval(context); + auto resolvedFn = resolution::resolveFunction(&rcval, sigObj->value_.signature, sigObj->value_.poiScope); if (!resolvedFn) return {}; auto r = resolvedFn->byAstOrNull(node); diff --git a/tools/chapel-py/src/resolution.cpp b/tools/chapel-py/src/resolution.cpp index 31a3e782df9e..e4417efe731b 100644 --- a/tools/chapel-py/src/resolution.cpp +++ b/tools/chapel-py/src/resolution.cpp @@ -22,6 +22,7 @@ #include "chpl/resolution/scope-queries.h" #include "chpl/resolution/resolution-queries.h" #include "chpl/framework/query-impl.h" +#include "chpl/uast/all-uast.h" using namespace chpl; using namespace uast; diff --git a/tools/chpldoc/chpldoc.cpp b/tools/chpldoc/chpldoc.cpp index 0e066fae9848..bfb2b165b5f9 100644 --- a/tools/chpldoc/chpldoc.cpp +++ b/tools/chpldoc/chpldoc.cpp @@ -36,6 +36,8 @@ #include "chpl/parsing/parsing-queries.h" #include "chpl/framework/compiler-configuration.h" #include "chpl/framework/Context.h" +#include "chpl/framework/ErrorBase.h" +#include "chpl/framework/ErrorMessage.h" #include "chpl/framework/UniqueString.h" #include "chpl/framework/query-impl.h" #include "chpl/framework/stringify-functions.h"