-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[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
[RemoteMirror] Fix lookup of generic type parameters #65332
Conversation
…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.
There was a problem hiding this 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); |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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> { |
There was a problem hiding this comment.
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.
@swift-ci Please test |
There was a problem hiding this 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.
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) |
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
@swift-ci Please test |
There was a problem hiding this 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; |
There was a problem hiding this comment.
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.
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