Bug 562551 - content assist not working inside lambda expression
Summary: content assist not working inside lambda expression
Status: NEW
Alias: None
Product: JDT
Classification: Eclipse Project
Component: Core (show other bugs)
Version: 4.13   Edit
Hardware: PC Mac OS X
: P3 normal (vote)
Target Milestone: 4.24 M3   Edit
Assignee: Jay Arthanareeswaran CLA
QA Contact: Srikanth Sankaran CLA
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2020-04-28 04:09 EDT by Yanming Zhou CLA
Modified: 2023-06-27 07:51 EDT (History)
5 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Yanming Zhou CLA 2020-04-28 04:09:11 EDT
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;

public class TestClass {

	SingletonSupplier<Map<String, Object>> beansSupplier = SingletonSupplier.of(() -> {
		Map<String, Object> beans = new HashMap<>();
		// beans. Alt+/ not working here
		return Collections.unmodifiableMap(beans);
	});

	SingletonSupplier<Map<String, Object>> beansSupplier2 = SingletonSupplier.of(new Supplier<Map<String, Object>>() {
		@Override
		public Map<String, Object> get() {
			Map<String, Object> beans = new HashMap<>();
			// beans. Alt+/ works fine here
			return Collections.unmodifiableMap(beans);
		}
	});

	// copy from org.springframework.util.function.SingletonSupplier
	public static class SingletonSupplier<T> implements Supplier<T> {

		private final Supplier<? extends T> instanceSupplier;

		private final Supplier<? extends T> defaultSupplier;

		private volatile T singletonInstance;

		public SingletonSupplier(T instance, Supplier<? extends T> defaultSupplier) {
			this.instanceSupplier = null;
			this.defaultSupplier = defaultSupplier;
			this.singletonInstance = instance;
		}

		public SingletonSupplier(Supplier<? extends T> instanceSupplier, Supplier<? extends T> defaultSupplier) {
			this.instanceSupplier = instanceSupplier;
			this.defaultSupplier = defaultSupplier;
		}

		private SingletonSupplier(Supplier<? extends T> supplier) {
			this.instanceSupplier = supplier;
			this.defaultSupplier = null;
		}

		private SingletonSupplier(T singletonInstance) {
			this.instanceSupplier = null;
			this.defaultSupplier = null;
			this.singletonInstance = singletonInstance;
		}

		@Override
		public T get() {
			T instance = this.singletonInstance;
			if (instance == null) {
				synchronized (this) {
					instance = this.singletonInstance;
					if (instance == null) {
						if (this.instanceSupplier != null) {
							instance = this.instanceSupplier.get();
						}
						if (instance == null && this.defaultSupplier != null) {
							instance = this.defaultSupplier.get();
						}
						this.singletonInstance = instance;
					}
				}
			}
			return instance;
		}

		public T obtain() {
			T instance = get();
			return instance;
		}

		public static <T> SingletonSupplier<T> of(T instance) {
			return new SingletonSupplier<>(instance);
		}

		public static <T> SingletonSupplier<T> ofNullable(T instance) {
			return (instance != null ? new SingletonSupplier<>(instance) : null);
		}

		public static <T> SingletonSupplier<T> of(Supplier<T> supplier) {
			return new SingletonSupplier<>(supplier);
		}

		public static <T> SingletonSupplier<T> ofNullable(Supplier<T> supplier) {
			return (supplier != null ? new SingletonSupplier<>(supplier) : null);
		}

	}

}
Comment 1 Yanming Zhou CLA 2020-04-28 04:14:35 EDT
Eclipse IDE for Java Developers

Version: 2020-03 (4.15.0)
Build id: 20200313-1211
Comment 2 Stephan Herrmann CLA 2020-04-28 06:22:01 EDT
(In reply to Yanming Zhou from comment #1)
> Eclipse IDE for Java Developers
> 
> Version: 2020-03 (4.15.0)
> Build id: 20200313-1211

thanks for re-testing.

Note that the "Version" field is used to mark the *first* version where the problem was observed. As long as the bug is unresolved, this implies that all subsequent versions also have the bug.
Comment 3 Eclipse Genie CLA 2022-04-19 03:35:49 EDT
This bug hasn't had any activity in quite some time. Maybe the problem got resolved, was a duplicate of something else, or became less pressing for some reason - or maybe it's still relevant but just hasn't been looked at yet.

If you have further information on the current state of the bug, please add it. The information can be, for example, that the problem still occurs, that you still want the feature, that more information is needed, or that the bug is (for whatever reason) no longer relevant.

--
The automated Eclipse Genie.
Comment 4 Yanming Zhou CLA 2022-04-19 04:41:20 EDT
Still not resolved.

Version: 2022-03 (4.23.0)
Build id: 20220310-1457
Comment 5 Jay Arthanareeswaran CLA 2022-04-20 02:17:07 EDT
Reproduced in master.

Gayan, is this something you can work on, given your recent work in this area?
Comment 6 Jay Arthanareeswaran CLA 2022-04-20 05:53:36 EDT
I did some initial investigation and found out that LambdaExpression.copy() returns null during parsing of the lambda expression snippet. Looks like it hits the error at completion point and halts. Beyond this is recovery, which is a black box to me.
Comment 7 Jay Arthanareeswaran CLA 2022-04-20 08:37:49 EDT
(In reply to Jay Arthanareeswaran from comment #6)
> I did some initial investigation and found out that LambdaExpression.copy()
> returns null during parsing of the lambda expression snippet. Looks like it
> hits the error at completion point and halts. Beyond this is recovery, which
> is a black box to me.

OK, that may have been a red herring. I tried to compare with a similar testcase, but without type parameters and found that there's a difference in the flow. In the case with type parameters, LambdaExpression#cachedResolvedCopy() get called instead of FunctionalExpression#resolveType(BlockScope, boolean).

The key difference seems to be, in the former, we are not setting the LE#descriptor to the SAM. Doing this appears to resolve the Lambda properly and thereby propose the completions.
Comment 8 Jay Arthanareeswaran CLA 2022-04-20 09:32:15 EDT
Proposed fix is here:

https://github.com/eclipse-jdt/eclipse.jdt.core/pull/30
Comment 9 Jay Arthanareeswaran CLA 2022-04-21 03:05:41 EDT
(In reply to Jay Arthanareeswaran from comment #8)
> Proposed fix is here:
> 
> https://github.com/eclipse-jdt/eclipse.jdt.core/pull/30

This causes some failures in NegativeLambdaExpressionsTest, which makes be doubt whether my approach is correct.

Stephan, can I request you for a glance and tell me if the patch is in the right direction. I don't completely understand the purpose of cachedResolvedCopy() and copiesPerTargetType in LambdaExpression. But somehow my setting the descriptor in the cachedResolvedCopy() seems to cause a ProblemMethodBinding being returns from the next call to cachedResolvedCopy() from internalIsCompatibleWith(), which is causing an NPE for this.descriptor.returnType.
Comment 10 Stephan Herrmann CLA 2022-04-21 07:03:48 EDT
(In reply to Jay Arthanareeswaran from comment #9)
> (In reply to Jay Arthanareeswaran from comment #8)
> > Proposed fix is here:
> > 
> > https://github.com/eclipse-jdt/eclipse.jdt.core/pull/30
> 
> This causes some failures in NegativeLambdaExpressionsTest, which makes be
> doubt whether my approach is correct.
> 
> Stephan, can I request you for a glance ...

I'll take a look, but it may have to wait until next week, OK?
Comment 11 Stephan Herrmann CLA 2022-04-22 18:10:16 EDT
Hi Jay, 

We're hitting quite a sore spot in this bug. In order to persist some of the background theory and design decisions I created a new page in the hitchhiker's guide: https://wiki.eclipse.org/JDT_Core_Programmer_Guide/ECJ/Lambda

Please let me know whether this text makes any sense to you, we'll expand it as necessary then.

I don't think that assigning #descriptor on the original is semantically correct, at least it violates the general design idea (though I can't prove any harm off-hand).

But from what I wrote I see two prior design decisions back on the table:

* is re-parsing really the best idea (vs. ASTNode.copy())?

* when reparsing, we should at least use the original parser (which had been discussed before IIRC), but more tricks may be needed to reproduce the exact same parse result.
Comment 12 Jay Arthanareeswaran CLA 2022-04-25 07:01:30 EDT
(In reply to Stephan Herrmann from comment #11)
> Hi Jay, 
> 
> We're hitting quite a sore spot in this bug. In order to persist some of the
> background theory and design decisions I created a new page in the
> hitchhiker's guide:
> https://wiki.eclipse.org/JDT_Core_Programmer_Guide/ECJ/Lambda
> 
> Please let me know whether this text makes any sense to you, we'll expand it
> as necessary then.
> 
> I don't think that assigning #descriptor on the original is semantically
> correct, at least it violates the general design idea (though I can't prove
> any harm off-hand).
> 
> But from what I wrote I see two prior design decisions back on the table:
> 
> * is re-parsing really the best idea (vs. ASTNode.copy())?
> 
> * when reparsing, we should at least use the original parser (which had been
> discussed before IIRC), but more tricks may be needed to reproduce the exact
> same parse result.

Thanks Stephan, that helped.

So, this means my observation in comment #6 might still be on the right path. So, is it expected for LE#copy() to return null on account of syntax error? If so, what is the way forward?
Comment 13 Jay Arthanareeswaran CLA 2022-04-25 08:48:08 EDT
I played around with the code a bit more and found something else. Let's use this somewhat shorter testcase:

public class TestClass {
	SingletonSupplier<Map<String, Object>> beansSupplier = SingletonSupplier.of(() -> {
		Map<String, Object> beans = new HashMap<>();
		beans.cl // Alt+/ not working here
		return Collections.unmodifiableMap(beans);
	});

	public static class SingletonSupplier<T> implements Supplier<T> {

		@Override
		public T get() {
			return null;
		}
		public T obtain() {
			T instance = get();
			return instance;
		}
		public static <T> SingletonSupplier<T> of(Supplier<T> supplier) {
			return null;
		}
		 //Presence of the following method causes the completion to fail
		public static <T> SingletonSupplier<T> of(T instance) {
			return null;
		}
	}
}

Looks like the presence of the second of() method causes the type inference to set the expectedType of the LE to java.lang.Object instead of public interface Supplier<T> (which is what's used in the absence of the second method). This later results in the descriptor not being set when the following is called from LE#resolve(), line no 275. 

During compilation (without the compiler error, expectedType is set to Supplier<T> and thus everything around resolution then onwards, works fine. I will try to find out what causes the change in behavior of inference.
Comment 14 Jay Arthanareeswaran CLA 2022-04-26 02:26:38 EDT
(In reply to Jay Arthanareeswaran from comment #13)
> During compilation (without the compiler error, expectedType is set to
> Supplier<T> and thus everything around resolution then onwards, works fine.
> I will try to find out what causes the change in behavior of inference.

This again points to the copy() not being working during completion due to the syntax error. So, not much help from here.
Comment 15 Jay Arthanareeswaran CLA 2022-05-09 23:09:49 EDT
(In reply to Stephan Herrmann from comment #11)
> * when reparsing, we should at least use the original parser (which had been
> discussed before IIRC), but more tricks may be needed to reproduce the exact
> same parse result.

After my experiments and attempts, I tend to agree this may be our safest bet. Only that, we have a completion parser, only which is capable of recognizing the broken code with lambda expression. Stephan, what do you think? Any pointer/precedence?
Comment 16 Nathan Sweet CLA 2022-05-21 20:33:58 EDT
I found that Open Declaration fails on the code below. It doesn't have lambdas but it does have switch expressions. I can't tell if this is the same bug. If not please let me know and I can file a new issue (on github, apparently).

public class Test {
	public void test (Type type, String string) {
		switch (type) {
		case openDeclarationFails -> {
			switch (string) {
			// Open Declaration on "openDeclarationFails" does nothing except making an error sound.
			// Open Declaration on "Type" opens the "Open" dialog to search for an element to open.
			// Ctrl+click on "Type" opens com.sun.tools.javac.code.Type, a seemingly random class with the same same.
			case "Test" -> method(Type.openDeclarationFails);
			}
		}
		case anotherValue -> { // Open Declaration on "anotherValue" fails.
			switch (string) { // Open Declaration on "string" fails.
			case "Test" -> method(Type.anotherValue); // The above works as expected here.
			}
		}
		}
	}

	private void method (Type relay) {
	}

	static public enum Type {
		openDeclarationFails, anotherValue;
	}
}
Comment 17 Jay Arthanareeswaran CLA 2023-02-06 22:55:16 EST
(In reply to Nathan Sweet from comment #16)
> I found that Open Declaration fails on the code below. It doesn't have
> lambdas but it does have switch expressions. I can't tell if this is the
> same bug. If not please let me know and I can file a new issue (on github,
> apparently).

I can reproduce but found it to be a different issue. Can you please raise a new issue from here: 

https://github.com/eclipse-jdt/eclipse.jdt.core/issues/new
Comment 18 Nathan Sweet CLA 2023-02-06 22:59:43 EST
Posted there as requested:
https://github.com/eclipse-jdt/eclipse.jdt.core/issues/708