Skip to content

[AST] More JSON AST dump improvements. #80498

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 5 commits into from
Apr 23, 2025
Merged

Conversation

allevato
Copy link
Member

@allevato allevato commented Apr 3, 2025

These improvements came about through great back-and-forth in this forum thread. They are summarized as follows:

  • Instead of using a custom-rolled type transformer, call mapTypeOutOfContext() to replace any primary archetypes with type parameters when computing a type USR.
  • If the type still contains local archetypes, replace them with an equivalent existential.
  • When dumping type inheritance, print the derived semantic information (all protocol conformances, raw type for enums, superclass/inherited protocols, etc.).
  • Dump additional information about protocol conformances (safety, preconcurrency, isolation, etc.). (I also decided to do this in the default dump, since it seems useful there.)
  • Add a source-file-level mapping of opaque type USRs to the USR of the equivalent existential type.

allevato added 2 commits April 2, 2025 08:55
- For type USRs, use `mapTypeOutOfContext()` to replace primary
  archetypes with type parameters. If the type contains local
  archetypes, replace them with their existential upper bounds.
- For inheritance of types, print the derived semantic information
  instead of the inheritance list as it is written.
- Dump conformance requirements as protocol decl USRs instead of
  type USRs, since the latter involves an existential conversion
  that loses suppressed protocols.
- Fix a small bug in conformance dumping where the `MemberLoading`
  field wasn't propagated to the nested printer, which caused
  protocols to be dumped as simple names instead of USRs.
The earlier changes to ASTDumper only printed information like
retroactiveness and safety when printing the `InheritedEntries`
of a decl. Now that we print the actual resolved semantic
information instead of the inheritance list as-written, we need
to surface these values via the `ProtocolConformance`s.
@allevato
Copy link
Member Author

allevato commented Apr 3, 2025

@slavapestov Thanks for all the pointers for these improvements. Would you mind reviewing this change please?

// decl instead of the type. The type USR for a protocol is based on
// the equivalent expanded existential, which drops suppressed
// protocols.
if (requirement.getKind() == RequirementKind::Conformance) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe switch over the kind?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, and used a fallthrough instead of a separate else-branch to handle the non-parsable case, reducing some duplication.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Realized I had missed an opportunity to turn the entire larger conditional into part of the same switch—printing the protocol name instead of the type yields the same result as printing the type in non-parsable mode, so we don't need to treat isParsable() as a special case at all here.

Label::always("conformances"));

if (auto CD = dyn_cast<ClassDecl>(DC); CD && CD->hasSuperclass()) {
printReferencedDeclField(CD->getSuperclassDecl(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More generally there is a superclass type getSuperclass() which is not just a decl. Eg,

class G<T> {}
class B: G<Int> {}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched to getSuperclass() for classes.

For a class-constrained ProtocolDecl, it looks like I don't have a Type-returning getSuperclass() there. But the full type is accessible in the requirement_signature that's being dumped; in your example there's a subclass_of requirement that contains the mangled G<Int>. So that'll just have to be a different access path for clients.

[&](Label label) {
printHead("opaque_to_existential_mapping", FieldLabelColor, label);
for (const auto OTD : opaqueDecls) {
Type interfaceType = OTD->getDeclaredInterfaceType();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

calling getDeclaredInterfaceType() on OpaqueTypeDecl should produce an assertion failure; are you sure you have test coverage for this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, it seems to be working? When I dump an OpaqueTypeDecl in the context of the declaring function, declared_interface_type is one of the fields we dump (line ~2158). For this snippet:

func f() -> some Sequence { [] }

I get

			"opaque_result_decl": {
				"_kind": "opaque_type",
				...
				"declared_interface_type": "$s3lib1fQryFQOyQo_D",

and the top-level mapping is

	"opaque_to_existential_mapping": {
		"_kind": "opaque_to_existential_mapping",
		"$s3lib1fQryFQOyQo_D": "$sST_pD"
	}

There are tests exercising opaque types in the omnibus Frontend/ast-dump-json-no-crash.swift test.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used getDeclaredInterfaceType() because it seemed to be the most likely match for what would appear in expressions that use the type, but I'm also happy to switch it to something else more appropriate.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, I missed that we set it in OpaqueResultTypeRequest::evaluate(). My apologies.

However, my original concern is that opaque return type can appear in a nested position, and we might have more than one opaque return type, eg:

func f() -> (some Any, some Any)? {}

Indeed, it appears that the declared interface type of the opaque result declaration is the entire return type of the function in this case, (some Any, some Any)?, so your castTo<OpaqueTypeArchetypeType>() will fail. You probably want to walk the declared interface type to collect all opaque archetypes that it contains.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, right. Updated this to use transformRec to replace any OpaqueTypeArchetypeTypes with their getExistentialType(), and added a handful of tests that cover different structural situations.

Copy link
Member Author

@allevato allevato left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

// decl instead of the type. The type USR for a protocol is based on
// the equivalent expanded existential, which drops suppressed
// protocols.
if (requirement.getKind() == RequirementKind::Conformance) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, and used a fallthrough instead of a separate else-branch to handle the non-parsable case, reducing some duplication.

[&](Label label) {
printHead("opaque_to_existential_mapping", FieldLabelColor, label);
for (const auto OTD : opaqueDecls) {
Type interfaceType = OTD->getDeclaredInterfaceType();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, it seems to be working? When I dump an OpaqueTypeDecl in the context of the declaring function, declared_interface_type is one of the fields we dump (line ~2158). For this snippet:

func f() -> some Sequence { [] }

I get

			"opaque_result_decl": {
				"_kind": "opaque_type",
				...
				"declared_interface_type": "$s3lib1fQryFQOyQo_D",

and the top-level mapping is

	"opaque_to_existential_mapping": {
		"_kind": "opaque_to_existential_mapping",
		"$s3lib1fQryFQOyQo_D": "$sST_pD"
	}

There are tests exercising opaque types in the omnibus Frontend/ast-dump-json-no-crash.swift test.

Label::always("conformances"));

if (auto CD = dyn_cast<ClassDecl>(DC); CD && CD->hasSuperclass()) {
printReferencedDeclField(CD->getSuperclassDecl(),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched to getSuperclass() for classes.

For a class-constrained ProtocolDecl, it looks like I don't have a Type-returning getSuperclass() there. But the full type is accessible in the requirement_signature that's being dumped; in your example there's a subclass_of requirement that contains the mangled G<Int>. So that'll just have to be a different access path for clients.

@allevato allevato force-pushed the json-usr-fixes branch 2 times, most recently from c2a4ec0 to 1ad6596 Compare April 3, 2025 19:57
@allevato
Copy link
Member Author

allevato commented Apr 8, 2025

@slavapestov Any other changes you'd like to see here?

@allevato
Copy link
Member Author

@swift-ci please test

@allevato
Copy link
Member Author

@swift-ci please smoke test

@allevato allevato enabled auto-merge April 22, 2025 12:55
@allevato
Copy link
Member Author

@swift-ci please smoke test

@allevato
Copy link
Member Author

@swift-ci please smoke test Linux platform

@allevato allevato merged commit a3993f9 into swiftlang:main Apr 23, 2025
5 checks passed
@allevato allevato deleted the json-usr-fixes branch April 24, 2025 11:50
allevato added a commit that referenced this pull request Apr 24, 2025
[AST] More JSON AST dump improvements.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants