Skip to content

[RemoteMirror] Fix lookup of generic type parameters #65332

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 6 commits into from
Apr 24, 2023

Conversation

tbkka
Copy link
Contributor

@tbkka tbkka commented Apr 20, 2023

PR #62854 identified an issue with generic type arguments. Unfortunately, that fix didn't quite work, since it inadvertently renumbered those arguments, leading to failures when we tried to look up generic type arguments when we had nested generic and non-generic types interspersed.

This is (I hope) a more correct fix. It changes how the parent types are recursively reconstructed to ensure that we number the nesting levels the same way the compiler does. I've also built some additional tests that verify not only the type reference (name) is correctly printed, but that the built types have the correct layout (which implies that the various generic type arguments are being correctly handled).

Resolves rdar://106790077

tbkka added 3 commits April 20, 2023 14:30
…hen building decl"

(Keeps the test, but reverts the code changes; the test will be updated later.)

This reverts most of commit 893d83b from PR swiftlang#62854
This is a more correct fix for the issue that inspired PR swiftlang#62854.
In particular, this does not change the numbering of generic
argument levels, and so does not break generic type parameter
lookups.

Added a new test case to verify that nested types that mix
generics and non-generics in different ways consistently identify
the correct generic argument labels.
The new logic no longer includes non-generic parent types as part
of the type reference.

This also combines the 32- and 64-bit tests validations, since they
were identical for this test anyway.
Copy link
Contributor Author

@tbkka tbkka left a comment

Choose a reason for hiding this comment

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

A few comments on the code here to help reviewers.

} else {
// If there is no generic context, this is a non-generic type
// which has 0 generic parameters.
paramsPerLevel.emplace_back(0);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was the key bug introduced by PR #62854: Adding a 0 params marker here caused non-generic types to get counted as generic types, throwing off the generic type parameter matching.

@@ -567,62 +567,58 @@ class TypeRefBuilder {
return nullptr;
}

// Recursively constructs TypeRef for a bound generic, including
// the enclosing (parent) generic contexts
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've significantly rearranged the logic here to make it more cleanly recursive (which allowed me to eliminate the iterative walk to find a parent type).

@@ -1,7 +1,7 @@
// RUN: %empty-directory(%t)
// RUN: %target-build-swift -lswiftSwiftReflectionTest %s -o %t/reflect_nested
// RUN: %target-codesign %t/reflect_nested
// RUN: %target-run %target-swift-reflection-test %t/reflect_nested 2>&1 | %FileCheck %s --check-prefix=CHECK-%target-ptrsize
// RUN: %target-run %target-swift-reflection-test %t/reflect_nested 2>&1 | %FileCheck %s --check-prefix=CHECK
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since the CHECK-64 and CHECK-32 lines here are all identical, I just eliminated the target-ptrsize distinction.

// CHECK-64: Type reference:
// CHECK-64: (bound_generic_class reflect_nested.OuterGeneric.Inner.Innermost
// CHECK-64-NEXT: (struct Swift.String)
// CHECK-64-NEXT: (bound_generic_class reflect_nested.OuterGeneric.Inner
Copy link
Contributor Author

@tbkka tbkka Apr 20, 2023

Choose a reason for hiding this comment

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

The one actual change in this test: reflect_nested.OuterGeneric.Inner is not a bound_generic_class, so does not need to be in the list of generic parent types.

struct Outer2 {
enum E<T: P> {
struct Inner {
enum F<U: P> {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changing this to a multi-payload Enum fails. I think that's just because all the MPE fixes from #64852 haven't landed yet.

@tbkka
Copy link
Contributor Author

tbkka commented Apr 20, 2023

@swift-ci Please test

Copy link
Contributor

@mikeash mikeash left a comment

Choose a reason for hiding this comment

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

Nice, that's a subtle bug.

tbkka added 3 commits April 22, 2023 11:52
The recursive structure made it a little awkward to correctly
distinguish between the root node (which has to be included
regardless of whether it has direct generic params)
and the parent nodes (which must only be included if they
have direct params).

So I rewrote this to do a simple two-pass iteration:
* The first pass walks the parent list collecting candidates
* The second pass walks the list backwards, assigning generic params

We then just stack the start node onto the end of the
list regardless of whether it has generic params.
Handle the top node directly in `createBoundGenericType`
so that we can be sure to always include it regardless
of whether it has direct generic type params or not.

The helper function can now focus only on handling
parents (where we must only include ones that actually
contain generic type params).  This separates the two
different cases and makes it a little easier to follow.

I refactored `reconstructParentsOfBoundGenericType` to
be iterative rather than recursive, though I'm not sure that
really matters.
const BoundGenericTypeRef *reconstructParentsOfBoundGenericType(
const NodePointer startNode,
const std::vector<size_t> &genericParamsPerLevel,
const llvm::ArrayRef<const TypeRef *> &args)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

After fussing around with different structures here, I finally reduced this to only handle reconstructing the parent list. That means it can consistently ignore nodes with no direct generic params. I also reworked it to be iterative rather than recursive, though that's not particularly relevant -- I just found it a little easier to follow that way.

// We're now going to build the type tree from the
// outermost parent in, which matches the order of
// the generic parameter list and genericParamsPerLevel.
std::reverse(nodes.begin(), nodes.end());
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Above, we walk the parent list from the innermost to outermost. That is, for Foo<Int>.Bar<Double>.Baz<String>.Quux, we visit Baz, then Bar, then Foo (skipping Quux, since that's the start node, not one of the parents we're collecting here.

I then need to build the new list in the opposite order, which is why I reverse the list here.

}
auto argEnd = argBegin + numGenericArgs;
std::vector<const TypeRef *> params(argBegin, argEnd);
argBegin = argEnd;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Walking the list in order makes it a lot easier to track which arguments go with with parent node.

// Sanity: Verify that the generic params per level add
// up exactly to the number of args we were provided, and
// that we don't have a rediculous number of either one
auto genericParamsPerLevel = *maybeGenericParamsPerLevel;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

There were a lot of checks to ensure that the shape list (params-per-level) and the actual args list made sense together. I found it easier to pull that together into a single unified check: Ensure that the total number of params mentioned in params-per-level adds up exactly to the number of args we have. I also imposed an arbitrary limit of 1000 nested types and 1000 generic args in order to be comfortably certain that we'll never have problems from integer wrapping.

std::vector<const TypeRef *> params(argBegin, args.end());

// Build and return the top typeref
return BoundGenericTypeRef::create(*this, mangling.result(), params, parents);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The innermost node always gets included here, since that's the node we're actually constructing the typeref for. This happens even if the innermost node has no explicit generic type params.

//CHECK: Enum value:
//CHECK-NEXT: (enum_value name=b index=2)

reflect(enum: Outer2.E<S1>.Inner.F<S2>.Innerer?.none)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

As @augusto2112 suggested, I added a test where the innermost type has no generic type parameters.

@tbkka
Copy link
Contributor Author

tbkka commented Apr 22, 2023

@swift-ci Please test

Copy link
Contributor

@augusto2112 augusto2112 left a comment

Choose a reason for hiding this comment

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

This looks much better than the original!

// Collect the first N parents that potentially have generic args
// (Ignore the last genericParamPerLevel, which
// applies to the startNode itself.)
std::vector<NodePointer> nodes;
Copy link
Contributor

@mikeash mikeash Apr 24, 2023

Choose a reason for hiding this comment

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

Fairly unimportant, but if you use a SmallVector here (and a couple other places), it'll avoid allocating in the common case where there number of parents is small.

@tbkka tbkka merged commit db14ae8 into swiftlang:main Apr 24, 2023
@tbkka tbkka deleted the tbkka-RemoteMirror-nested-generics branch August 1, 2024 16:37
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.

3 participants