Bug 24941 - inline method - doesn't handle implicit cast [refactoring]
Summary: inline method - doesn't handle implicit cast [refactoring]
Status: RESOLVED FIXED
Alias: None
Product: JDT
Classification: Eclipse Project
Component: UI (show other bugs)
Version: 2.0   Edit
Hardware: PC All
: P3 minor (vote)
Target Milestone: 3.0 M2   Edit
Assignee: Dirk Baeumer CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2002-10-17 08:20 EDT by Claude Knaus CLA
Modified: 2014-04-25 09:55 EDT (History)
2 users (show)

See Also:


Attachments
Patch fixing the bug (7.65 KB, patch)
2003-06-14 12:58 EDT, Dmitry Stalnov CLA
no flags Details | Diff
final patch fixing the bug (9.93 KB, patch)
2003-06-19 08:59 EDT, Dmitry Stalnov CLA
no flags Details | Diff
unit tests for the submitted patch (2.89 KB, text/plain)
2003-06-19 09:04 EDT, Dmitry Stalnov CLA
no flags Details
Resources for unit tests (8.03 KB, application/octet-stream)
2003-06-19 09:05 EDT, Dmitry Stalnov CLA
no flags Details
Code restructuring, fixed null types problem (5.51 KB, patch)
2003-06-26 03:45 EDT, Dmitry Stalnov CLA
no flags Details | Diff
Unit test for patch #5298 (2.05 KB, patch)
2003-06-26 03:47 EDT, Dmitry Stalnov CLA
no flags Details | Diff
Generalized Bindings.findMethodsInSuperclasses (6.53 KB, patch)
2003-06-30 08:41 EDT, Dmitry Stalnov CLA
no flags Details | Diff
Considered interfaces, created binding predicate (9.78 KB, patch)
2003-07-01 09:04 EDT, Dmitry Stalnov CLA
no flags Details | Diff
Replaced binding predicate with visitor (10.01 KB, patch)
2003-07-07 16:38 EDT, Dmitry Stalnov CLA
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Claude Knaus CLA 2002-10-17 08:20:36 EDT
I-20021016 linux-gtk:

public class Woo {
  static void goo(char c) {
    System.out.println("char");
  }
  static void goo(int i) {
    System.out.println("i");
  }
  static int foo() {
    return 'a';
  }
  public static void main(String[] args) {
    goo(foo());
  }
}

1. execute the class
2. observe the output is "int"
3. inline method foo
4. execute the class
5. observe the output is "char"

characters are implicitely cast to int on return. When the method is inlined,
the implicit cast must be made explicit. If there's no ambiguity the cast could 
be omitted.

The fix should handle all kind of possible implicit conversions.

There's a similar case:

public class Zoo {
  static int foo() {
    return 'A';
  }
  public static void main(String[] args) {
    System.out.println("output: " + foo());
  }
}

Before inlining, the output is '65', after inlining, it's 'A'.

In the past I often got bitten by implicit conversions of characters to int, 
which is hard to debug.
Comment 1 Dmitry Stalnov CLA 2003-06-14 12:58:39 EDT
Created attachment 5197 [details]
Patch fixing the bug

The patch fixes the problem described in the bug report but there is one issue.
If access to overloaded function is restricted then no explicit cast is really
needed, in this case I still think it is safer to have cast in place.

Example:

public class Fruit {
}
public class Apple extends Fruit {
}
public class Woo {
	public static Fruit foo() {
		return new Apple();
	}
	private static void goo(Apple apple) {
	}
	public static void goo(Fruit fruit) {
	}
}
public class Test {
	public static void main(String[] args) {
		Woo.goo(Woo.foo());
	}
}

Woo.foo() will be inlined as (Fruit)new Apple() though goo(Apple apple) is
private in class Woo.
Comment 2 Dmitry Stalnov CLA 2003-06-19 08:59:35 EDT
Created attachment 5243 [details]
final patch fixing the bug

Previous version of the patch created unneeded explicit casts. This one doesn't
have such problems.
Comment 3 Dmitry Stalnov CLA 2003-06-19 09:04:27 EDT
Created attachment 5244 [details]
unit tests for the submitted patch

Eclipse 'Create Patch' command didn't include resources in generated patch.
Probably, because I put them into separate folder.
Comment 4 Dmitry Stalnov CLA 2003-06-19 09:05:16 EDT
Created attachment 5245 [details]
Resources for unit tests
Comment 5 Dirk Baeumer CLA 2003-06-19 12:22:41 EDT
I will look at the patch (very likely not before beginning of next week).
Comment 6 Dirk Baeumer CLA 2003-06-24 10:28:59 EDT
Dmitry,

nice work ! I released the patch after applying the following changes.

- adapted code to JDT/UI guidelines (f prefix for fields and asymmetric
  assignment)
- moved resolveInvokedType to ASTNodes and renamed it to
  getReceiverTypeBinding since it is a general useful method.
- moved code from ASTResolving to new class TypeRules to avoid layer breakers.
- shortened needsExplicitCast() a little bit since fInvocation == arguments(i)
  for 0 < i < number of arguments. No need to loop over it and to compare
  start offsets.

Besides these changes I think the findMethodsInHierarchy method should 
consider interfaces as well. Currently it only considers super classes. 
Additionally it is sufficient to collect all methods with the same number of 
parameters. Method foo(Woo) doesn't overload method foo(Woo, int). Furthermore 
the method canImplicitlyCall can be optimized. Since we only replace one 
argument it is sufficient to check this one. Are you interested in 
implementing these changes/optimizations.
Comment 7 Dirk Baeumer CLA 2003-06-24 10:35:51 EDT
And a question: what is the purpose of

// fixes bug #35905
if(expression instanceof CastExpression) {
	realArguments[i] = "(" + realArguments[i] + ")";
}
Comment 8 Dmitry Stalnov CLA 2003-06-25 10:39:13 EDT
Dirk, thank you for the review!

I thought about moving resolveInvokedType method to Bindings class and haven't 
done that only because wanted to limit my changes to as less number of files as 
possible. If you think ASTNodes serves that purpose better then I agree.

Method findMethodsInHierarchy was designed to be more generic than was required 
for this fix and again that was done to simplify movement of the method into 
Bindings afterwards. Now, taking into account your comments, I think that 
following set of methods can be added to ASTNodes class:
  - findMethodsInHierarchy(ITypeBinding type, String methodName) - it will 
return all methods with specified name, considering interfaces too
  - findMethodsInHierarchy(ITypeBinding type, String methodName, int 
numberOfArguments) - the same as previous but methods are additionally filtered 
with number of arguments
  - findMethodsInSuperclasses(ITypeBinding type, String methodName) - this one 
is equal to current implementation of findMethodsInHierarchy.
  - findMethodsInSuperclasses(ITypeBinding type, String methodName, int 
numberOfArguments) - the patch must be modified to use this method.
If you approve this change I will implement it.

Regarding comment #7. The change got into the patch accidentially and was 
inteded to fix bug #35905.

I'm definitely interested in further work on this bug.
Comment 9 Dirk Baeumer CLA 2003-06-25 12:47:28 EDT
I think we should start with one findMethodInHierarchy. It should take the 
type, the method name and the number of parameters. If we need another method 
we can add it later.

Have you thought about optimizing "canImplicitlyCall" as well ?

As said, I have released your fix to the HEAD so it is part of todays 
integration build.

So I am expecting another patch from you ;-)).
Comment 10 Dmitry Stalnov CLA 2003-06-25 17:52:07 EDT
I don't see a possibility how "canImplicitlyCall" can be optimized. Consider 
following code:

class Base {}
class Derived extends Base {}
class Woo {
  static void goo(String s, Base b) {
    System.out.println("String");
  }
  static void goo(Integer i, Derived d) {
    System.out.println("Integer");
  }
  static Base foo() {
    return new Derived();
  }
  public static void main(String[] args) {
    goo("String", foo());
  }
}
If only second parameters of foo() methods are taken into account then it 
looks like explicit cast is needed, but it is not since the first parameter 
defines which overloaded method will be invoked.

Actually, above example helped me to discover another case when the fix 
doesn't work. Here it is:

class Woo {
  static void goo(String s) {
    System.out.println("String");
  }
  static void goo(Integer i) {
    System.out.println("Integer");
  }
  static Integer foo() {
    return null;
  }
  public static void main(String[] args) {
    goo(foo());
  }
}
Inlining foo() will break the code. Method TypeRules.canAssign doesn't process 
null types correctly. I will fix it along with the "findMethodInHierarchy" 
change.
Comment 11 Dmitry Stalnov CLA 2003-06-26 03:45:49 EDT
Created attachment 5298 [details]
Code restructuring, fixed null types problem

I moved findMethodsInSuperclasses to Bindings because it takes ITypeBinding as
first parameter and ASTNodes class manipulates with AST nodes only.

Fixed the null types problem described in comment #10.

I'm not sure that additional check is neccessary in the following code:
  int argumentIndex= methodInvocation.arguments().indexOf(fInvocation);
  if (argumentIndex == -1)
    return false; // will never be executed
Since the compilation unit is compiled without errors, state of bindings is
up-to-date and consistent then argumentIndex will never be -1. Am I wrong?

When will the status of the bug change to FIXED ? :)
Comment 12 Dmitry Stalnov CLA 2003-06-26 03:47:03 EDT
Created attachment 5299 [details]
Unit test for patch #5298
Comment 13 Dirk Baeumer CLA 2003-06-27 10:57:10 EDT
Looking at your comment #10 I get the feeling that findMethodInHierarchy 
should be coded in the following way:

IMethod[] findMethodInHierarchy(ITypeBinding type, String methodName, 
ITypeBinding parameters[])

if (parameters[i] == null) then the parameter is ignored during method 
comparison.

This will find all methods that cause a potential conflict. So instead of 
opimizing canImplicitlyCall we should optimize findMethodInHierarchy.

What do you think ?

Regarding the two questions:

  if (argumentIndex == -1)
    return false; // will never be executed

is not needed. You are correct.

When will the bug be closed: we close the bug when all issues enumerated in 
this bug are solved. IMO we are 95% done. Only little tweaking here and there 
<g>.
Comment 14 Dmitry Stalnov CLA 2003-06-30 08:41:17 EDT
Created attachment 5315 [details]
Generalized Bindings.findMethodsInSuperclasses

I generalized method Bindings.findMethodsInSuperclasses as you suggested. The
change added one more loop traversing method arguments (see
Bindings.isEqualMethod) and array allocation when calling to
Bindings.findMethodsInSuperclasses from CallInliner. This can decrease
performance a little.

I also removed "if (argumentIndex == -1)" check and added another one
"if(methodInvocation.getExpression() == fTargetNode)" because target node can
be a receiver expression in the method invocation (see TestParenthesis unit
test).

Unit test for the patch is attached to comment #12.
Comment 15 Dmitry Stalnov CLA 2003-07-01 09:04:06 EDT
Created attachment 5324 [details]
Considered interfaces, created binding predicate

  Dirk, 

Sorry for the 'spam' but there is no limit to code improvements :)

First of all, I realized that interfaces should be considered too. Following
code demonstrates why it is so:

class Base {}
class Derived extends Base {}
interface I {
	public void foo(Derived d);
}
abstract class Goo implements I {
	public void foo(Base b) {
	}
}
class Woo extends Goo {
	public void foo(Derived d) {
	}
}
class Test {
	Base inlineMe() {
		return new Derived();
	}
	void main() {
		Goo goo = new Woo();
		goo.foo(inlineMe());
	}
}

Actually, we don't need to traverse all superclasses and interfaces searching
for ALL methods with the specified name and number of parameters. What we
really want is to find ONE(any) method that after inlining can be confused with
original invocation. That would save us many loop iterations and probably call
to type.getInterfaces(). I also noticed that there is many
Bindings.findMethod...s in Bindings class and the number tends to grow. That
can make it difficult to find an appropriate method, especially for newbies
like me :)

Considering above ideas I decided to employ predicate pattern. I created new
BindingPredicate interface and added several searching routines based on the
predicates to Bindings class. Method CallInliner.needsExlplicitCast was
simplified and canImplicitlyCall moved to new AmbiguousMethodPredicate class.

What do you think? Is it 100% now? ;)
Comment 16 Dirk Baeumer CLA 2003-07-01 13:16:00 EDT
Will look into it tomorrow morning. 
Comment 17 Dirk Baeumer CLA 2003-07-02 10:10:06 EDT
Dmitry,

I looked at the code and functional wise I think we are done. However I don't 
like the additional methods on Bindings since I am not sure if they are really 
useful (for example we can't remove the existing find* method or even make 
them wrappers around the new methods).

From looking at your new code I get the feeling that we are missing a type 
hierarchy visitor. The the different find methods would be different visitors 
and the Bindings class would only contain code to visit the hierarchy.

What do you think about this ? 
Comment 18 Dmitry Stalnov CLA 2003-07-07 16:38:09 EDT
Created attachment 5370 [details]
Replaced binding predicate with visitor

I modified the patch and replaced predicate with visitor as you suggested. The
new BindingVisitor is very simple but in the future it can be extended to the
scale of ASTVisitor if needed.

Take a look and let me know if it is what you meant.
Comment 19 Dirk Baeumer CLA 2003-07-14 11:58:24 EDT
Released path with two little changes:

- renamed BindingVisitor to TypeBindingVisitor
- made AmbiguousMethodAnalyzer an inner class of CallInliner (we don't use 
  secondary top level types in Eclipse).

Thanks for your contribution !!
Comment 20 Markus Keller CLA 2014-04-25 09:55:46 EDT
Bindings.visitInterfaces(ITypeBinding, TypeBindingVisitor) didn't visit superinterfaces of interfaces recursively.

Fixed with http://git.eclipse.org/c/jdt/eclipse.jdt.ui.git/commit/?id=7edc5f69024dacad9e60d721905913043dc5a57f