From 770a6b2869ea312b474b0fc6108e06b57fc89767 Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Thu, 4 Dec 2025 13:45:15 -0500 Subject: [PATCH] [ASTDumper] Print the USR for a protocol conformance's declaring context. It's useful to know where a conformance is coming from when doing semantic analysis. The main motivating use case is this: imagine a module that declares a type: ```swift // SomeTypeModule struct SomeType {} ``` Then a second module that adds a conformance: ```swift // SomeTypeConformanceModule extension SomeType: @retroactive Codable { // implementation of requirements } ``` Then a third module that uses that conformance: ```swift import SomeTypeModule import SomeTypeConformanceModule func f() { _ = try? JSONEncoder().encode(SomeType()) } ``` There's currently nothing in the printed AST that indicates that the import of `SomeTypeConformanceModule` is required. Printing the declaring context for conformances lets us get that signal from the substitution map at the call site in `f()`. Also removed a place where we redundantly output `retroactive`. --- lib/AST/ASTDumper.cpp | 22 ++++++-- .../ast-dump-json-conformance-context.swift | 52 +++++++++++++++++++ 2 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 test/Frontend/ast-dump-json-conformance-context.swift diff --git a/lib/AST/ASTDumper.cpp b/lib/AST/ASTDumper.cpp index 218d4c2fe4bbc..37a400682fdc9 100644 --- a/lib/AST/ASTDumper.cpp +++ b/lib/AST/ASTDumper.cpp @@ -5786,8 +5786,26 @@ class PrintConformance : public PrintBase { printField(conformance->getSourceKind(), Label::optional("source_kind")); printFlag(conformance->isRetroactive(), "retroactive"); printIsolation(conformance->getIsolation()); - if (!Writer.isParsable()) + + if (Writer.isParsable() && isTypeChecked()) { + // Print the decl context that declares the conformance, which should + // always be a type or extension declaration. Print the module as well, + // since an extension decl USR won't actually contain this if the + // module containing the conformance is different than the modules + // containing the type and the protocol. + printRecArbitrary([&](Label label) { + printHead("conformance_context", ASTNodeColor, label); + DeclContext *DC = conformance->getDeclContext(); + if (auto *D = DC->getAsDecl()) { + printField(declUSR(D), Label::always("decl")); + } + printField(DC->getParentModule()->getRealName(), + Label::always("module")); + printFoot(); + }, Label::always("context")); + } else { printFlag(!shouldPrintDetails, "
"); + } }; switch (conformance->getKind()) { @@ -5800,7 +5818,6 @@ class PrintConformance : public PrintBase { printFlag(normal->isPreconcurrencyEffectful(), "effectful_preconcurrency"); } - printFlag(normal->isRetroactive(), "retroactive"); printFlag(normal->isUnchecked(), "unchecked"); if (normal->getExplicitSafety() != ExplicitSafety::Unspecified) printField(normal->getExplicitSafety(), Label::always("safety")); @@ -5808,7 +5825,6 @@ class PrintConformance : public PrintBase { if (!shouldPrintDetails) break; - // Maybe print information about the conforming context? if (normal->isLazilyLoaded()) { printFlag("lazy"); } else { diff --git a/test/Frontend/ast-dump-json-conformance-context.swift b/test/Frontend/ast-dump-json-conformance-context.swift new file mode 100644 index 0000000000000..1cac17da2e882 --- /dev/null +++ b/test/Frontend/ast-dump-json-conformance-context.swift @@ -0,0 +1,52 @@ +// RUN: %empty-directory(%t) +// RUN: split-file %s %t +// RUN: %target-swift-frontend %t/SomeProtocolModule.swift -parse-as-library -emit-module -emit-module-path %t/SomeProtocolModule.swiftmodule -module-name SomeProtocolModule +// RUN: %target-swift-frontend %t/SomeTypeModule.swift -parse-as-library -emit-module -emit-module-path %t/SomeTypeModule.swiftmodule -module-name SomeTypeModule +// RUN: %target-swift-frontend %t/SomeTypeConformanceModule.swift -I %t -parse-as-library -emit-module -emit-module-path %t/SomeTypeConformanceModule.swiftmodule -module-name SomeTypeConformanceModule +// RUN: %target-swift-frontend %t/Client.swift -I %t -parse-as-library -dump-ast -dump-ast-format json -module-name Client -o - > %t/Client.json +// RUN: %{python} -c 'import json, sys; print(json.dumps(json.load(sys.stdin), indent=4))' < %t/Client.json | %FileCheck %s + +//--- SomeProtocolModule.swift +public protocol SomeProtocol {} + +//--- SomeTypeModule.swift +public struct SomeType { + public init() {} +} + +//--- SomeTypeConformanceModule.swift +import SomeProtocolModule +import SomeTypeModule + +extension SomeType: @retroactive SomeProtocol {} + +//--- Client.swift +import SomeProtocolModule +import SomeTypeConformanceModule +import SomeTypeModule + +func g(_ t: T) {} + +func f() { + g(SomeType()) +} + +// CHECK: "substitutions": { +// CHECK-NEXT: "_kind": "substitution_map", +// CHECK: "reqs": [ +// CHECK: { +// CHECK-NEXT: "_kind": "conformance", +// CHECK-NEXT: "type": "$sxD", +// CHECK-NEXT: "conformance": { +// CHECK-NEXT: "_kind": "normal_conformance", +// CHECK-NEXT: "type": "$s14SomeTypeModule0aB0VD", +// CHECK-NEXT: "protocol": "s:18SomeProtocolModule0aB0P", +// CHECK: "context": { +// CHECK-NEXT: "_kind": "conformance_context", +// CHECK-NEXT: "decl": "s:e:s:14SomeTypeModule0aB0Vs:18SomeProtocolModule0aB0P", +// CHECK-NEXT: "module": "SomeTypeConformanceModule" +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: }