Skip to content

[CodeCompletion] Duplicate existential requirements when restated #16811

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

Conversation

AnthonyLatsis
Copy link
Collaborator

@AnthonyLatsis AnthonyLatsis commented May 24, 2018

Resolves SR-7035.

Also resolves duplicates in cases like

func foo<T: A & B>(...) -> T.#^PARAM^#

class Foo : A, B {
  #^MEMBER^#
}

Please look into how I tell apart restated requirements. If there is a better way (ideally wipe out any self occurences from a type, which isn't trivial as far as I see), I'll be glad to hear.

I would like to know if it's more efficient and better to do

if (decl.getKind() >= DeclKind::AbstractFuncDecl_First &&
    decl.getKind() <= DeclKind::AbstractFuncDecl_Last)

than isa<AbstractFunctionDecl>(decl).

P.S. There is something strange going on with the SemanticContextKind on the tests. Have a look, it can be Super or CurrNominal depending on whether the function argument is or isn't of an archetype type. This was already present, the tests simply checked for one of them among the duplicates.

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.

What happens if one requirement is overloaded and the only difference between the overloads is the generic signatures? I think calling getResult() on the overload signature will produce the same type then. Eg,

func f<T : P>(_: T)
func f<T : Q>(_: T)

if (foundFuncs[{VD->getFullName(), type}].empty()) {
parentConsumer.foundDecl(VD, Reason);
}
foundFuncs[{VD->getFullName(), type}].insert(VD);
Copy link
Contributor

Choose a reason for hiding this comment

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

The return value of insert() tells you if the entry was inserted or not so you can use that instead.

Why is the value of this map a set of types here, do you actually do anything with them?

Copy link
Collaborator Author

@AnthonyLatsis AnthonyLatsis May 24, 2018

Choose a reason for hiding this comment

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

No, that's the key type, it's just that it's a pair (I'll change it to a pair, right now it's a tuple). I need both a type and a declName to sort out functions and subscripts.

Copy link
Collaborator Author

@AnthonyLatsis AnthonyLatsis May 24, 2018

Choose a reason for hiding this comment

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

The return value of insert() tells you if the entry was inserted or not so you can use that instead.

Actually, don't need more that one element per key, != empty suffices, so I better change the container type to TinyVector or simply a pointer.


void foundDecl(ValueDecl *VD, DeclVisibilityKind Reason) override {
assert(VD);
if (!VD->getDeclContext()->getAsProtocolOrProtocolExtensionContext()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don’t understand why this is specific to protocol extensions

Copy link
Collaborator Author

@AnthonyLatsis AnthonyLatsis May 24, 2018

Choose a reason for hiding this comment

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

It isn't! Thanks for the remark, I'll change this to dyn_cast<ProtocolDecl>

return;
}
auto type =
VD->getOverloadSignatureType()->getAs<AnyFunctionType>()->getResult();
Copy link
Contributor

Choose a reason for hiding this comment

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

getAs returns null if it’s the wrong type; use castTo() if you don’t expect it to fail

getReasonForSuper(Reason), TypeResolver, GSB, Visited);

if (auto superclass = Archetype->getSuperclass())
if (ArchetypeType *AT = BaseTy->getAs<ArchetypeType>()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this a non-functional change? Can you split it off into a separate patch if it’s unrelated?

@slavapestov
Copy link
Contributor

Using isa is always better than checking the kind directly. I’m not sure what you mean with regard to wiping out Self requirements - can you give an example?

@AnthonyLatsis
Copy link
Collaborator Author

AnthonyLatsis commented May 24, 2018

What happens if one requirement is overloaded and the only difference between the overloads is the generic signatures? I think calling getResult() on the overload signature will produce the same type then.

I checked for that and surprisingly, it works. I will add a test case. This must be due to the information T carries still being in the signature.
However, I am very curious about type comparing. It is done by pointer equality, but doesn't that mean that two equal types that are separate instances won't be equal? Sometimes it's useful to strip off information from types and compare them, but it works even then through pointers. How come?

UDP After rechecking, it seems you're right after all. It works without getting the overload signature type, which I do get.

@AnthonyLatsis
Copy link
Collaborator Author

AnthonyLatsis commented May 24, 2018

Using isa is always better than checking the kind directly. I’m not sure what you mean with regard to wiping out Self requirements - can you give an example?

Thank you for the advice. So it is safer, but less efficient.
The problem is to tell whether a ValueDecl is a restatement,

protocol P {
  func foo()
}
protocol P1: P {
  func foo() // like here
}

The type of foo in P is <Self where Self: P> (P)->()->(). In P1, it's <Self where Self: P1> (P1)->()->(). This is simple, I can get the result type here. But how about a generic function that has both Self and T? As you see, the only thing that hiders us is the presence of Self.

I’m not sure what you mean with regard to wiping out Self requirements - can you give an example?

That said, and considering subscripts have to be handled specially since they are not self-curried with the generic signature still floating around, ideally, to tell apart restatements we have to compare with Self removed from the type, be it an associated type or a function.

<Self where Self: P, T>(P) -> (arg: T) -> () becomes <T>(arg: T)->(). Using resultType, I do the same except for the inclusion of the left-over generic signature. I've tried to search for functionality and ended up with the option to manually transform the type, but since everything is fine I haven't gone deeper into it.

Additionally, since the type of an associated type is also dependent on Self (Self.E.Type), but I don't know how to strip the Self off it, I had to create a separate set for properties and associated types, which I compare simply by name. I don't think this is a bad idea though.

@slavapestov
Copy link
Contributor

Types are compared for pointer equality because they are memoized. We never allocate the same type twice. However, type aliases and other sugar do create unique types, so you should always use isEqual() (or compare CanTypes) to strip off the sugar before.

I think the reason the example with overloads by generic signature worked is because it was comparing sugared types. Each generic parameter type gets a sugared type with its name as written in source, but when canonicalized, only the index in the signature matters, so I think it will stop working once you use canonical types instead.

Eg, we want these two to be the same decl:

typealias X = Int
func f() -> X
func f() -> Int

@AnthonyLatsis
Copy link
Collaborator Author

AnthonyLatsis commented May 24, 2018

Thank you for the explanation. I do compute new signatures and create new types here for comparison reasons, but they still somehow manage to correctly compare if they are canonical.

so I think it will stop working once you use canonical types instead.

I faced an opposite problem when fixing the overload issue: two signatures, for example

<Self> (Self) -> () -> ()
<Self> (Self) -> () -> ()

compare as different if I do not canonicalize them and equal otherwise. Is this due to each Self being a different instance? If so, what changes after canonicalization that makes these two equal? The canonicalized version is <τ_0_0> (τ_0_0) -> () -> ()

@AnthonyLatsis
Copy link
Collaborator Author

AnthonyLatsis commented May 24, 2018

I now remove the requirements from Self when dealing with subscripts or explicitly generic functions. Eg
<Self, T where Self: This, T: Other> (Self) -> (T) -> () becomes
<Self, T where T: Other> (Self) -> (T) -> ()
This fixes overload cases where the only difference is the generic signature. Added a test for that.

Also simplified the container types.

@AnthonyLatsis AnthonyLatsis force-pushed the code-compl-dup-restated-requirements branch from a42ea6e to 3db6673 Compare May 24, 2018 18:25
@AnthonyLatsis AnthonyLatsis force-pushed the code-compl-dup-restated-requirements branch from 3db6673 to 6c13eba Compare May 24, 2018 21:11
@AnthonyLatsis
Copy link
Collaborator Author

Oops, conflicts from #16534

if (params.size() == 1 && !isa<SubscriptDecl>(VD)) {
type = AFT->getResult()->getCanonicalType();
} else {
auto newReqs = sig->getRequirements().drop_front();
Copy link
Contributor

Choose a reason for hiding this comment

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

Please don’t make assumptions about the order of requirements in the generic signature like this. There must be a better way to accomplish what you’re trying to do. Perhaps you want to drop any conformance requirement with Self on the left hand side instead of assuming it’s the first one. Also this kind of thing should be factored out into a new method; the code here is getting a bit too long.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Perhaps you want to drop any conformance requirement with Self on the left hand side instead of assuming it’s the first one.

OK, fair enough. Who knows how the order might change in the future.
But... ~30 lines is long? :)

Copy link
Contributor

Choose a reason for hiding this comment

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

It will be longer than 30 lines once you do the right thing here, and it's more about single responsibility per method than raw length.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I see, you mean the type-computing logic.

@slavapestov
Copy link
Contributor

Yes, each protocol has its own sugared Self type, but they all canonicalize to the same thing.

if (params.size() == 1 && !isa<SubscriptDecl>(VD)) {
return AFT->getResult()->getCanonicalType();
}
GenericTypeParamType *SelfParam;
Copy link
Contributor

Choose a reason for hiding this comment

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

You don't have to "search" for it. Instead use VD->getDeclContext()->getSelfInterfaceType()

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thank you, that is exactly what I was looking for...in the wrong place.


return GenericFunctionType::get(newSig, AFT->getInput(),
AFT->getResult(), AFT->getExtInfo())
->getCanonicalType(newSig);
Copy link
Contributor

Choose a reason for hiding this comment

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

Indentation needs to be fixed, and you can use the no-argument form of getCanonicalType() instead.

private:
CanType stripSelfRequirementsIfNeeded(ValueDecl *VD, AnyFunctionType *AFT) {
// Preserve the generic signature if this is a subscript, which are uncurried,
// or if we have genenic params other than Self. Otherwise, strip if off
Copy link
Contributor

Choose a reason for hiding this comment

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

typo: "genenic"

Copy link
Contributor

Choose a reason for hiding this comment

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

typo: "strip if off"

// When preserving the generic signature, we remove the requirements
// from Self to make they don't prevent us from recognizing restatements.

// This can't be null since we are dealing with method or
Copy link
Contributor

Choose a reason for hiding this comment

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

In that case, the function should take a GenericFunctionType instead of an AnyFunctionType, then this comment becomes unnecessary

@AnthonyLatsis
Copy link
Collaborator Author

AnthonyLatsis commented May 29, 2018

@slavapestov, have a couple of minutes to take a glance?

@AnthonyLatsis
Copy link
Collaborator Author

I have the similar SR-7758 waiting which is dependent on the new consumer here. Can I commit to this PR then to solve them together?

@AnthonyLatsis
Copy link
Collaborator Author

@slavapestov Any comments? Should I ask Ben or someone else to have a look as well?

@AnthonyLatsis
Copy link
Collaborator Author

AnthonyLatsis commented Jun 5, 2018

cc @nkcsgexi @benlangmuir Please have a look when possible.

// TEST_INTERNAL_DE-DAG: Decl[InstanceMethod]/Super: func colliding() {|}{{; name=.+$}}
// TEST_INTERNAL_DE-DAG: Decl[InstanceMethod]/Super: func collidingGeneric<T>(x: T) {|}{{; name=.+$}}

// TEST_PUBLIC_DE: Begin completions, 4 items
// TEST_PUBLIC_DE-DAG: Decl[InstanceMethod]/Super: public func colliding() {|}{{; name=.+$}}
// TEST_PUBLIC_DE-DAG: Decl[InstanceMethod]/Super: public func collidingGeneric<T>(x: T) {|}{{; name=.+$}}
Copy link
Contributor

Choose a reason for hiding this comment

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

Why did we lose the public keyword here?

Copy link
Collaborator Author

@AnthonyLatsis AnthonyLatsis Jun 5, 2018

Choose a reason for hiding this comment

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

It was a bit strange but I thought it isn't a big deal since we didn't lose it on the other test. Now I see. Apparently because I am filtering without taking access levels into account. This means that, given

public protocol PublicProtocol {func foo}
protocol InternalProtocol {func foo}

, completing in class Foo: PublicProtocol, InternalProtocol and class Foo: InternalProtocol, PublicProtocol will result in the removal of different foos from the look up.
Thanks for pointing it out, I'll fix this by prioritizing declarations with higher access level.

@slavapestov
Copy link
Contributor

@swift-ci Please smoke test

@slavapestov slavapestov self-assigned this Jun 7, 2018
@AnthonyLatsis
Copy link
Collaborator Author

AnthonyLatsis commented Jun 7, 2018

I guess these are failures from the recently merged removal of self completions from type identifiers.

@AnthonyLatsis
Copy link
Collaborator Author

AnthonyLatsis commented Jun 8, 2018

The failures were because some tests began having unstable result ordering and the NEXT checks fail from time to time. This is because while processing, I don't respect the order in which they would have been fed to the parent consumer. Is this something to be concerned about? Should I write additional logic to keep the original order? If just stability is important, the order can be made stable by using MapVector, but it will still be different from how it was. You can have a look at what changed to DAG in the diff..

@slavapestov
Copy link
Contributor

@AnthonyLatsis Yes, order stability is important. Please use MapVector and similar structures to ensure it doesn't depend on hash values.

@AnthonyLatsis
Copy link
Collaborator Author

Order restored

@AnthonyLatsis AnthonyLatsis force-pushed the code-compl-dup-restated-requirements branch from fe33730 to ef11b11 Compare June 9, 2018 10:54
@slavapestov
Copy link
Contributor

@swift-ci Please smoke test

@AnthonyLatsis
Copy link
Collaborator Author

I got it, let's try again.

@AnthonyLatsis
Copy link
Collaborator Author

@slavapestov Can you smoke test please?

@slavapestov
Copy link
Contributor

@swift-ci Please smoke 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.

3 participants