package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInsight.NullableNotNullManager;
-import com.intellij.lang.ASTNode;
-import com.intellij.lang.TreeBackedLighterAST;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.RecursionManager;
-import com.intellij.psi.PsiCodeBlock;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.PsiType;
}
return CachedValuesManager.getCachedValue(method, () -> {
- TreeBackedLighterAST tree = new TreeBackedLighterAST(method.getContainingFile().getNode());
- PsiCodeBlock body = method.getBody();
- assert body != null;
- ASTNode methodNode = method.getNode();
- ASTNode bodyNode = body.getNode();
- List<PreContract> preContracts = methodNode == null || bodyNode == null ? Collections.emptyList() :
- new ContractInferenceInterpreter(tree, TreeBackedLighterAST.wrap(methodNode), TreeBackedLighterAST.wrap(bodyNode)).inferContracts();
- List<MethodContract> result = RecursionManager.doPreventingRecursion(method, true, () -> postProcessContracts(method, body, preContracts));
+ MethodData data = ContractInferenceIndexKt.getIndexedData(method);
+ List<PreContract> preContracts = data == null ? Collections.emptyList() : data.getContracts();
+ List<MethodContract> result = RecursionManager.doPreventingRecursion(method, true, () -> postProcessContracts(method, data, preContracts));
if (result == null) result = Collections.emptyList();
return CachedValueProvider.Result.create(result, method, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT);
});
}
@NotNull
- private static List<MethodContract> postProcessContracts(@NotNull PsiMethod method, @NotNull PsiCodeBlock body, List<PreContract> rawContracts) {
- List<MethodContract> contracts = ContainerUtil.concat(rawContracts, c -> c.toContracts(method, body));
+ private static List<MethodContract> postProcessContracts(@NotNull PsiMethod method, MethodData data, List<PreContract> rawContracts) {
+ List<MethodContract> contracts = ContainerUtil.concat(rawContracts, c -> c.toContracts(method, data.methodBody(method)));
if (contracts.isEmpty()) return Collections.emptyList();
final PsiType returnType = method.getReturnType();
--- /dev/null
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.codeInspection.dataFlow
+
+import com.intellij.ide.highlighter.JavaFileType
+import com.intellij.lang.LighterAST
+import com.intellij.lang.LighterASTNode
+import com.intellij.lang.TreeBackedLighterAST
+import com.intellij.openapi.util.Ref
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.impl.source.JavaFileElementType
+import com.intellij.psi.impl.source.JavaLightStubBuilder
+import com.intellij.psi.impl.source.PsiFileImpl
+import com.intellij.psi.impl.source.PsiMethodImpl
+import com.intellij.psi.impl.source.tree.JavaElementType
+import com.intellij.psi.impl.source.tree.LightTreeUtil
+import com.intellij.psi.impl.source.tree.RecursiveLighterASTNodeWalkingVisitor
+import com.intellij.psi.search.GlobalSearchScope
+import com.intellij.psi.util.CachedValueProvider
+import com.intellij.psi.util.CachedValuesManager
+import com.intellij.util.indexing.*
+import com.intellij.util.io.DataExternalizer
+import com.intellij.util.io.IntInlineKeyDescriptor
+import java.util.*
+
+/**
+ * @author peter
+ */
+
+private val INDEX_ID = ID.create<Int, MethodData>("java.inferred.contracts")
+
+class ContractInferenceIndex : FileBasedIndexExtension<Int, MethodData>(), PsiDependentIndex {
+ override fun getName() = INDEX_ID
+ override fun getVersion() = 0
+ override fun dependsOnFileContent() = true
+ override fun getKeyDescriptor() = IntInlineKeyDescriptor()
+ override fun getValueExternalizer(): DataExternalizer<MethodData> = MethodDataExternalizer
+
+ override fun getInputFilter() = FileBasedIndex.InputFilter {
+ it.fileType == JavaFileType.INSTANCE && JavaFileElementType.isInSourceContent(it)
+ }
+
+ override fun getIndexer() = DataIndexer<Int, MethodData, FileContent> { fc ->
+ val result = HashMap<Int, MethodData>()
+
+ val tree = (fc as FileContentImpl).lighterASTForPsiDependentIndex
+ object : RecursiveLighterASTNodeWalkingVisitor(tree) {
+ var methodIndex = 0
+
+ override fun visitNode(element: LighterASTNode) {
+ if (element.tokenType === JavaElementType.METHOD) {
+ calcData(tree, element)?.let { data -> result[methodIndex] = data }
+ methodIndex++
+ }
+
+ if (JavaLightStubBuilder.isCodeBlockWithoutStubs(element)) return
+
+ super.visitNode(element)
+ }
+ }.visitNode(tree.root)
+
+ result
+ }
+
+}
+
+private fun calcData(tree: LighterAST, method: LighterASTNode): MethodData? {
+ val body = LightTreeUtil.firstChildOfType(tree, method, JavaElementType.CODE_BLOCK) ?: return null
+
+ val nullity = NullityInference.doInferNullity(tree, body)
+ val purity = PurityInference.doInferPurity(body, tree)
+ val contracts = ContractInferenceInterpreter(tree, method, body).inferContracts()
+ if (nullity == null && purity == null && !contracts.isNotEmpty()) return null
+
+ return MethodData(nullity, purity, contracts, body.startOffset, body.endOffset)
+}
+
+fun getIndexedData(method: PsiMethod): MethodData? {
+ if (method !is PsiMethodImpl || !InferenceFromSourceUtil.shouldInferFromSource(method)) return null
+ val vFile = method.containingFile.virtualFile ?: return calcNonPhysicalMethodData(method)
+
+ val ref = Ref<MethodData>()
+ val scope = GlobalSearchScope.fileScope(method.project, vFile)
+ FileBasedIndex.getInstance().processValues(INDEX_ID, methodIndex(method), vFile, { file, data -> ref.set(data); true }, scope)
+ return ref.get()
+}
+
+private fun methodIndex(method: PsiMethodImpl): Int {
+ val file = method.containingFile as PsiFileImpl
+ val stubTree = file.stubTree ?: file.calcStubTree()
+ return stubTree.plainList.filter { it.stubType == JavaElementType.METHOD }.map { it.psi }.indexOf(method)
+}
+
+private fun calcNonPhysicalMethodData(method: PsiMethodImpl): MethodData? {
+ return CachedValuesManager.getCachedValue(method) {
+ CachedValueProvider.Result(calcData(method.containingFile.node.lighterAST, TreeBackedLighterAST.wrap(method.node)), method)
+ }
+}
\ No newline at end of file
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
return paramList != null ? getChildrenOfType(myTree, paramList, PARAMETER) : Collections.emptyList();
}
+ @NotNull
List<PreContract> inferContracts() {
LighterASTNode[] statements = getStatements(myBody);
if (statements.length == 0) return Collections.emptyList();
return asPreContracts(toContracts(states, NOT_NULL_VALUE));
}
if (type == METHOD_CALL_EXPRESSION) {
- return Collections.singletonList(new MethodCallContract(ExpressionRange.create(expr, myBody.getStartOffset()), states));
+ return Collections.singletonList(new MethodCallContract(ExpressionRange.create(expr, myBody.getStartOffset()),
+ ContainerUtil.map(states, Arrays::asList)));
}
final ValueConstraint constraint = getLiteralConstraint(expr);
private static boolean calcShouldInferFromSource(@NotNull PsiMethod method) {
if (isLibraryCode(method) ||
method.hasModifierProperty(PsiModifier.ABSTRACT) ||
- PsiUtil.canBeOverriden(method) ||
- method.getBody() == null) {
+ PsiUtil.canBeOverriden(method)) {
return false;
}
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import java.util.Arrays;
import java.util.List;
/**
return args;
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof MethodContract)) return false;
+
+ MethodContract contract = (MethodContract)o;
+
+ if (!Arrays.equals(arguments, contract.arguments)) return false;
+ if (returnValue != contract.returnValue) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 0;
+ for (ValueConstraint argument : arguments) {
+ result = 31 * result + argument.ordinal();
+ }
+ result = 31 * result + returnValue.ordinal();
+ return result;
+ }
+
@Override
public String toString() {
return StringUtil.join(arguments, constraint -> constraint.toString(), ", ") + " -> " + returnValue;
--- /dev/null
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.codeInspection.dataFlow
+
+import com.intellij.util.io.DataExternalizer
+import com.intellij.util.io.DataInputOutputUtil
+import com.intellij.util.io.IOUtil
+import java.io.DataInput
+import java.io.DataOutput
+
+/**
+ * @author peter
+ */
+internal object MethodDataExternalizer : DataExternalizer<MethodData> {
+
+ override fun save(out: DataOutput, data: MethodData) {
+ writeNullable(out, data.nullity) { writeNullity(out, it) }
+ writeNullable(out, data.purity) { writePurity(out, it) }
+ writeList(out, data.contracts) { writeContract(out, it) }
+ DataInputOutputUtil.writeINT(out, data.bodyStart)
+ DataInputOutputUtil.writeINT(out, data.bodyEnd)
+ }
+
+ override fun read(input: DataInput): MethodData {
+ val nullity = readNullable(input) { readNullity(input) }
+ val purity = readNullable(input) { readPurity(input) }
+ val contracts = readList(input) { readContract(input) }
+ return MethodData(nullity, purity, contracts, DataInputOutputUtil.readINT(input), DataInputOutputUtil.readINT(input))
+ }
+
+ private fun writeNullity(out: DataOutput, nullity: NullityInferenceResult) = when (nullity) {
+ is NullityInferenceResult.Predefined -> { out.writeByte(0); out.writeByte(nullity.value.ordinal) }
+ is NullityInferenceResult.FromDelegate -> { out.writeByte(1); writeRanges(out, nullity.delegateCalls) }
+ else -> throw IllegalArgumentException(nullity.toString())
+ }
+ private fun readNullity(input: DataInput): NullityInferenceResult = when (input.readByte().toInt()) {
+ 0 -> NullityInferenceResult.Predefined(Nullness.values()[input.readByte().toInt()])
+ else -> NullityInferenceResult.FromDelegate(readRanges(input))
+ }
+
+ private fun writeRanges(out: DataOutput, ranges: List<ExpressionRange>) = writeList(out, ranges) { writeRange(out, it) }
+ private fun readRanges(input: DataInput) = readList(input) { readRange(input) }
+
+ private fun writeRange(out: DataOutput, range: ExpressionRange) {
+ DataInputOutputUtil.writeINT(out, range.startOffset)
+ DataInputOutputUtil.writeINT(out, range.endOffset)
+ }
+ private fun readRange(input: DataInput) = ExpressionRange(DataInputOutputUtil.readINT(input), DataInputOutputUtil.readINT(input))
+
+ private fun writePurity(out: DataOutput, purity: PurityInferenceResult) {
+ writeRanges(out, purity.mutatedRefs)
+ writeNullable(out, purity.singleCall) { writeRange(out, it) }
+ }
+ private fun readPurity(input: DataInput) = PurityInferenceResult(readRanges(input), readNullable(input) { readRange(input) })
+
+ private fun writeContract(out: DataOutput, contract: PreContract): Unit = when (contract) {
+ is DelegationContract -> { out.writeByte(0); writeRange(out, contract.expression); out.writeBoolean(contract.negated) }
+ is KnownContract -> { out.writeByte(1);
+ writeContractArguments(out, contract.contract.arguments.toList())
+ out.writeByte(contract.contract.returnValue.ordinal)
+ }
+ is MethodCallContract -> { out.writeByte(2);
+ writeRange(out, contract.call);
+ writeList(out, contract.states) { writeContractArguments(out, it) }
+ }
+ is NegatingContract -> { out.writeByte(3); writeContract(out, contract.negated) }
+ is SideEffectFilter -> { out.writeByte(4);
+ writeRanges(out, contract.expressionsToCheck)
+ writeList(out, contract.contracts) { writeContract(out, it) }
+ }
+ else -> throw IllegalArgumentException(contract.toString())
+ }
+ private fun readContract(input: DataInput): PreContract = when (input.readByte().toInt()) {
+ 0 -> DelegationContract(readRange(input), input.readBoolean())
+ 1 -> KnownContract(MethodContract(readContractArguments(input).toTypedArray(), readValueConstraint(input)))
+ 2 -> MethodCallContract(readRange(input), readList(input) { readContractArguments(input) })
+ 3 -> NegatingContract(readContract(input))
+ else -> SideEffectFilter(readRanges(input), readList(input) { readContract(input) })
+ }
+
+ private fun writeContractArguments(out: DataOutput, arguments: List<MethodContract.ValueConstraint>) =
+ writeList(out, arguments) { out.writeByte(it.ordinal) }
+ private fun readContractArguments(input: DataInput) = readList(input, { readValueConstraint(input) })
+
+ private fun readValueConstraint(input: DataInput) = MethodContract.ValueConstraint.values()[input.readByte().toInt()]
+
+}
+
+// utils
+
+private fun <T> writeNullable(out: DataOutput, value: T?, writeItem: (T) -> Unit) = when (value) {
+ null -> out.writeBoolean(false)
+ else -> { out.writeBoolean(true); writeItem(value) }
+}
+private fun <T> readNullable(input: DataInput, readEach: () -> T): T? = if (input.readBoolean()) readEach() else null
+
+private fun <T> writeList(out: DataOutput, list: List<T>, writeEach: (T) -> Unit) {
+ DataInputOutputUtil.writeINT(out, list.size)
+ list.forEach(writeEach)
+}
+private fun <T> readList(input: DataInput, readEach: () -> T) = (0 until DataInputOutputUtil.readINT(input)).map { readEach() }
*/
package com.intellij.codeInspection.dataFlow;
-import com.intellij.lang.ASTNode;
import com.intellij.lang.LighterAST;
import com.intellij.lang.LighterASTNode;
-import com.intellij.lang.TreeBackedLighterAST;
import com.intellij.openapi.util.RecursionManager;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.JavaLightTreeUtil;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiModificationTracker;
-import com.intellij.util.ObjectUtils;
+import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
}
return CachedValuesManager.getCachedValue(method, () -> {
- TreeBackedLighterAST tree = new TreeBackedLighterAST(method.getContainingFile().getNode());
- PsiCodeBlock body = ObjectUtils.assertNotNull(method.getBody());
- ASTNode node = body.getNode();
- NullityInferenceResult result = node == null ? null : doInferNullity(tree, TreeBackedLighterAST.wrap(node));
- Nullness nullness = result == null ? null : RecursionManager.doPreventingRecursion(method, true, () -> result.getNullness(method, body));
+ MethodData data = ContractInferenceIndexKt.getIndexedData(method);
+ NullityInferenceResult result = data == null ? null : data.getNullity();
+ Nullness nullness = result == null ? null : RecursionManager.doPreventingRecursion(method, true, () -> result.getNullness(method, data.methodBody(method)));
if (nullness == null) nullness = Nullness.UNKNOWN;
return CachedValueProvider.Result.create(nullness, method, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT);
});
}
@Nullable
- private static NullityInferenceResult doInferNullity(LighterAST tree, LighterASTNode body) {
+ static NullityInferenceResult doInferNullity(LighterAST tree, LighterASTNode body) {
AtomicBoolean hasErrors = new AtomicBoolean();
AtomicBoolean hasNotNulls = new AtomicBoolean();
AtomicBoolean hasNulls = new AtomicBoolean();
return null;
}
if (delegates.size() == 1) {
- return new NullityInferenceResult.FromDelegate(delegates.get(delegates.keySet().iterator().next()));
+ return new NullityInferenceResult.FromDelegate(ContainerUtil.newArrayList(delegates.get(delegates.keySet().iterator().next())));
}
if (hasNotNulls.get()) {
*/
package com.intellij.codeInspection.dataFlow;
-import com.intellij.lang.ASTNode;
import com.intellij.lang.LighterAST;
import com.intellij.lang.LighterASTNode;
-import com.intellij.lang.TreeBackedLighterAST;
import com.intellij.openapi.util.RecursionManager;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.JavaTokenType;
-import com.intellij.psi.PsiCodeBlock;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiType;
import com.intellij.psi.impl.source.JavaLightTreeUtil;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
-import com.intellij.psi.util.PropertyUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public static boolean inferPurity(@NotNull final PsiMethod method) {
if (!InferenceFromSourceUtil.shouldInferFromSource(method) ||
PsiType.VOID.equals(method.getReturnType()) ||
- method.getBody() == null ||
- method.isConstructor() ||
- PropertyUtil.isSimpleGetter(method)) {
+ method.isConstructor()) {
return false;
}
return CachedValuesManager.getCachedValue(method, () -> {
- TreeBackedLighterAST tree = new TreeBackedLighterAST(method.getContainingFile().getNode());
- PsiCodeBlock body = method.getBody();
- ASTNode node = body.getNode();
- PurityInferenceResult result = node == null ? null : doInferPurity(TreeBackedLighterAST.wrap(node), tree);
- Boolean pure = RecursionManager.doPreventingRecursion(method, true, () -> result != null && result.isPure(method, body));
+ MethodData data = ContractInferenceIndexKt.getIndexedData(method);
+ PurityInferenceResult result = data == null ? null : data.getPurity();
+ Boolean pure = RecursionManager.doPreventingRecursion(method, true, () -> result != null && result.isPure(method, data.methodBody(method)));
return CachedValueProvider.Result.create(pure == Boolean.TRUE, method);
});
}
@Nullable
- private static PurityInferenceResult doInferPurity(LighterASTNode body, LighterAST tree) {
+ static PurityInferenceResult doInferPurity(LighterASTNode body, LighterAST tree) {
List<LighterASTNode> mutatedRefs = new ArrayList<>();
Ref<Boolean> hasReturns = Ref.create(false);
List<LighterASTNode> calls = new ArrayList<>();
@Override
public void visitNode(@NotNull LighterASTNode element) {
IElementType type = element.getTokenType();
+ if (type == CLASS || type == ANONYMOUS_CLASS || type == LAMBDA_EXPRESSION) return;
+
if (type == ASSIGNMENT_EXPRESSION) {
mutatedRefs.add(tree.getChildren(element).get(0));
}
import com.intellij.psi.*
import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.searches.ReferencesSearch
+import com.intellij.psi.util.CachedValueProvider
+import com.intellij.psi.util.CachedValuesManager
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.PsiUtil
/**
* @author peter
*/
-data class ExpressionRange private constructor (private val startOffset: Int, private val endOffset: Int) {
+data class ExpressionRange internal constructor (internal val startOffset: Int, internal val endOffset: Int) {
companion object {
@JvmStatic
}
-data class PurityInferenceResult(private val mutatedRefs: List<ExpressionRange>, private val singleCall: ExpressionRange?) {
+data class PurityInferenceResult(internal val mutatedRefs: List<ExpressionRange>, internal val singleCall: ExpressionRange?) {
- fun isPure(method: PsiMethod, body: PsiCodeBlock) = !mutatesNonLocals(method, body) && callsOnlyPureMethods(body)
+ fun isPure(method: PsiMethod, body: () -> PsiCodeBlock) = !mutatesNonLocals(method, body) && callsOnlyPureMethods(body)
- private fun mutatesNonLocals(method: PsiMethod, body: PsiCodeBlock): Boolean {
- return mutatedRefs.any { range -> !isLocalVarReference(range.restoreExpression(body), method) }
+ private fun mutatesNonLocals(method: PsiMethod, body: () -> PsiCodeBlock): Boolean {
+ return mutatedRefs.any { range -> !isLocalVarReference(range.restoreExpression(body()), method) }
}
- private fun callsOnlyPureMethods(body: PsiCodeBlock): Boolean {
+ private fun callsOnlyPureMethods(body: () -> PsiCodeBlock): Boolean {
if (singleCall == null) return true
- val called = (singleCall.restoreExpression(body) as PsiCall).resolveMethod()
+ val called = (singleCall.restoreExpression(body()) as PsiCall).resolveMethod()
return called != null && ControlFlowAnalyzer.isPure(called)
}
interface NullityInferenceResult {
- fun getNullness(method: PsiMethod, body: PsiCodeBlock): Nullness
+ fun getNullness(method: PsiMethod, body: () -> PsiCodeBlock): Nullness
- data class Predefined(private val value: Nullness) : NullityInferenceResult {
-
- override fun getNullness(method: PsiMethod, body: PsiCodeBlock) = when {
+ @Suppress("EqualsOrHashCode")
+ data class Predefined(internal val value: Nullness) : NullityInferenceResult {
+ override fun hashCode() = value.ordinal
+ override fun getNullness(method: PsiMethod, body: () -> PsiCodeBlock) = when {
value == Nullness.NULLABLE && InferenceFromSourceUtil.suppressNullable(method) -> Nullness.UNKNOWN
else -> value
}
}
- data class FromDelegate (private val delegateCalls: Collection<ExpressionRange>) : NullityInferenceResult {
-
- override fun getNullness(method: PsiMethod, body: PsiCodeBlock) = when {
- delegateCalls.all { range -> isNotNullCall(range, body) } -> Nullness.NOT_NULL
+ data class FromDelegate(internal val delegateCalls: List<ExpressionRange>) : NullityInferenceResult {
+ override fun getNullness(method: PsiMethod, body: () -> PsiCodeBlock) = when {
+ delegateCalls.all { range -> isNotNullCall(range, body()) } -> Nullness.NOT_NULL
else -> Nullness.UNKNOWN
}
return target != null && NullableNotNullManager.isNotNull(target)
}
}
+}
+
+data class MethodData(
+ val nullity: NullityInferenceResult?,
+ val purity: PurityInferenceResult?,
+ val contracts: List<PreContract>,
+ internal val bodyStart: Int,
+ internal val bodyEnd: Int
+) {
+ fun methodBody(method: PsiMethod): () -> PsiCodeBlock = {
+ if ((method as StubBasedPsiElement<*>?)?.stub != null)
+ CachedValuesManager.getCachedValue(method) { CachedValueProvider.Result(getDetachedBody(method), method) }
+ else
+ method.body!!
+ }
+
+ private fun getDetachedBody(method: PsiMethod): PsiCodeBlock {
+ val document = method.containingFile.viewProvider.document ?: return method.body!!
+ val bodyText = PsiDocumentManager.getInstance(method.project).getLastCommittedText(document).substring(bodyStart, bodyEnd)
+ return JavaPsiFacade.getElementFactory(method.project).createCodeBlockFromText(bodyText, method)
+ }
}
\ No newline at end of file
* @author peter
*/
interface PreContract {
- fun toContracts(method: PsiMethod, body: PsiCodeBlock): List<MethodContract>
+ fun toContracts(method: PsiMethod, body: () -> PsiCodeBlock): List<MethodContract>
fun negate(): PreContract? = NegatingContract(this)
}
internal data class KnownContract(val contract: MethodContract) : PreContract {
- override fun toContracts(method: PsiMethod, body: PsiCodeBlock) = listOf(contract)
+ override fun toContracts(method: PsiMethod, body: () -> PsiCodeBlock) = listOf(contract)
override fun negate() = negateContract(contract)?.let(::KnownContract)
}
-internal data class DelegationContract(private val expression: ExpressionRange, private val negated: Boolean) : PreContract {
+internal data class DelegationContract(internal val expression: ExpressionRange, internal val negated: Boolean) : PreContract {
- override fun toContracts(method: PsiMethod, body: PsiCodeBlock): List<MethodContract> {
- val call = expression.restoreExpression(body) as PsiMethodCallExpression? ?: return emptyList()
+ override fun toContracts(method: PsiMethod, body: () -> PsiCodeBlock): List<MethodContract> {
+ val call = expression.restoreExpression(body()) as PsiMethodCallExpression? ?: return emptyList()
val result = call.resolveMethodGenerics()
val targetMethod = result.element as PsiMethod? ?: return emptyList()
}
}
-internal data class SideEffectFilter(private val expressionsToCheck: List<ExpressionRange>, private val contracts: List<PreContract>) : PreContract {
+internal data class SideEffectFilter(internal val expressionsToCheck: List<ExpressionRange>, internal val contracts: List<PreContract>) : PreContract {
- override fun toContracts(method: PsiMethod, body: PsiCodeBlock): List<MethodContract> {
- if (expressionsToCheck.any { d -> mayHaveSideEffects(body, d) }) {
+ override fun toContracts(method: PsiMethod, body: () -> PsiCodeBlock): List<MethodContract> {
+ if (expressionsToCheck.any { d -> mayHaveSideEffects(body(), d) }) {
return emptyList()
}
return contracts.flatMap { c -> c.toContracts(method, body) }
range.restoreExpression(body)?.let { SideEffectChecker.mayHaveSideEffects(it) } ?: false
}
-internal data class NegatingContract(private val negated: PreContract) : PreContract {
- override fun toContracts(method: PsiMethod, body: PsiCodeBlock) = negated.toContracts(method, body).mapNotNull(::negateContract)
+internal data class NegatingContract(internal val negated: PreContract) : PreContract {
+ override fun toContracts(method: PsiMethod, body: () -> PsiCodeBlock) = negated.toContracts(method, body).mapNotNull(::negateContract)
}
private fun negateContract(c: MethodContract): MethodContract? {
return if (ret == TRUE_VALUE || ret == FALSE_VALUE) MethodContract(c.arguments, negateConstraint(ret)) else null
}
-internal data class MethodCallContract(private val call: ExpressionRange, private val states: List<Array<MethodContract.ValueConstraint>>) : PreContract {
+@Suppress("EqualsOrHashCode")
+internal data class MethodCallContract(internal val call: ExpressionRange, internal val states: List<List<MethodContract.ValueConstraint>>) : PreContract {
+ override fun hashCode() = call.hashCode() * 31 + states.flatten().map { it.ordinal }.hashCode()
- override fun toContracts(method: PsiMethod, body: PsiCodeBlock): List<MethodContract> {
- val target = (call.restoreExpression(body) as PsiMethodCallExpression?)?.resolveMethod()
+ override fun toContracts(method: PsiMethod, body: () -> PsiCodeBlock): List<MethodContract> {
+ val target = (call.restoreExpression(body()) as PsiMethodCallExpression?)?.resolveMethod()
if (target != null && NullableNotNullManager.isNotNull(target)) {
- return ContractInferenceInterpreter.toContracts(states, NOT_NULL_VALUE)
+ return ContractInferenceInterpreter.toContracts(states.map { it.toTypedArray() }, NOT_NULL_VALUE)
}
return emptyList()
}
@Override
protected boolean skipChildProcessingWhenBuildingStubs(@NotNull LighterAST tree, @NotNull LighterASTNode parent, @NotNull LighterASTNode node) {
- IElementType parentType = parent.getTokenType();
- IElementType nodeType = node.getTokenType();
-
- if (checkByTypes(parentType, nodeType)) return true;
+ return checkByTypes(parent.getTokenType(), node.getTokenType()) || isCodeBlockWithoutStubs(node);
+ }
- if (nodeType == JavaElementType.CODE_BLOCK) {
+ public static boolean isCodeBlockWithoutStubs(@NotNull LighterASTNode node) {
+ if (node.getTokenType() == JavaElementType.CODE_BLOCK && node instanceof LighterLazyParseableNode) {
CodeBlockVisitor visitor = new CodeBlockVisitor();
((LighterLazyParseableNode)node).accept(visitor);
return visitor.result;
import com.intellij.codeInspection.dataFlow.ContractInference
import com.intellij.psi.PsiAnonymousClass
+import com.intellij.psi.impl.source.PsiFileImpl
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
-
/**
* @author peter
*/
private List<String> inferContracts(String method) {
def clazz = myFixture.addClass("final class Foo { $method }")
- return ContractInference.inferContracts(clazz.methods[0]).collect { it as String }
+ assert !((PsiFileImpl) clazz.containingFile).contentsLoaded
+ def contracts = ContractInference.inferContracts(clazz.methods[0])
+ assert !((PsiFileImpl) clazz.containingFile).contentsLoaded
+ return contracts.collect { it as String }
}
}
import com.intellij.codeInspection.dataFlow.DfaUtil
import com.intellij.codeInspection.dataFlow.Nullness
import com.intellij.psi.PsiMethod
+import com.intellij.psi.impl.source.PsiFileImpl
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
import org.jetbrains.annotations.Contract
static class LightInferenceTest extends NullityInferenceFromSourceTestCase {
Nullness inferNullity(PsiMethod method) {
- return NullableNotNullManager.isNotNull(method) ? NOT_NULL : NullableNotNullManager.isNullable(method) ? NULLABLE : UNKNOWN
+ assert !((PsiFileImpl) method.containingFile).contentsLoaded
+ def result = NullableNotNullManager.isNotNull(method) ? NOT_NULL : NullableNotNullManager.isNullable(method) ? NULLABLE : UNKNOWN
+ assert !((PsiFileImpl) method.containingFile).contentsLoaded
+ return result
}
void "test skip when errors"() {
package com.intellij.codeInspection
import com.intellij.codeInspection.dataFlow.PurityInference
+import com.intellij.psi.impl.source.PsiFileImpl
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
/**
* @author peter
private void assertPure(boolean expected, String classBody) {
def clazz = myFixture.addClass("final class Foo { $classBody }")
- assert expected == PurityInference.inferPurity(clazz.methods[0])
+ assert !((PsiFileImpl) clazz.containingFile).contentsLoaded
+ def purity = PurityInference.inferPurity(clazz.methods[0])
+ assert !((PsiFileImpl) clazz.containingFile).contentsLoaded
+ assert expected == purity
}
}
<projectService serviceInterface="com.intellij.codeInsight.InferredAnnotationsManager"
serviceImplementation="com.intellij.codeInsight.InferredAnnotationsManagerImpl"/>
+ <fileBasedIndex implementation="com.intellij.codeInspection.dataFlow.ContractInferenceIndex"/>
+
<projectService serviceInterface="com.intellij.task.ProjectTaskManager"
serviceImplementation="com.intellij.task.impl.ProjectTaskManagerImpl"/>