Skip to content

Sema: Improve the infinite opaque return type check #83141

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jul 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions include/swift/AST/InFlightSubstitution.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,36 @@
#define SWIFT_AST_INFLIGHTSUBSTITUTION_H

#include "swift/AST/SubstitutionMap.h"
#include "llvm/ADT/DenseMap.h"

namespace swift {
class SubstitutionMap;

class InFlightSubstitution {
SubstOptions Options;
friend class SubstitutionMap;

TypeSubstitutionFn BaselineSubstType;
LookupConformanceFn BaselineLookupConformance;
SubstOptions Options;
RecursiveTypeProperties Props;
unsigned RemainingCount : 15;
unsigned InitLimit : 1;
unsigned RemainingDepth : 15;
unsigned LimitReached : 1;

struct ActivePackExpansion {
bool isSubstExpansion = false;
unsigned expansionIndex = 0;
};
SmallVector<ActivePackExpansion, 4> ActivePackExpansions;
llvm::SmallVector<ActivePackExpansion, 4> ActivePackExpansions;
llvm::SmallDenseMap<SubstitutionMap, SubstitutionMap, 2> SubMaps;

Type projectLaneFromPackType(
Type substType, unsigned level);
ProtocolConformanceRef projectLaneFromPackConformance(
PackConformance *substPackConf, unsigned level);

bool checkLimits(Type ty);

public:
InFlightSubstitution(TypeSubstitutionFn substType,
Expand Down Expand Up @@ -145,6 +160,10 @@ class InFlightSubstitution {

/// Is the given type invariant to substitution?
bool isInvariant(Type type) const;

bool wasLimitReached() const {
return LimitReached;
}
};

/// A helper classes that provides stable storage for the query
Expand Down
2 changes: 1 addition & 1 deletion include/swift/AST/SubstitutionMap.h
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ class SubstitutionMap {

/// Whether to dump the full substitution map, or just a minimal useful subset
/// (on a single line).
enum class DumpStyle { Minimal, Full };
enum class DumpStyle { Minimal, NoConformances, Full };
/// Dump the contents of this substitution map for debugging purposes.
void dump(llvm::raw_ostream &out, DumpStyle style = DumpStyle::Full,
unsigned indent = 0) const;
Expand Down
10 changes: 1 addition & 9 deletions include/swift/AST/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -7077,24 +7077,16 @@ enum class OpaqueSubstitutionKind {
/// archetypes with underlying types visible at a given resilience expansion
/// to their underlying types.
class ReplaceOpaqueTypesWithUnderlyingTypes {
public:
using SeenDecl = std::pair<OpaqueTypeDecl *, SubstitutionMap>;
private:
ResilienceExpansion contextExpansion;
llvm::PointerIntPair<const DeclContext *, 1, bool> inContextAndIsWholeModule;
llvm::DenseSet<SeenDecl> *seenDecls;

public:
ReplaceOpaqueTypesWithUnderlyingTypes(const DeclContext *inContext,
ResilienceExpansion contextExpansion,
bool isWholeModuleContext)
: contextExpansion(contextExpansion),
inContextAndIsWholeModule(inContext, isWholeModuleContext),
seenDecls(nullptr) {}

ReplaceOpaqueTypesWithUnderlyingTypes(
const DeclContext *inContext, ResilienceExpansion contextExpansion,
bool isWholeModuleContext, llvm::DenseSet<SeenDecl> &seen);
inContextAndIsWholeModule(inContext, isWholeModuleContext) {}

/// TypeSubstitutionFn
Type operator()(SubstitutableType *maybeOpaqueType) const;
Expand Down
8 changes: 8 additions & 0 deletions include/swift/Basic/LangOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,14 @@ namespace swift {
/// rewrite system.
bool EnableRequirementMachineOpaqueArchetypes = false;

/// Maximum nesting depth for type substitution operations, to prevent
/// runaway recursion.
unsigned MaxSubstitutionDepth = 50;

/// Maximum step count for type substitution operations, to prevent
/// runaway recursion.
unsigned MaxSubstitutionCount = 2000;

/// Enable implicit lifetime dependence for ~Escapable return types.
bool EnableExperimentalLifetimeDependenceInference = false;

Expand Down
8 changes: 8 additions & 0 deletions include/swift/Option/FrontendOptions.td
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,14 @@ def disable_requirement_machine_reuse : Flag<["-"], "disable-requirement-machine
def enable_requirement_machine_opaque_archetypes : Flag<["-"], "enable-requirement-machine-opaque-archetypes">,
HelpText<"Enable more correct opaque archetype support, which is off by default because it might fail to produce a convergent rewrite system">;

def max_substitution_depth : Joined<["-"], "max-substitution-depth=">,
Flags<[FrontendOption, HelpHidden, DoesNotAffectIncrementalBuild]>,
HelpText<"Set the maximum nesting depth for type substitution operations">;

def max_substitution_count : Joined<["-"], "max-substitution-count=">,
Flags<[FrontendOption, HelpHidden, DoesNotAffectIncrementalBuild]>,
HelpText<"Set the maximum step count for type substitution operations">;

def dump_type_witness_systems : Flag<["-"], "dump-type-witness-systems">,
HelpText<"Enables dumping type witness systems from associated type inference">;

Expand Down
28 changes: 18 additions & 10 deletions lib/AST/ASTDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1255,13 +1255,19 @@ namespace {

/// Print a substitution map as a child node.
void printRec(SubstitutionMap map, Label label) {
printRec(map, SubstitutionMap::DumpStyle::Full, label);
}

/// Print a substitution map as a child node.
void printRec(SubstitutionMap map, SubstitutionMap::DumpStyle style,
Label label) {
SmallPtrSet<const ProtocolConformance *, 4> Dumped;
printRec(map, Dumped, label);
printRec(map, style, Dumped, label);
}

/// Print a substitution map as a child node.
void printRec(SubstitutionMap map, VisitedConformances &visited,
Label label);
void printRec(SubstitutionMap map, SubstitutionMap::DumpStyle style,
VisitedConformances &visited, Label label);

/// Print a substitution map as a child node.
void printRec(const ProtocolConformanceRef &conf,
Expand Down Expand Up @@ -5807,7 +5813,8 @@ class PrintConformance : public PrintBase {
if (!shouldPrintDetails)
break;

printRec(conf->getSubstitutionMap(), visited,
printRec(conf->getSubstitutionMap(),
SubstitutionMap::DumpStyle::Full, visited,
Label::optional("substitutions"));
if (auto condReqs = conf->getConditionalRequirementsIfAvailableOrCached(/*computeIfPossible=*/false)) {
printList(*condReqs, [&](auto subReq, Label label) {
Expand Down Expand Up @@ -5906,7 +5913,7 @@ class PrintConformance : public PrintBase {

// A minimal dump doesn't need the details about the conformances, a lot of
// that info can be inferred from the signature.
if (style == SubstitutionMap::DumpStyle::Minimal)
if (style != SubstitutionMap::DumpStyle::Full)
return;

auto conformances = map.getConformances();
Expand All @@ -5925,13 +5932,12 @@ class PrintConformance : public PrintBase {
}
};

void PrintBase::printRec(SubstitutionMap map, VisitedConformances &visited,
Label label) {
void PrintBase::printRec(SubstitutionMap map, SubstitutionMap::DumpStyle style,
VisitedConformances &visited, Label label) {
printRecArbitrary(
[&](Label label) {
PrintConformance(Writer, MemberLoading)
.visitSubstitutionMap(map, SubstitutionMap::DumpStyle::Full,
visited, label);
.visitSubstitutionMap(map, style, visited, label);
},
label);
}
Expand Down Expand Up @@ -6354,7 +6360,9 @@ namespace {

printArchetypeCommonRec(T);
if (!T->getSubstitutions().empty()) {
printRec(T->getSubstitutions(), Label::optional("substitutions"));
printRec(T->getSubstitutions(),
SubstitutionMap::DumpStyle::NoConformances,
Label::optional("substitutions"));
}

printFoot();
Expand Down
34 changes: 30 additions & 4 deletions lib/AST/SubstitutionMap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,13 @@ SubstitutionMap SubstitutionMap::subst(TypeSubstitutionFn subs,
SubstitutionMap SubstitutionMap::subst(InFlightSubstitution &IFS) const {
if (empty()) return SubstitutionMap();

// FIXME: Get this caching working with pack expansions as well.
if (IFS.ActivePackExpansions.empty()) {
auto found = IFS.SubMaps.find(*this);
if (found != IFS.SubMaps.end())
return found->second;
}

SmallVector<Type, 4> newSubs;
for (Type type : getReplacementTypes()) {
newSubs.push_back(type.subst(IFS));
Expand All @@ -345,7 +352,12 @@ SubstitutionMap SubstitutionMap::subst(InFlightSubstitution &IFS) const {
}

assert(oldConformances.empty());
return SubstitutionMap(genericSig, newSubs, newConformances);
auto result = SubstitutionMap(genericSig, newSubs, newConformances);

if (IFS.ActivePackExpansions.empty())
(void) IFS.SubMaps.insert(std::make_pair(*this, result));

return result;
}

SubstitutionMap
Expand Down Expand Up @@ -624,9 +636,23 @@ SubstitutionMap swift::substOpaqueTypesWithUnderlyingTypes(
ReplaceOpaqueTypesWithUnderlyingTypes replacer(
context.getContext(), context.getResilienceExpansion(),
context.isWholeModuleContext());
return subs.subst(replacer, replacer,
SubstFlags::SubstituteOpaqueArchetypes |
SubstFlags::PreservePackExpansionLevel);
InFlightSubstitution IFS(replacer, replacer,
SubstFlags::SubstituteOpaqueArchetypes |
SubstFlags::PreservePackExpansionLevel);

auto substSubs = subs.subst(IFS);

if (IFS.wasLimitReached()) {
ABORT([&](auto &out) {
out << "Possible non-terminating type substitution detected\n\n";
out << "Original substitution map:\n";
subs.dump(out, SubstitutionMap::DumpStyle::NoConformances);
out << "Substituted substitution map:\n";
substSubs.dump(out, SubstitutionMap::DumpStyle::NoConformances);
});
}

return substSubs;
}

Type OuterSubstitutions::operator()(SubstitutableType *type) const {
Expand Down
Loading