1 /******************************************************************************* 2 * Copyright (c) 2000, 2020 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 * Dmitry Stalnov (dstalnov@fusionone.com) - contributed fixes for: 14 * o bug "inline method - doesn't handle implicit cast" (see 15 * https://bugs.eclipse.org/bugs/show_bug.cgi?id=24941). 16 * o bug inline method: compile error (array related) 17 * (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=38471) 18 * o inline call that is used in a field initializer 19 * (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=38137) 20 * o inline call a field initializer: could detect self reference 21 * (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=44417) 22 * o Allow 'this' constructor to be inlined 23 * (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=38093) 24 * Nikolay Metchev <nikolaymetchev@gmail.com> - Anonymous class using final parameter breaks method inlining - https://bugs.eclipse.org/269401 25 * Microsoft Corporation - copied to jdt.core.manipulation 26 * Pierre-Yves B. <pyvesdev@gmail.com> - [inline] Inlining a local variable leads to ambiguity with overloaded methods - https://bugs.eclipse.org/434747 27 *******************************************************************************/ 28 package org.eclipse.jdt.internal.corext.refactoring.code; 29 30 import java.util.ArrayList; 31 import java.util.HashSet; 32 import java.util.List; 33 import java.util.Set; 34 35 import org.eclipse.core.runtime.Assert; 36 import org.eclipse.core.runtime.CoreException; 37 38 import org.eclipse.core.filebuffers.ITextFileBuffer; 39 40 import org.eclipse.text.edits.TextEdit; 41 import org.eclipse.text.edits.TextEditGroup; 42 43 import org.eclipse.jface.text.BadLocationException; 44 45 import org.eclipse.ltk.core.refactoring.RefactoringStatus; 46 import org.eclipse.ltk.core.refactoring.RefactoringStatusEntry; 47 48 import org.eclipse.jdt.core.ICompilationUnit; 49 import org.eclipse.jdt.core.dom.AST; 50 import org.eclipse.jdt.core.dom.ASTNode; 51 import org.eclipse.jdt.core.dom.ASTVisitor; 52 import org.eclipse.jdt.core.dom.ArrayInitializer; 53 import org.eclipse.jdt.core.dom.Assignment; 54 import org.eclipse.jdt.core.dom.Block; 55 import org.eclipse.jdt.core.dom.BodyDeclaration; 56 import org.eclipse.jdt.core.dom.CastExpression; 57 import org.eclipse.jdt.core.dom.CompilationUnit; 58 import org.eclipse.jdt.core.dom.DoStatement; 59 import org.eclipse.jdt.core.dom.EnhancedForStatement; 60 import org.eclipse.jdt.core.dom.Expression; 61 import org.eclipse.jdt.core.dom.FieldAccess; 62 import org.eclipse.jdt.core.dom.FieldDeclaration; 63 import org.eclipse.jdt.core.dom.ForStatement; 64 import org.eclipse.jdt.core.dom.IBinding; 65 import org.eclipse.jdt.core.dom.IMethodBinding; 66 import org.eclipse.jdt.core.dom.ITypeBinding; 67 import org.eclipse.jdt.core.dom.IVariableBinding; 68 import org.eclipse.jdt.core.dom.IfStatement; 69 import org.eclipse.jdt.core.dom.LabeledStatement; 70 import org.eclipse.jdt.core.dom.MethodDeclaration; 71 import org.eclipse.jdt.core.dom.MethodInvocation; 72 import org.eclipse.jdt.core.dom.Modifier; 73 import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword; 74 import org.eclipse.jdt.core.dom.Name; 75 import org.eclipse.jdt.core.dom.ParenthesizedExpression; 76 import org.eclipse.jdt.core.dom.ReturnStatement; 77 import org.eclipse.jdt.core.dom.SimpleName; 78 import org.eclipse.jdt.core.dom.Statement; 79 import org.eclipse.jdt.core.dom.SuperFieldAccess; 80 import org.eclipse.jdt.core.dom.SwitchStatement; 81 import org.eclipse.jdt.core.dom.ThisExpression; 82 import org.eclipse.jdt.core.dom.Type; 83 import org.eclipse.jdt.core.dom.VariableDeclarationFragment; 84 import org.eclipse.jdt.core.dom.VariableDeclarationStatement; 85 import org.eclipse.jdt.core.dom.WhileStatement; 86 import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; 87 import org.eclipse.jdt.core.dom.rewrite.ImportRewrite; 88 import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext; 89 import org.eclipse.jdt.core.dom.rewrite.ListRewrite; 90 91 import org.eclipse.jdt.internal.core.manipulation.JavaManipulationPlugin; 92 import org.eclipse.jdt.internal.core.manipulation.StubUtility; 93 import org.eclipse.jdt.internal.core.manipulation.dom.NecessaryParenthesesChecker; 94 import org.eclipse.jdt.internal.corext.CorextCore; 95 import org.eclipse.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext; 96 import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory; 97 import org.eclipse.jdt.internal.corext.dom.ASTNodes; 98 import org.eclipse.jdt.internal.corext.dom.CodeScopeBuilder; 99 import org.eclipse.jdt.internal.corext.dom.HierarchicalASTVisitor; 100 import org.eclipse.jdt.internal.corext.dom.LocalVariableIndex; 101 import org.eclipse.jdt.internal.corext.dom.Selection; 102 import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages; 103 import org.eclipse.jdt.internal.corext.refactoring.base.RefactoringStatusCodes; 104 import org.eclipse.jdt.internal.corext.refactoring.code.flow.FlowContext; 105 import org.eclipse.jdt.internal.corext.refactoring.code.flow.FlowInfo; 106 import org.eclipse.jdt.internal.corext.refactoring.code.flow.InputFlowAnalyzer; 107 import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.types.TypeEnvironment; 108 import org.eclipse.jdt.internal.corext.refactoring.util.JavaStatusContext; 109 import org.eclipse.jdt.internal.corext.refactoring.util.NoCommentSourceRangeComputer; 110 import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringFileBuffers; 111 112 public class CallInliner { 113 114 private ICompilationUnit fCUnit; 115 private ASTRewrite fRewrite; 116 private ImportRewrite fImportRewrite; 117 private ITextFileBuffer fBuffer; 118 private SourceProvider fSourceProvider; 119 private TypeEnvironment fTypeEnvironment; 120 121 private BodyDeclaration fBodyDeclaration; 122 private CodeScopeBuilder.Scope fRootScope; 123 private int fNumberOfLocals; 124 125 private ASTNode fInvocation; 126 127 private int fInsertionIndex; 128 private ListRewrite fListRewrite; 129 130 private boolean fNeedsStatement; 131 private ASTNode fTargetNode; 132 private FlowContext fFlowContext; 133 private FlowInfo fFlowInfo; 134 private CodeScopeBuilder.Scope fInvocationScope; 135 private boolean fFieldInitializer; 136 private List<VariableDeclarationStatement> fLocals; 137 private CallContext fContext; 138 139 private class InlineEvaluator extends HierarchicalASTVisitor { 140 private ParameterData fFormalArgument; 141 private boolean fResult; InlineEvaluator(ParameterData argument)142 public InlineEvaluator(ParameterData argument) { 143 fFormalArgument= argument; 144 } getResult()145 public boolean getResult() { 146 return fResult; 147 } setResult(boolean result)148 private boolean setResult(boolean result) { 149 fResult= result; 150 return false; 151 } 152 @Override visit(Expression node)153 public boolean visit(Expression node) { 154 int accessMode= fFormalArgument.getSimplifiedAccessMode(); 155 if (accessMode == FlowInfo.WRITE) 156 return setResult(false); 157 if (accessMode == FlowInfo.UNUSED) 158 return setResult(true); 159 if (ASTNodes.isLiteral(node)) 160 return setResult(true); 161 return setResult(fFormalArgument.getNumberOfAccesses() <= 1); 162 } 163 @Override visit(SimpleName node)164 public boolean visit(SimpleName node) { 165 IBinding binding= node.resolveBinding(); 166 if (binding instanceof IVariableBinding) { 167 int accessMode = fFormalArgument.getSimplifiedAccessMode(); 168 if (fFormalArgument.isFinal() && !Modifier.isFinal(binding.getModifiers())) { 169 return setResult(false); 170 } 171 if (accessMode == FlowInfo.READ || accessMode == FlowInfo.UNUSED) 172 return setResult(true); 173 // from now on we only have write accesses. 174 IVariableBinding vb= (IVariableBinding)binding; 175 if (vb.isField()) 176 return setResult(false); 177 return setResult(fFlowInfo.hasAccessMode(fFlowContext, vb, FlowInfo.UNUSED | FlowInfo.WRITE)); 178 } 179 return setResult(false); 180 } 181 @Override visit(FieldAccess node)182 public boolean visit(FieldAccess node) { 183 return visit(node.getName()); 184 } 185 @Override visit(SuperFieldAccess node)186 public boolean visit(SuperFieldAccess node) { 187 return visit(node.getName()); 188 } 189 @Override visit(ThisExpression node)190 public boolean visit(ThisExpression node) { 191 int accessMode= fFormalArgument.getSimplifiedAccessMode(); 192 if (accessMode == FlowInfo.READ || accessMode == FlowInfo.UNUSED) 193 return setResult(true); 194 return setResult(false); 195 } 196 } 197 CallInliner(ICompilationUnit unit, CompilationUnit targetAstRoot, SourceProvider provider)198 public CallInliner(ICompilationUnit unit, CompilationUnit targetAstRoot, SourceProvider provider) throws CoreException { 199 super(); 200 fCUnit= unit; 201 fBuffer= RefactoringFileBuffers.acquire(fCUnit); 202 fSourceProvider= provider; 203 fImportRewrite= StubUtility.createImportRewrite(targetAstRoot, true); 204 fLocals= new ArrayList<>(3); 205 fRewrite= ASTRewrite.create(targetAstRoot.getAST()); 206 fRewrite.setTargetSourceRangeComputer(new NoCommentSourceRangeComputer()); 207 fTypeEnvironment= new TypeEnvironment(); 208 } 209 dispose()210 public void dispose() { 211 try { 212 RefactoringFileBuffers.release(fCUnit); 213 } catch (CoreException exception) { 214 JavaManipulationPlugin.log(exception); 215 } 216 } 217 218 getImportEdit()219 public ImportRewrite getImportEdit() { 220 return fImportRewrite; 221 } 222 getTargetNode()223 public ASTNode getTargetNode() { 224 return fTargetNode; 225 } 226 initialize(BodyDeclaration declaration)227 public void initialize(BodyDeclaration declaration) { 228 fBodyDeclaration= declaration; 229 fRootScope= CodeScopeBuilder.perform(declaration, fSourceProvider.getDeclaration().resolveBinding()); 230 fNumberOfLocals= 0; 231 switch (declaration.getNodeType()) { 232 case ASTNode.METHOD_DECLARATION: 233 case ASTNode.INITIALIZER: 234 fNumberOfLocals= LocalVariableIndex.perform(declaration); 235 break; 236 } 237 } 238 initialize(ASTNode invocation, int severity)239 public RefactoringStatus initialize(ASTNode invocation, int severity) { 240 RefactoringStatus result= new RefactoringStatus(); 241 fInvocation= invocation; 242 fLocals= new ArrayList<>(3); 243 244 checkMethodDeclaration(result, severity); 245 if (result.getSeverity() >= severity) 246 return result; 247 248 initializeRewriteState(); 249 initializeTargetNode(); 250 flowAnalysis(); 251 252 fContext= new CallContext(fInvocation, fInvocationScope, fTargetNode.getNodeType(), fImportRewrite); 253 254 try { 255 computeRealArguments(); 256 computeReceiver(); 257 } catch (BadLocationException exception) { 258 JavaManipulationPlugin.log(exception); 259 } 260 checkInvocationContext(result, severity); 261 262 return result; 263 } 264 initializeRewriteState()265 private void initializeRewriteState() { 266 fFieldInitializer= false; 267 ASTNode parent= fInvocation.getParent(); 268 do { 269 if (parent instanceof FieldDeclaration) { 270 fFieldInitializer= true; 271 return; 272 } else if (parent instanceof Block) { 273 return; 274 } 275 parent= parent.getParent(); 276 } while (parent != null); 277 } 278 initializeTargetNode()279 private void initializeTargetNode() { 280 ASTNode parent= fInvocation.getParent(); 281 int nodeType= parent.getNodeType(); 282 if (nodeType == ASTNode.EXPRESSION_STATEMENT || nodeType == ASTNode.RETURN_STATEMENT) { 283 fTargetNode= parent; 284 } else { 285 fTargetNode= fInvocation; 286 } 287 } 288 289 // the checks depend on invocation context and therefore can't be done in SourceAnalyzer checkMethodDeclaration(RefactoringStatus result, int severity)290 private void checkMethodDeclaration(RefactoringStatus result, int severity) { 291 MethodDeclaration methodDeclaration= fSourceProvider.getDeclaration(); 292 // it is not allowed to inline constructor invocation only if it is used for class instance creation 293 // if constructor is invoked from another constructor then we can inline such invocation 294 if (fInvocation.getNodeType() != ASTNode.CONSTRUCTOR_INVOCATION && methodDeclaration.isConstructor()) { 295 result.addEntry(new RefactoringStatusEntry( 296 severity, 297 RefactoringCoreMessages.CallInliner_constructors, 298 JavaStatusContext.create(fCUnit, fInvocation))); 299 } 300 if (fSourceProvider.hasSuperMethodInvocation() && fInvocation.getNodeType() == ASTNode.METHOD_INVOCATION) { 301 Expression receiver= ((MethodInvocation)fInvocation).getExpression(); 302 if (receiver instanceof ThisExpression) { 303 result.addEntry(new RefactoringStatusEntry( 304 severity, 305 RefactoringCoreMessages.CallInliner_super_into_this_expression, 306 JavaStatusContext.create(fCUnit, fInvocation))); 307 } 308 } 309 } 310 checkInvocationContext(RefactoringStatus result, int severity)311 private void checkInvocationContext(RefactoringStatus result, int severity) { 312 if (fInvocation.getNodeType() == ASTNode.METHOD_INVOCATION) { 313 if (((MethodInvocation)fInvocation).resolveTypeBinding() == null) { 314 addEntry(result, RefactoringCoreMessages.CallInliner_receiver_type, 315 RefactoringStatusCodes.INLINE_METHOD_NULL_BINDING, severity); 316 return; 317 } 318 } 319 int nodeType= fTargetNode.getNodeType(); 320 if (nodeType == ASTNode.EXPRESSION_STATEMENT) { 321 if (fSourceProvider.isExecutionFlowInterrupted()) { 322 addEntry(result, RefactoringCoreMessages.CallInliner_execution_flow, 323 RefactoringStatusCodes.INLINE_METHOD_EXECUTION_FLOW, severity); 324 return; 325 } 326 } else if (nodeType == ASTNode.METHOD_INVOCATION) { 327 ASTNode parent= fTargetNode.getParent(); 328 if (isReturnStatement(parent)) { 329 //support inlining even if the execution flow is interrupted 330 return; 331 } 332 if (fSourceProvider.isExecutionFlowInterrupted()) { 333 addEntry(result, RefactoringCoreMessages.CallInliner_execution_flow, 334 RefactoringStatusCodes.INLINE_METHOD_EXECUTION_FLOW, severity); 335 return; 336 } 337 if (isAssignment(parent) || isSingleDeclaration(parent)) { 338 // we support inlining expression in assigment and initializers as 339 // long as the execution flow isn't interrupted. 340 return; 341 } else { 342 boolean isFieldDeclaration= ASTNodes.getParent(fInvocation, FieldDeclaration.class) != null; 343 if (!fSourceProvider.isSimpleFunction()) { 344 if (isMultiDeclarationFragment(parent)) { 345 addEntry(result, RefactoringCoreMessages.CallInliner_multiDeclaration, 346 RefactoringStatusCodes.INLINE_METHOD_INITIALIZER_IN_FRAGEMENT, severity); 347 } else if (isFieldDeclaration) { 348 addEntry(result, 349 RefactoringCoreMessages.CallInliner_field_initializer_simple, 350 RefactoringStatusCodes.INLINE_METHOD_FIELD_INITIALIZER, severity); 351 } else { 352 addEntry(result, RefactoringCoreMessages.CallInliner_simple_functions, 353 RefactoringStatusCodes.INLINE_METHOD_ONLY_SIMPLE_FUNCTIONS, severity); 354 } 355 return; 356 } 357 if (isFieldDeclaration) { 358 int argumentsCount= fContext.arguments.length; 359 for (int i= 0; i < argumentsCount; i++) { 360 ParameterData parameter= fSourceProvider.getParameterData(i); 361 if(parameter.isWrite()) { 362 addEntry(result, 363 RefactoringCoreMessages.CallInliner_field_initialize_write_parameter, 364 RefactoringStatusCodes.INLINE_METHOD_FIELD_INITIALIZER, severity); 365 return; 366 } 367 } 368 if(fLocals.size() > 0) { 369 addEntry(result, 370 RefactoringCoreMessages.CallInliner_field_initialize_new_local, 371 RefactoringStatusCodes.INLINE_METHOD_FIELD_INITIALIZER, severity); 372 return; 373 } 374 // verify that the field is not referenced by the initializer method 375 VariableDeclarationFragment variable= (VariableDeclarationFragment)ASTNodes.getParent(fInvocation, ASTNode.VARIABLE_DECLARATION_FRAGMENT); 376 if(fSourceProvider.isVariableReferenced(variable.resolveBinding())) { 377 addEntry(result, 378 RefactoringCoreMessages.CallInliner_field_initialize_self_reference, 379 RefactoringStatusCodes.INLINE_METHOD_FIELD_INITIALIZER, severity); 380 return; 381 } 382 } 383 } 384 } 385 } 386 isAssignment(ASTNode node)387 private static boolean isAssignment(ASTNode node) { 388 return node instanceof Assignment; 389 } 390 isReturnStatement(ASTNode node)391 private static boolean isReturnStatement(ASTNode node) { 392 return node instanceof ReturnStatement; 393 } 394 isSingleDeclaration(ASTNode node)395 private static boolean isSingleDeclaration(ASTNode node) { 396 int type= node.getNodeType(); 397 if (type == ASTNode.SINGLE_VARIABLE_DECLARATION) 398 return true; 399 if (type == ASTNode.VARIABLE_DECLARATION_FRAGMENT) { 400 node= node.getParent(); 401 if (node.getNodeType() == ASTNode.VARIABLE_DECLARATION_STATEMENT) { 402 VariableDeclarationStatement vs= (VariableDeclarationStatement)node; 403 return vs.fragments().size() == 1; 404 } 405 } 406 return false; 407 } 408 isMultiDeclarationFragment(ASTNode node)409 private static boolean isMultiDeclarationFragment(ASTNode node) { 410 int nodeType= node.getNodeType(); 411 if (nodeType == ASTNode.VARIABLE_DECLARATION_FRAGMENT) { 412 node= node.getParent(); 413 if (node.getNodeType() == ASTNode.VARIABLE_DECLARATION_STATEMENT) { 414 VariableDeclarationStatement vs= (VariableDeclarationStatement)node; 415 return vs.fragments().size() > 1; 416 } 417 } 418 return false; 419 } 420 addEntry(RefactoringStatus result, String message, int code, int severity)421 private void addEntry(RefactoringStatus result, String message, int code, int severity) { 422 result.addEntry(new RefactoringStatusEntry( 423 severity, message, 424 JavaStatusContext.create(fCUnit, fInvocation), 425 CorextCore.getPluginId(), 426 code, null)); 427 } 428 flowAnalysis()429 private void flowAnalysis() { 430 fInvocationScope= fRootScope.findScope(fTargetNode.getStartPosition(), fTargetNode.getLength()); 431 fInvocationScope.setCursor(fTargetNode.getStartPosition()); 432 fFlowContext= new FlowContext(0, fNumberOfLocals + 1); 433 fFlowContext.setConsiderAccessMode(true); 434 fFlowContext.setComputeMode(FlowContext.ARGUMENTS); 435 Selection selection= Selection.createFromStartLength(fInvocation.getStartPosition(), fInvocation.getLength()); 436 switch (fBodyDeclaration.getNodeType()) { 437 case ASTNode.INITIALIZER: 438 case ASTNode.FIELD_DECLARATION: 439 case ASTNode.METHOD_DECLARATION: 440 case ASTNode.ENUM_CONSTANT_DECLARATION: 441 fFlowInfo= new InputFlowAnalyzer(fFlowContext, selection, true).perform(fBodyDeclaration); 442 break; 443 default: 444 Assert.isTrue(false, "Should not happen"); //$NON-NLS-1$ 445 } 446 } 447 perform(TextEditGroup textEditGroup)448 public RefactoringStatus perform(TextEditGroup textEditGroup) throws CoreException { 449 RefactoringStatus result= new RefactoringStatus(); 450 String[] blocks= fSourceProvider.getCodeBlocks(fContext, fImportRewrite); 451 if(!fFieldInitializer) { 452 initializeInsertionPoint(fSourceProvider.getNumberOfStatements() + fLocals.size()); 453 } 454 455 addNewLocals(textEditGroup); 456 replaceCall(result, blocks, textEditGroup); 457 return result; 458 } 459 getModifications()460 public TextEdit getModifications() { 461 return fRewrite.rewriteAST(fBuffer.getDocument(), fCUnit.getJavaProject().getOptions(true)); 462 } 463 computeRealArguments()464 private void computeRealArguments() { 465 List<Expression> arguments= Invocations.getArguments(fInvocation); 466 Set<Expression> canNotInline= crossCheckArguments(arguments); 467 boolean needsVarargBoxing= needsVarargBoxing(arguments); 468 int varargIndex= fSourceProvider.getVarargIndex(); 469 AST ast= fInvocation.getAST(); 470 Expression[] realArguments= new Expression[needsVarargBoxing ? varargIndex + 1 : arguments.size()]; 471 for (int i= 0; i < (needsVarargBoxing ? varargIndex : arguments.size()); i++) { 472 Expression expression= arguments.get(i); 473 ParameterData parameter= fSourceProvider.getParameterData(i); 474 if (canInline(expression, parameter) && !canNotInline.contains(expression)) { 475 realArguments[i]= expression; 476 } else { 477 String name= fInvocationScope.createName(parameter.getName(), true); 478 realArguments[i]= ast.newSimpleName(name); 479 VariableDeclarationStatement local= createLocalDeclaration(parameter.getTypeBinding(), name, (Expression) fRewrite.createCopyTarget(expression)); 480 if (parameter.isFinal()) { 481 local.modifiers().add(fInvocation.getAST().newModifier(ModifierKeyword.FINAL_KEYWORD)); 482 } 483 fLocals.add(local); 484 } 485 } 486 if (needsVarargBoxing) { 487 ParameterData parameter= fSourceProvider.getParameterData(varargIndex); 488 String name= fInvocationScope.createName(parameter.getName(), true); 489 realArguments[varargIndex]= ast.newSimpleName(name); 490 Type type= fImportRewrite.addImport(parameter.getTypeBinding(), ast); 491 VariableDeclarationFragment fragment= ast.newVariableDeclarationFragment(); 492 fragment.setName(ast.newSimpleName(name)); 493 ArrayInitializer initializer= ast.newArrayInitializer(); 494 for (int i= varargIndex; i < arguments.size(); i++) { 495 initializer.expressions().add(fRewrite.createCopyTarget(arguments.get(i))); 496 } 497 fragment.setInitializer(initializer); 498 VariableDeclarationStatement decl= ast.newVariableDeclarationStatement(fragment); 499 decl.setType(type); 500 fLocals.add(decl); 501 } 502 fContext.compilationUnit= fCUnit; 503 fContext.arguments= realArguments; 504 } 505 needsVarargBoxing(List<Expression> arguments)506 private boolean needsVarargBoxing(List<Expression> arguments) { 507 if (!fSourceProvider.isVarargs()) 508 return false; 509 /* 510 if (!fSourceProvider.hasArrayAccess()) 511 return false; 512 */ 513 int index= fSourceProvider.getVarargIndex(); 514 // we have varags but the call doesn't pass any arguments 515 if (index >= arguments.size()) 516 return true; 517 // parameter is array type 518 // one arg 519 if (index == arguments.size() - 1) { 520 ITypeBinding argument= arguments.get(index).resolveTypeBinding(); 521 if (argument == null) 522 return false; 523 ITypeBinding parameter= fSourceProvider.getParameterData(index).getTypeBinding(); 524 return !fTypeEnvironment.create(argument).canAssignTo(fTypeEnvironment.create(parameter)); 525 } 526 return true; 527 } 528 computeReceiver()529 private void computeReceiver() throws BadLocationException { 530 Expression receiver= Invocations.getExpression(fInvocation); 531 if (receiver == null) 532 return; 533 final boolean isName= receiver instanceof Name; 534 if (isName) 535 fContext.receiverIsStatic= ((Name)receiver).resolveBinding() instanceof ITypeBinding; 536 if (ASTNodes.isLiteral(receiver) || isName || receiver instanceof ThisExpression) { 537 fContext.receiver= fBuffer.getDocument().get(receiver.getStartPosition(), receiver.getLength()); 538 return; 539 } 540 switch(fSourceProvider.getReceiversToBeUpdated()) { 541 case 0: 542 // Make sure we evaluate the current receiver. Best is to assign to 543 // local. 544 fLocals.add(createLocalDeclaration( 545 receiver.resolveTypeBinding(), 546 fInvocationScope.createName("r", true), //$NON-NLS-1$ 547 (Expression)fRewrite.createCopyTarget(receiver))); 548 return; 549 case 1: 550 fContext.receiver= fBuffer.getDocument().get(receiver.getStartPosition(), receiver.getLength()); 551 return; 552 default: 553 String local= fInvocationScope.createName("r", true); //$NON-NLS-1$ 554 fLocals.add(createLocalDeclaration( 555 receiver.resolveTypeBinding(), 556 local, 557 (Expression)fRewrite.createCopyTarget(receiver))); 558 fContext.receiver= local; 559 return; 560 } 561 } 562 addNewLocals(TextEditGroup textEditGroup)563 private void addNewLocals(TextEditGroup textEditGroup) { 564 if (fLocals.isEmpty()) 565 return; 566 for (VariableDeclarationStatement variableDeclarationStatement : fLocals) { 567 ASTNode element= variableDeclarationStatement; 568 fListRewrite.insertAt(element, fInsertionIndex++, textEditGroup); 569 } 570 } 571 replaceCall(RefactoringStatus status, String[] blocks, TextEditGroup textEditGroup)572 private void replaceCall(RefactoringStatus status, String[] blocks, TextEditGroup textEditGroup) { 573 // Inline empty body 574 if (blocks.length == 0 && fTargetNode != null) { 575 if (fNeedsStatement) { 576 fRewrite.replace(fTargetNode, fTargetNode.getAST().newEmptyStatement(), textEditGroup); 577 } else { 578 fRewrite.remove(fTargetNode, textEditGroup); 579 } 580 } else { 581 ASTNode node= null; 582 for (int i= 0; i < blocks.length - 1; i++) { 583 node= fRewrite.createStringPlaceholder(blocks[i], ASTNode.RETURN_STATEMENT); 584 fListRewrite.insertAt(node, fInsertionIndex++, textEditGroup); 585 } 586 String block= blocks[blocks.length - 1]; 587 // We can inline a call where the declaration is a function and the call itself 588 // is a statement. In this case we have to create a temporary variable if the 589 // returned expression must be evaluated. 590 if (fContext.callMode == ASTNode.EXPRESSION_STATEMENT && fSourceProvider.hasReturnValue()) { 591 if (fSourceProvider.mustEvaluateReturnedExpression()) { 592 if (fSourceProvider.returnValueNeedsLocalVariable()) { 593 IMethodBinding invocation= Invocations.resolveBinding(fInvocation); 594 node= createLocalDeclaration( 595 invocation.getReturnType(), 596 fInvocationScope.createName(fSourceProvider.getMethodName(), true), 597 (Expression)fRewrite.createStringPlaceholder(block, ASTNode.METHOD_INVOCATION)); 598 } else { 599 node= fRewrite.getAST().newExpressionStatement( 600 (Expression)fRewrite.createStringPlaceholder(block, ASTNode.METHOD_INVOCATION)); 601 } 602 } else { 603 node= null; 604 } 605 } else if (fTargetNode instanceof Expression) { 606 node= fRewrite.createStringPlaceholder(block, ASTNode.METHOD_INVOCATION); 607 608 // fixes bug #24941 609 if (needsExplicitCast(status)) { 610 AST ast= node.getAST(); 611 CastExpression castExpression= ast.newCastExpression(); 612 Type returnType= fImportRewrite.addImport(fSourceProvider.getReturnType(), ast); 613 castExpression.setType(returnType); 614 615 if (NecessaryParenthesesChecker.needsParentheses(fSourceProvider.getReturnExpressions().get(0), castExpression, CastExpression.EXPRESSION_PROPERTY)) { 616 ParenthesizedExpression parenthesized= ast.newParenthesizedExpression(); 617 parenthesized.setExpression((Expression)node); 618 node= parenthesized; 619 } 620 621 castExpression.setExpression((Expression)node); 622 node= castExpression; 623 624 if (NecessaryParenthesesChecker.needsParentheses(castExpression, fTargetNode.getParent(), fTargetNode.getLocationInParent())) { 625 ParenthesizedExpression parenthesized= ast.newParenthesizedExpression(); 626 parenthesized.setExpression((Expression)node); 627 node= parenthesized; 628 } 629 } else if (fSourceProvider.needsReturnedExpressionParenthesis(fTargetNode.getParent(), fTargetNode.getLocationInParent())) { 630 ParenthesizedExpression pExp= fTargetNode.getAST().newParenthesizedExpression(); 631 pExp.setExpression((Expression)node); 632 node= pExp; 633 } 634 } else { 635 node= fRewrite.createStringPlaceholder(block, ASTNode.RETURN_STATEMENT); 636 } 637 638 // Now replace the target node with the source node 639 if (node != null) { 640 if (fTargetNode == null) { 641 fListRewrite.insertAt(node, fInsertionIndex++, textEditGroup); 642 } else { 643 fRewrite.replace(fTargetNode, node, textEditGroup); 644 } 645 } else { 646 if (fTargetNode != null) { 647 fRewrite.remove(fTargetNode, textEditGroup); 648 } 649 } 650 } 651 } 652 653 /** 654 * @param status the status 655 * @return <code>true</code> if explicit cast is needed otherwise <code>false</code> 656 */ needsExplicitCast(RefactoringStatus status)657 private boolean needsExplicitCast(RefactoringStatus status) { 658 // if the return type of the method is the same as the type of the 659 // returned expression then we don't need an explicit cast. 660 if (fSourceProvider.returnTypeMatchesReturnExpressions()) 661 return false; 662 663 List<Expression> returnExprs= fSourceProvider.getReturnExpressions(); 664 // it is inferred that only methods consisting of a single 665 // return statement can be inlined as parameters in other 666 // method invocations 667 if (returnExprs.size() != 1) 668 return false; 669 670 if (fTargetNode.getLocationInParent() == MethodInvocation.ARGUMENTS_PROPERTY) { 671 MethodInvocation methodInvocation= (MethodInvocation)fTargetNode.getParent(); 672 if(methodInvocation.getExpression() == fTargetNode) 673 return false; 674 IMethodBinding method= methodInvocation.resolveMethodBinding(); 675 if (method == null) { 676 status.addError(RefactoringCoreMessages.CallInliner_cast_analysis_error, 677 JavaStatusContext.create(fCUnit, methodInvocation)); 678 return false; 679 } 680 681 ITypeBinding parameterType= returnExprs.get(0).resolveTypeBinding(); 682 return ASTNodes.isTargetAmbiguous((Expression) fTargetNode, parameterType); 683 } else { 684 ITypeBinding explicitCast= ASTNodes.getExplicitCast(returnExprs.get(0), (Expression)fTargetNode); 685 return explicitCast != null; 686 } 687 } 688 createLocalDeclaration(ITypeBinding type, String name, Expression initializer)689 private VariableDeclarationStatement createLocalDeclaration(ITypeBinding type, String name, Expression initializer) { 690 ImportRewriteContext context= new ContextSensitiveImportRewriteContext(fTargetNode, fImportRewrite); 691 String typeName= fImportRewrite.addImport(type, context); 692 VariableDeclarationStatement decl= (VariableDeclarationStatement)ASTNodeFactory.newStatement( 693 fInvocation.getAST(), typeName + " " + name + ";"); //$NON-NLS-1$ //$NON-NLS-2$ 694 ((VariableDeclarationFragment)decl.fragments().get(0)).setInitializer(initializer); 695 return decl; 696 } 697 698 /** 699 * Checks whether arguments are passed to the method which do some assignments 700 * inside the expression. If so these arguments can't be inlined into the 701 * calling method since the assignments might be reorder. An example is: 702 * <code> 703 * add((field=args).length,field.hashCode()); 704 * </code> 705 * Field might not be initialized when the arguments are reorder in the called 706 * method. 707 * @param arguments the arguments 708 * @return all arguments that cannot be inlined 709 */ crossCheckArguments(List<Expression> arguments)710 private Set<Expression> crossCheckArguments(List<Expression> arguments) { 711 final Set<IBinding> assigned= new HashSet<>(); 712 final Set<Expression> result= new HashSet<>(); 713 for (Expression expression : arguments) { 714 expression.accept(new ASTVisitor() { 715 @Override 716 public boolean visit(Assignment node) { 717 Expression lhs= node.getLeftHandSide(); 718 if (lhs instanceof Name) { 719 IBinding binding= ((Name)lhs).resolveBinding(); 720 if (binding instanceof IVariableBinding) { 721 assigned.add(binding); 722 result.add(expression); 723 } 724 } 725 return true; 726 } 727 }); 728 } 729 for (Expression expression : arguments) { 730 if (!result.contains(expression)) { 731 expression.accept(new HierarchicalASTVisitor() { 732 @Override 733 public boolean visit(Name node) { 734 IBinding binding= node.resolveBinding(); 735 if (binding != null && assigned.contains(binding)) 736 result.add(expression); 737 return false; 738 } 739 }); 740 } 741 } 742 return result; 743 } 744 canInline(Expression actualParameter, ParameterData formalParameter)745 private boolean canInline(Expression actualParameter, ParameterData formalParameter) { 746 InlineEvaluator evaluator= new InlineEvaluator(formalParameter); 747 actualParameter.accept(evaluator); 748 return evaluator.getResult(); 749 } 750 initializeInsertionPoint(int nos)751 private void initializeInsertionPoint(int nos) { 752 fInsertionIndex= -1; 753 fNeedsStatement= false; 754 // if we have a constructor invocation the invocation itself is already a statement 755 ASTNode parentStatement= fInvocation instanceof Statement 756 ? fInvocation 757 : ASTNodes.getParent(fInvocation, Statement.class); 758 if (parentStatement == null) 759 return; 760 761 ASTNode container= parentStatement.getParent(); 762 int type= container.getNodeType(); 763 if (type == ASTNode.BLOCK) { 764 Block block= (Block)container; 765 fListRewrite= fRewrite.getListRewrite(block, Block.STATEMENTS_PROPERTY); 766 fInsertionIndex= fListRewrite.getRewrittenList().indexOf(parentStatement); 767 } else if (type == ASTNode.SWITCH_STATEMENT) { 768 SwitchStatement switchStatement= (SwitchStatement)container; 769 fListRewrite= fRewrite.getListRewrite(switchStatement, SwitchStatement.STATEMENTS_PROPERTY); 770 fInsertionIndex= fListRewrite.getRewrittenList().indexOf(parentStatement); 771 } else if (isControlStatement(container) || type == ASTNode.LABELED_STATEMENT) { 772 fNeedsStatement= true; 773 if (nos > 1 || needsBlockAroundDanglingIf()) { 774 Block block= fInvocation.getAST().newBlock(); 775 fInsertionIndex= 0; 776 Statement currentStatement= null; 777 switch(type) { 778 case ASTNode.LABELED_STATEMENT: 779 currentStatement= ((LabeledStatement)container).getBody(); 780 break; 781 case ASTNode.FOR_STATEMENT: 782 currentStatement= ((ForStatement)container).getBody(); 783 break; 784 case ASTNode.ENHANCED_FOR_STATEMENT: 785 currentStatement= ((EnhancedForStatement)container).getBody(); 786 break; 787 case ASTNode.WHILE_STATEMENT: 788 currentStatement= ((WhileStatement)container).getBody(); 789 break; 790 case ASTNode.DO_STATEMENT: 791 currentStatement= ((DoStatement)container).getBody(); 792 break; 793 case ASTNode.IF_STATEMENT: 794 IfStatement node= (IfStatement)container; 795 Statement thenPart= node.getThenStatement(); 796 if (fTargetNode == thenPart || ASTNodes.isParent(fTargetNode, thenPart)) { 797 currentStatement= thenPart; 798 } else { 799 currentStatement= node.getElseStatement(); 800 } 801 break; 802 } 803 Assert.isNotNull(currentStatement); 804 fRewrite.replace(currentStatement, block, null); 805 fListRewrite= fRewrite.getListRewrite(block, Block.STATEMENTS_PROPERTY); 806 // The method to be inlined is not the body of the control statement. 807 if (currentStatement != fTargetNode) { 808 fListRewrite.insertLast(fRewrite.createCopyTarget(currentStatement), null); 809 } else { 810 // We can't replace a copy with something else. So we 811 // have to insert all statements to be inlined. 812 fTargetNode= null; 813 } 814 } 815 } 816 // We only insert one new statement or we delete the existing call. 817 // So there is no need to have an insertion index. 818 } 819 needsBlockAroundDanglingIf()820 private boolean needsBlockAroundDanglingIf() { 821 /* see https://bugs.eclipse.org/bugs/show_bug.cgi?id=169331 822 * 823 * Situation: 824 * boolean a, b; 825 * void toInline() { 826 * if (a) 827 * hashCode(); 828 * } 829 * void m() { 830 * if (b) 831 * toInline(); 832 * else 833 * toString(); 834 * } 835 * => needs block around inlined "if (a)..." to avoid attaching else to wrong if. 836 */ 837 return fTargetNode.getLocationInParent() == IfStatement.THEN_STATEMENT_PROPERTY 838 && fTargetNode.getParent().getStructuralProperty(IfStatement.ELSE_STATEMENT_PROPERTY) != null 839 && fSourceProvider.isDangligIf(); 840 } 841 isControlStatement(ASTNode node)842 private boolean isControlStatement(ASTNode node) { 843 int type= node.getNodeType(); 844 return type == ASTNode.IF_STATEMENT || type == ASTNode.FOR_STATEMENT || type == ASTNode.ENHANCED_FOR_STATEMENT || 845 type == ASTNode.WHILE_STATEMENT || type == ASTNode.DO_STATEMENT; 846 } 847 } 848