Skip to content

[🍒 6.2] [AST] More JSON AST dump improvements. #81069

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 1 commit into from
Apr 24, 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
270 changes: 212 additions & 58 deletions lib/AST/ASTDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,33 @@ class escaping_ostream : public raw_ostream {
virtual void anchor() override {}
};

/// Replaces any local archetypes in the given type with their equivalent
/// existential upper bounds so that they can be passed to the AST mangler. This
/// loses information but is probably sufficient for most questions about these
/// types that consumers of the JSON AST would ask.
Type replaceLocalArchetypesWithExistentials(Type type) {
return type.transformRec([&](TypeBase *t) -> std::optional<Type> {
if (auto LAT = dyn_cast<LocalArchetypeType>(t)) {
return LAT->getExistentialType();
}
return std::nullopt;
});
}

/// Replaces any opaque type archetypes in the given type with their equivalent
/// existential upper bounds. This is used when dumping the mapping of all
/// opaque types in the source file so that their conformances can be more
/// easily reasoned about without having to find the declaring opaque result
/// type deeper in the AST.
Type replaceOpaqueArchetypesWithExistentials(Type type) {
return type.transformRec([&](TypeBase *t) -> std::optional<Type> {
if (auto OT = dyn_cast<OpaqueTypeArchetypeType>(t)) {
return OT->getExistentialType();
}
return std::nullopt;
});
}

/// Returns the USR of the given declaration. Gracefully returns an empty
/// string if D is null or invalid.
std::string declUSR(const Decl *D) {
Expand Down Expand Up @@ -234,14 +261,10 @@ std::string typeUSR(Type type) {
return "";

if (type->hasArchetype()) {
// We can't generate USRs for types that contain archetypes. Replace them
// with their interface types.
type = type.transformRec([&](TypeBase *t) -> std::optional<Type> {
if (auto AT = dyn_cast<ArchetypeType>(t)) {
return AT->getInterfaceType();
}
return std::nullopt;
});
type = type->mapTypeOutOfContext();
}
if (type->hasLocalArchetype()) {
type = replaceLocalArchetypesWithExistentials(type);
}

std::string usr;
Expand Down Expand Up @@ -628,6 +651,21 @@ static StringRef getDumpString(ExplicitSafety safety) {
return "unsafe";
}
}
static StringRef getDumpString(ConformanceEntryKind kind) {
switch (kind) {
case ConformanceEntryKind::Inherited:
return "inherited";
case ConformanceEntryKind::Explicit:
return "explicit";
case ConformanceEntryKind::PreMacroExpansion:
return "pre_macro_expansion";
case ConformanceEntryKind::Synthesized:
return "synthesized";
case ConformanceEntryKind::Implied:
return "implied";
}
llvm_unreachable("unhandled ConformanceEntryKind");
}
static StringRef getDumpString(StringRef s) {
return s;
}
Expand Down Expand Up @@ -1251,6 +1289,40 @@ namespace {
void printRec(const ProtocolConformance *conformance,
VisitedConformances &visited, Label label);

// Print a field that describes the actor isolation associated with an AST
// node.
void printIsolation(const ActorIsolation &isolation) {
switch (isolation) {
case ActorIsolation::Unspecified:
case ActorIsolation::NonisolatedUnsafe:
break;

case ActorIsolation::Nonisolated:
printFlag(true, "nonisolated", CapturesColor);
break;

case ActorIsolation::Erased:
printFlag(true, "dynamically_isolated", CapturesColor);
break;

case ActorIsolation::CallerIsolationInheriting:
printFlag(true, "isolated_to_caller_isolation", CapturesColor);
break;

case ActorIsolation::ActorInstance:
printReferencedDeclWithContextField(isolation.getActorInstance(),
Label::always("actor_isolated"),
CapturesColor);
break;

case ActorIsolation::GlobalActor:
printTypeField(isolation.getGlobalActor(),
Label::always("global_actor_isolated"), PrintOptions(),
CapturesColor);
break;
}
}

/// Print a requirement node.
void visitRequirement(const Requirement &requirement, Label label) {
printHead("requirement", ASTNodeColor, label);
Expand All @@ -1262,13 +1334,19 @@ namespace {

printField(requirement.getKind(), Label::optional("kind"));

if (requirement.getKind() != RequirementKind::Layout
&& requirement.getSecondType())
printTypeField(requirement.getSecondType(),
Label::optional("second_type"), opts);
else if (requirement.getLayoutConstraint())
switch (requirement.getKind()) {
case RequirementKind::Layout:
printFieldQuoted(requirement.getLayoutConstraint(),
Label::optional("layout"));
break;
case RequirementKind::Conformance:
printReferencedDeclField(requirement.getProtocolDecl(),
Label::optional("protocol"));
break;
default:
printTypeField(requirement.getSecondType(),
Label::optional("second_type"), opts);
}

printFoot();
}
Expand Down Expand Up @@ -1917,6 +1995,62 @@ namespace {
}
}

void printInheritance(const IterableDeclContext *DC) {
if (!(Writer.isParsable() && isTypeChecked())) {
// If the output is not parsable or we're not type-checked, just print
// the inheritance list as written.
switch (DC->getIterableContextKind()) {
case IterableDeclContextKind::NominalTypeDecl:
printInherited(cast<NominalTypeDecl>(DC)->getInherited());
break;
case IterableDeclContextKind::ExtensionDecl:
printInherited(cast<ExtensionDecl>(DC)->getInherited());
break;
}
return;
}

// For parsable, type-checked output, print a more structured
// representation of the data.
printRecArbitrary(
[&](Label label) {
printHead("inheritance", FieldLabelColor, label);

SmallPtrSet<const ProtocolConformance *, 4> dumped;
printList(
DC->getLocalConformances(),
[&](auto conformance, Label label) {
printRec(conformance, dumped, label);
},
Label::always("conformances"));

if (auto CD = dyn_cast<ClassDecl>(DC); CD && CD->hasSuperclass()) {
printTypeField(CD->getSuperclass(),
Label::always("superclass_type"));
}

if (auto ED = dyn_cast<EnumDecl>(DC); ED && ED->hasRawType()) {
printTypeField(ED->getRawType(), Label::always("raw_type"));
}

if (auto PD = dyn_cast<ProtocolDecl>(DC)) {
printList(
PD->getAllInheritedProtocols(),
[&](auto inherited, Label label) {
printReferencedDeclField(inherited, label);
},
Label::always("protocols"));
if (PD->hasSuperclass()) {
printReferencedDeclField(PD->getSuperclassDecl(),
Label::always("superclass_decl_usr"));
}
}

printFoot();
},
Label::always("inherits"));
}

void printInherited(InheritedTypes Inherited) {
if (Writer.isParsable()) {
printList(
Expand Down Expand Up @@ -2252,13 +2386,13 @@ namespace {
switch (IDC->getIterableContextKind()) {
case IterableDeclContextKind::NominalTypeDecl: {
const auto NTD = cast<NominalTypeDecl>(IDC);
printInherited(NTD->getInherited());
printInheritance(NTD);
printWhereRequirements(NTD);
break;
}
case IterableDeclContextKind::ExtensionDecl:
const auto ED = cast<ExtensionDecl>(IDC);
printInherited(ED->getInherited());
printInheritance(ED);
printWhereRequirements(ED);
break;
}
Expand Down Expand Up @@ -2289,6 +2423,27 @@ namespace {
printFoot();
}

// Prints a mapping from the declared interface types of the opaque types to
// their equivalent existential type. This loses some information, but it is
// meant to make it easier to determine which protocols an opaque type
// conforms to when such a type appears elsewhere in an expression dump,
// farther away from where the opaque type is declared.
void printOpaqueTypeMapping(ArrayRef<OpaqueTypeDecl *> opaqueDecls) {
printRecArbitrary(
[&](Label label) {
printHead("opaque_to_existential_mapping", FieldLabelColor, label);
for (const auto OTD : opaqueDecls) {
Type interfaceType = OTD->getDeclaredInterfaceType();
Type existentialType =
replaceOpaqueArchetypesWithExistentials(interfaceType);
printTypeField(existentialType,
Label::always(typeUSR(interfaceType)));
}
printFoot();
},
Label::always("opaque_to_existential_mapping"));
}

void visitSourceFile(const SourceFile &SF) {
Writer.setMainBufferID(SF.getBufferID());

Expand Down Expand Up @@ -2357,6 +2512,15 @@ namespace {
}
},
Label::optional("items"));

if (Writer.isParsable() && isTypeChecked()) {
SmallVector<OpaqueTypeDecl *, 4> opaqueDecls;
SF.getOpaqueReturnTypeDecls(opaqueDecls);
if (!opaqueDecls.empty()) {
printOpaqueTypeMapping(opaqueDecls);
}
}

printFoot();
}

Expand Down Expand Up @@ -3941,36 +4105,7 @@ class PrintExpr : public ExprVisitor<PrintExpr, void, Label>,

printField(E->getRawDiscriminator(), Label::always("discriminator"),
DiscriminatorColor);

switch (auto isolation = E->getActorIsolation()) {
case ActorIsolation::Unspecified:
case ActorIsolation::NonisolatedUnsafe:
break;

case ActorIsolation::Nonisolated:
printFlag(true, "nonisolated", CapturesColor);
break;

case ActorIsolation::Erased:
printFlag(true, "dynamically_isolated", CapturesColor);
break;

case ActorIsolation::CallerIsolationInheriting:
printFlag(true, "isolated_to_caller_isolation", CapturesColor);
break;

case ActorIsolation::ActorInstance:
printReferencedDeclWithContextField(isolation.getActorInstance(),
Label::always("actor_isolated"),
CapturesColor);
break;

case ActorIsolation::GlobalActor:
printTypeField(isolation.getGlobalActor(),
Label::always("global_actor_isolated"), PrintOptions(),
CapturesColor);
break;
}
printIsolation(E->getActorIsolation());

if (auto captureInfo = E->getCachedCaptureInfo()) {
printCaptureInfoField(captureInfo, Label::optional("captures"));
Expand Down Expand Up @@ -5551,6 +5686,9 @@ class PrintConformance : public PrintBase {
printTypeField(conformance->getType(), Label::always("type"));
printReferencedDeclField(conformance->getProtocol(),
Label::always("protocol"));
printField(conformance->getSourceKind(), Label::optional("source_kind"));
printFlag(conformance->isRetroactive(), "retroactive");
printIsolation(conformance->getIsolation());
if (!Writer.isParsable())
printFlag(!shouldPrintDetails, "<details printed above>");
};
Expand All @@ -5560,6 +5698,16 @@ class PrintConformance : public PrintBase {
auto normal = cast<NormalProtocolConformance>(conformance);

printCommon("normal_conformance");
printFlag(normal->isPreconcurrency(), "preconcurrency");
if (normal->isPreconcurrency() && normal->isComplete()) {
printFlag(normal->isPreconcurrencyEffectful(),
"effectful_preconcurrency");
}
printFlag(normal->isRetroactive(), "retroactive");
printFlag(normal->isUnchecked(), "unchecked");
if (normal->getExplicitSafety() != ExplicitSafety::Unspecified)
printField(normal->getExplicitSafety(), Label::always("safety"));

if (!shouldPrintDetails)
break;

Expand Down Expand Up @@ -5774,27 +5922,33 @@ class PrintConformance : public PrintBase {

void PrintBase::printRec(SubstitutionMap map, VisitedConformances &visited,
Label label) {
printRecArbitrary([&](Label label) {
PrintConformance(Writer)
.visitSubstitutionMap(map, SubstitutionMap::DumpStyle::Full, visited,
label);
}, label);
printRecArbitrary(
[&](Label label) {
PrintConformance(Writer, MemberLoading)
.visitSubstitutionMap(map, SubstitutionMap::DumpStyle::Full,
visited, label);
},
label);
}

void PrintBase::printRec(const ProtocolConformanceRef &ref,
VisitedConformances &visited, Label label) {
printRecArbitrary([&](Label label) {
PrintConformance(Writer)
.visitProtocolConformanceRef(ref, visited, label);
}, label);
printRecArbitrary(
[&](Label label) {
PrintConformance(Writer, MemberLoading)
.visitProtocolConformanceRef(ref, visited, label);
},
label);
}

void PrintBase::printRec(const ProtocolConformance *conformance,
VisitedConformances &visited, Label label) {
printRecArbitrary([&](Label label) {
PrintConformance(Writer)
.visitProtocolConformance(conformance, visited, label);
}, label);
printRecArbitrary(
[&](Label label) {
PrintConformance(Writer, MemberLoading)
.visitProtocolConformance(conformance, visited, label);
},
label);
}

} // end anonymous namespace
Expand Down
Loading