Skip to content

[Sema] Fix crash when retrieving typeContextInfo for a partially bound generic type #37034

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 27, 2021

Conversation

ahoppen
Copy link
Member

@ahoppen ahoppen commented Apr 23, 2021

In the following test case, we are crashing while building the generic signature of someGenericFunc, potentially invoked on model in line 11.

struct MyBinding<BindingOuter> {
  func someGenericFunc<BindingInner>(x: BindingInner) {}
}

struct MyTextField<TextFieldOuter> {
  init<TextFieldInner>(text: MyBinding<TextFieldInner>) {}
}

struct EncodedView {
    func foo(model: MyBinding<String>) {
        let _ = MyTextField<String>(text: model)
    }
}

Because we know that model has type MyBinding<TextFieldInner>, we substitute the BindingOuter generic parameter by TextFieldInner. Thus, someGenericFunc has the signature <TextFieldInner /* substitutes BindingOuter */, BindingInner>. TextFieldInner and BindingOuter both have depth = 1, index = 0. Thus the verification in GenericSignatureBuilder is failing.

After discussion with @slavapestov, the root issue appears to be that we shouldn’t be calling subst on a GenericFunctionType at all. Instead we should be using substGenericArgs which doesn’t attempt to rebuild a generic signature, but instead builds a non-generic function type.


We slightly regress in code completion results by showing two collidingGeneric twice in the following case.

protocol P1 {
  func collidingGeneric<T>(x: T)
}
protocol P2 {
  func collidingGeneric<T>(x: T)
}
class C : P1, P2 {
  #^COMPLETE^#
}

Previously, we were representing the type of collidingGeneric by a generic function type with generic param T that doesn’t have any restrictions. Since we are now using substGenericArgs instead of subst, we receive a non-generic function type that represents T as an archetype. And since that archetype is different for the two function signatures, we show the result twice in code completion.

One could also argue that showing the result twice is intended (or at least acceptable) behaviour since, the two protocol may name their generic params differently. E.g. in

protocol P1 {
  func collidingGeneric<S>(x: S)
}
protocol P2 {
  func collidingGeneric<T>(x: T)
}
class C : P1, P2 {
  #^COMPLETE^#
}

we might be expected to show the following two results

func collidingGeneric<S>(x: S)
func collidingGeneric<T>(x: T)

Resolves rdar://76711477 [SR-14495]

@ahoppen ahoppen requested a review from slavapestov April 23, 2021 10:18
@ahoppen
Copy link
Member Author

ahoppen commented Apr 23, 2021

@swift-ci Please test

Copy link
Contributor

@slavapestov slavapestov left a comment

Choose a reason for hiding this comment

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

Just some minor nitpicks you can skip or roll over into another PR if you want. The logic looks fine otherwise.

@@ -930,16 +932,35 @@ class OverrideFilteringConsumer : public VisibleDeclConsumer {
(BaseTy->getNominalOrBoundGenericNominal() ||
BaseTy->is<ArchetypeType>()) &&
VD->getDeclContext()->isTypeContext());
ModuleDecl *M = DC->getParentModule();

/// Substitue generic parameters in the signature of a found decl. The
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick: "Substitute"

if (auto GenFuncSignature =
SignatureType->getAs<GenericFunctionType>()) {
GenericEnvironment *GenEnv;
if (auto *GenCtx = VD->getAsGenericContext()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's always going to be a GenericContext if it has a GenericFunctionType

Copy link
Member Author

@ahoppen ahoppen Apr 27, 2021

Choose a reason for hiding this comment

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

It actually doesn’t. In the following example

protocol FooProtocol {
  var bar: Int { get set }
}

func test(foo: FooProtocol) {
  foo.#^COMPLETE^#
}

foo has a SignatureType of

(generic_function_type escaping
  (input=function_params num_params=1
    (param
      (generic_type_param_type depth=0 index=0)))
  (output=tuple_type num_elements=0)
  ( generic_sig=<τ_0_0 where τ_0_0 : FooProtocol>))

but the value decl VD (which is bar) doesn’t have a generic environment because it’s not generic..

/// Substitue generic parameters in the signature of a found decl. The
/// returned type can be used to determine if we have already found a
/// conflicting declaration.
auto normalizeSignatureType = [&](CanType SignatureType, ValueDecl *VD,
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe rename this to "substGenericArgs" since that's more descriptive than "normalize" which could mean anything

…d generic type

In the following test case, we are crashing while building the generic signature of `someGenericFunc`, potentially invoked on `model` in line 11.

```swift
struct MyBinding<BindingOuter> {
  func someGenericFunc<BindingInner>(x: BindingInner) {}
}

struct MyTextField<TextFieldOuter> {
  init<TextFieldInner>(text: MyBinding<TextFieldInner>) {}
}

struct EncodedView {
    func foo(model: MyBinding<String>) {
        let _ = MyTextField<String>(text: model)
    }
}
```

Because we know that `model` has type `MyBinding<TextFieldInner>`, we substitute the `BindingOuter` generic parameter by `TextFieldInner`. Thus, `someGenericFunc` has the signature `<TextFieldInner /* substitutes BindingOuter */, BindingInner>`. `TextFieldInner` and `BindingOuter` both have `depth = 1`, `index = 0`. Thus the verification in `GenericSignatureBuilder` is failing.

After discussion with Slava, the root issue appears to be that we shouldn’t be calling `subst` on a `GenericFunctionType` at all. Instead we should be using `substGenericArgs` which doesn’t attempt to rebuild a generic signature, but instead builds a non-generic function type.

--------------------------------------------------------------------------------

We slightly regress in code completion results by showing two `collidingGeneric` twice in the following case.

```swift
protocol P1 {
  func collidingGeneric<T>(x: T)
}
protocol P2 {
  func collidingGeneric<T>(x: T)
}
class C : P1, P2 {
  #^COMPLETE^#
}
```

Previously, we were representing the type of `collidingGeneric` by a generic function type with generic param `T` that doesn’t have any restrictions. Since we are now using `substGenericArgs` instead of `subst`, we receive a non-generic function type that represents `T` as an archetype. And since that archetype is different for the two function signatures, we show the result twice in code completion.

One could also argue that showing the result twice is intended (or at least acceptable) behaviour since, the two protocol may name their generic params differently. E.g. in

```swift
protocol P1 {
  func collidingGeneric<S>(x: S)
}
protocol P2 {
  func collidingGeneric<T>(x: T)
}
class C : P1, P2 {
  #^COMPLETE^#
}
```

we might be expected to show the following two results
```
func collidingGeneric<S>(x: S)
func collidingGeneric<T>(x: T)
```

Resolves rdar://76711477 [SR-14495]
@ahoppen ahoppen force-pushed the pr/subst-generic-params-mismatch branch from 70311cc to b70e91f Compare April 27, 2021 06:14
@ahoppen
Copy link
Member Author

ahoppen commented Apr 27, 2021

@swift-ci Please test

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