Skip to content
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

Type inference broken in 6.9.0 #5522

Closed
manusa opened this issue Oct 13, 2023 · 16 comments · Fixed by sundrio/sundrio#431 or #5553
Closed

Type inference broken in 6.9.0 #5522

manusa opened this issue Oct 13, 2023 · 16 comments · Fixed by sundrio/sundrio#431 or #5553
Assignees
Labels

Comments

@manusa
Copy link
Member

manusa commented Oct 13, 2023

Description

(From internal conversation with @iocanel and @metacosm)

PR #5138, in scope of #5135, provided changes to make Resources Editable.

While this improvement allows for one line edit operations similar to what Lombok's toBuilder does, it has somehow broken type inference in some scenarios.

One such scenario is where you have elements that are parameterized as such: A<R extends HasMetadata, P>. If you have several such objects which all have the same value for P but different values for R and you want to process them as a list.

Before 6.9.0, the list was correctly inferred to be a list containing elements of type A<? extends HasMetadata, P>.
With 6.9.0, the list is now inferred to contain elements of type A<? extends Editable<? extends BaseFluent<? extends BaseFluent<?>>>, P>.

With type erasure, this results in hard-to-diagnose errors because they occur outside of the user's code, similar to:

accept(java.util.List<java.util.Map.Entry<java.lang.String,java.lang.Object>>,java.lang.String,io.fabric8.kubernetes.api.builder.Visitor...) in io.fabric8.kubernetes.api.builder.BaseFluent cannot implement accept(java.util.List<java.util.Map.Entry<java.lang.String,java.lang.Object>>,java.lang.String,io.fabric8.kubernetes.api.builder.Visitor...) in io.fabric8.kubernetes.api.builder.Visitable
[ERROR]   return type capture#1 of ? is not compatible with capture#2 of ?

You can see such an error here: https:/operator-framework/java-operator-sdk/actions/runs/6456675029/job/17526638395#step:5:2605
If you look at the code, you'll see that none of the classes or method mentioned in the error are used, which makes for a very unpleasant diagnosing experience.

@metacosm
Copy link
Collaborator

Note that the workaround this issue is to either not process related elements generically or relax typing which isn't desirable and goes against the philosophy of the client.

@shawkins
Copy link
Contributor

@manusa @metacosm two thoughts. It could be unrelated but it could be worth seeing the split package that was introduced - #5520 - could be contributing. Could you try to move at least Pluralize moved to common and see if that helps. The other thought is that the resource does not actually need to implement Editable to expose these methods - that was done for the sundrio logic that is creating Builders, but that can just fall through to the lookup tables.

@manusa
Copy link
Member Author

manusa commented Oct 13, 2023

I'm trying to but I'm unable to reproduce this.

#5520 needs to get solved anyway, could you please create the PR with the fix? I'll try to release snapshots after merging.

@shawkins
Copy link
Contributor

#5520 needs to get solved anyway, could you please create the PR with the fix? I'll try to release snapshots after merging.

#5525 - should just be a tweak to the pom. It was copying the builder stuff into both modules.

@iocanel
Copy link
Member

iocanel commented Oct 17, 2023

@manusa @metacosm two thoughts. It could be unrelated but it could be worth seeing the split package that was introduced - #5520 - could be contributing.

Yeah, that does look suspicious. The Editable part of the theory explains how the BaseFluent gets dragged in. This might explain why inference actually breaks.

@metacosm
Copy link
Collaborator

Added a test that should exhibit the issue.

@shawkins
Copy link
Contributor

shawkins commented Oct 19, 2023

@metacosm observations based upon the test:

Arrays.<KubernetesDependentResource> asList(cdr, ddr).forEach(dr -> dr.reconcile());

or

Arrays.asList(cdr, ddr).forEach(KubernetesDependentResource::reconcile);

Let me try backing out the change that added the edit method.

@manusa
Copy link
Member Author

manusa commented Oct 20, 2023

Added a test that should exhibit the issue.

I can't really figure out what makes this test different to the one I proposed in the internal chat, hence why one fails and the other doesn't.
It might be the extra-nesting provided by DependentResourceConfigurator.

Arrays.asList(cdr, ddr).forEach(KubernetesDependentResource::reconcile);

It's interesting that the method reference works.

With Visitors we had the problem that lambdas didn't work and that anonymous inner classes had to be used instead.

@manusa manusa mentioned this issue Oct 20, 2023
11 tasks
@manusa
Copy link
Member Author

manusa commented Oct 20, 2023

In my case, running from the command-line (Java 8 from kubernetes-client-api directory) is still not failing.

Update It does fail with Java11 and Java17, so it definitely might have something to do with modules.

@metacosm
Copy link
Collaborator

@metacosm observations based upon the test:

* the [fix: should not export the builder package #5525](https:/fabric8io/kubernetes-client/pull/5525) fix does not resolve the issue

Doesn't indeed appear to change anything.

* running the test in the ide is fine, but not with the command line

It doesn't work in my IDE here, or at least, the original issue in JOSDK showed up in my IDE.

* Explicitly adding the typing works fine:
Arrays.<KubernetesDependentResource> asList(cdr, ddr).forEach(dr -> dr.reconcile());

or

Arrays.asList(cdr, ddr).forEach(KubernetesDependentResource::reconcile);

In both cases, though, this is a wider type than what would be expected. The more specific common type should be KubernetesDependentResource<? extends HasMetadata, Service>, which is what was previously correctly inferred. I should be able to resolve operations / objects that use Service as a parameter in my lambda without first having to cast.

Let me try backing out the change that added the edit method.

@metacosm
Copy link
Collaborator

Added a test that should exhibit the issue.

I can't really figure out what makes this test different to the one I proposed in the internal chat, hence why one fails and the other doesn't. It might be the extra-nesting provided by DependentResourceConfigurator.

Yes, the thing that makes it fail is DependentResourceConfigurator, I think it introduces a different parameter that is not resolved properly anymore.

Arrays.asList(cdr, ddr).forEach(KubernetesDependentResource::reconcile);

It's interesting that the method reference works.

I think that's expected because it's basically telling Java to consider all elements as KubernetesDependentResource, which they are but which is a wider type than what should be resolvable.

With Visitors we had the problem that lambdas didn't work and that anonymous inner classes had to be used instead.

@shawkins
Copy link
Contributor

In both cases, though, this is a wider type than what would be expected. The more specific common type should be KubernetesDependentResource<? extends HasMetadata, Service>, which is what was previously correctly inferred. I should be able to resolve operations / objects that use Service as a parameter in my lambda without first having to cast.

I wasn't trying to be overly narrow, just using the class where the resolve method is introduced. I understand that it should work without the typing.

I expanded the test to inline definitions of an AltConfigMap and AltDeployment along with dummy builders. Based upon that the next observations are:

  • the compile error goes away if the resources are not Editable
  • the compile error also goes away if BaseFluent is not used for the Builders

Further inlining all of the builder package with the test shows:

  • the compile error still persists, so it's not related to modules nor classloading

So I think there's a viable workaround by removing the Editable interface from the resources (but the edit / toBuilder methods can stay) - however there just seems to be something pretty funky about the sundrio typing that is confusing the compiler. I'll poke around a little more and see if there are any changes that could be made to BaseFluent to resolve this.

@shawkins
Copy link
Contributor

@manusa @metacosm I've opened a sundrio pr for this. The short form is that implementing Visitable twice - even if it has the same bound from a high-level perspective is causing confusion.

@metacosm
Copy link
Collaborator

I was thinking that indeed the error might have come from an interface being implemented twice with different type constraints…

@manusa
Copy link
Member Author

manusa commented Nov 6, 2023

Similar problem is still ongoing in 6.9.3 (Sundrio 0.101.3)

Quarkus build reports:

Error: ]: Build step io.quarkus.kubernetes.deployment.KubernetesProcessor#build threw an exception: java.lang.NoSuchMethodError: 'io.fabric8.kubernetes.api.builder.Fluent io.fabric8.kubernetes.api.model.KubernetesListBuilder.accept(io.fabric8.kubernetes.api.builder.Visitor[])'
	at io.dekorate.ResourceRegistry.lambda$generate$2(ResourceRegistry.java:181)
	at java.base/java.util.HashMap.forEach(HashMap.java:1421)
	at io.dekorate.ResourceRegistry.generate(ResourceRegistry.java:173)
	at io.dekorate.Session.generate(Session.java:293)
	at io.dekorate.Session.close(Session.java:256)
	at io.quarkus.kubernetes.deployment.KubernetesProcessor.lambda$build$2(KubernetesProcessor.java:194)
	at java.base/java.util.Optional.ifPresent(Optional.java:178)
	at io.quarkus.kubernetes.deployment.KubernetesProcessor.build(KubernetesProcessor.java:142)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at io.quarkus.deployment.ExtensionLoader$3.execute(ExtensionLoader.java:849)
	at io.quarkus.builder.BuildContext.run(BuildContext.java:256)
	at org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18)
	at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513)
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1538)
	at java.base/java.lang.Thread.run(Thread.java:840)
	at org.jboss.threads.JBossThread.run(JBossThread.java:501)

	at io.quarkus.test.QuarkusProdModeTest.beforeAll(QuarkusProdModeTest.java:512)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)

Triggered at:

https:/dekorateio/dekorate/blob/5a9d246bdd0ecbcbfabbbbc3070f757605366a21/core/src/main/java/io/dekorate/ResourceRegistry.java#L181

        for (Decorator d : sortDecorators(union)) {
          log("Applying decorator '%s'", d.getClass().getName());
          groups.get(group).accept(d);
        }
      }

@metacosm
Copy link
Collaborator

metacosm commented Nov 6, 2023

Indeed seeing this type of errors as well in QOSDK.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
4 participants