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

Challenge exception inference #2198

Open
stephan-herrmann opened this issue Mar 21, 2024 · 3 comments
Open

Challenge exception inference #2198

stephan-herrmann opened this issue Mar 21, 2024 · 3 comments
Assignees

Comments

@stephan-herrmann
Copy link
Contributor

Follow-up from #1181, where I tried to create test cases that specifically challenge the interaction of lambda shape analysis and exception inference.

Firstly, I could hardly find any existing test cases in this area.

Secondly, a naive experiment surfaced a few differences to javac that need scrutiny.

Consider the following programm:

import java.io.FileNotFoundException;
import java.io.IOException;

class Foo {
	boolean test() throws FileNotFoundException { return false; }
}
class Bar { }

public class Ex {

	void test(Foo foo) throws IOException {
		ThrowingFunction<?,? extends Exception> fun = throwingFunction(f -> {
			if (f.test())
				throw new IllegalAccessException();
			return foo;
		},
		getEx());
	}
	public <X extends IOException> ThrowingFunction<Foo, X> throwingFunction(
			final ThrowingFunction<Foo, X> function, X e) {
		return function;
	}
	public <X extends IllegalAccessException> ThrowingFunction<Bar, X> throwingFunction(
			final ThrowingFunction<Bar, X> function, X e) {
		return function;
	}
	<X extends Exception> X getEx() { return null; }
}
interface ThrowingFunction<T, X extends Throwable> {
	abstract T apply(T t) throws X;
}

(1) Compiled as given above we get:

  • ecj: The method throwingFunction(ThrowingFunction<Foo,IOException>, IOException) is ambiguous for the type Ex
  • javac: reference to throwingFunction is ambiguous

(2) When we change the type bound of method getEx() to extends IOException then:

  • ecj: Unhandled exception type IllegalAccessException
  • javac: inference variable X#1 has incompatible bounds

(3) When we change the same type bound to extends IllegalAccessError then:

  • ecj:
    • The method test() is undefined for the type Bar
    • Type mismatch: cannot convert from Foo to Bar
  • javac:
    • incompatible types: cannot infer type-variable(s) X (argument mismatch; bad return type in lambda expression Foo cannot be converted to Bar)

For (1) compilers are well aligned. But (2) and (3) show that ecj's inference accepts the program and leaves it to downstream phases to detect resulting errors, whereas javac succeeds to connect the respective problem to inference.

@stephan-herrmann stephan-herrmann self-assigned this Mar 21, 2024
@stephan-herrmann
Copy link
Contributor Author

To recap the role of exception constraints during inference, here's as central statement from 18.2.1:

We do not attempt to produce bounds on inference variables that appear in the target function type's throws clause. This is because exception containment is not part of compatibility (§15.27.3) - in particular, it must not influence method applicability (§18.5.1). However, we do get bounds on these variables later, because invocation type inference (§18.5.2.2) produces exception containment constraint formulas (§18.2.5).

This helps to explain, why in the above example leaving getEx() under specified results in method ambiguity, even when we remove throw new IllegalAccessException(); from the lambda body. Knowing that only IOException can be thrown doesn't help selecting one of the overloads based on inferring X. This inference happens only after applicability inference.

For the same reason, availability of test() will only be determined after inference, because it needs the result of invocation type inference.

@stephan-herrmann
Copy link
Contributor Author

This item from 18.2.5. is incompletely implemented in ecj:

If n = 0 (the function type's throws clause consists only of proper types), then if there exists some i (1 ≤ i ≤ m) such that Xi is not a subtype of any proper type in the throws clause, the constraint reduces to false; otherwise, the constraint reduces to true.

We don't have that 'false' case.

@stephan-herrmann
Copy link
Contributor Author

In 18.2.5. E' (JLS 8) has been renamed to X (JLS 22) but other than that plus the omission mentioned above, our implementation of exception inference is well-aligned with the spec, still.

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

No branches or pull requests

1 participant