Debugger: skip visibility check

This commit is contained in:
Natalia Ukhorskaya
2016-01-28 11:25:17 +03:00
parent bad8320038
commit d60a5e65c1
12 changed files with 72 additions and 31 deletions
@@ -18,12 +18,23 @@ package org.jetbrains.kotlin.psi.codeFragmentUtil
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import org.jetbrains.kotlin.psi.KtCodeFragment import org.jetbrains.kotlin.psi.KtCodeFragment
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtTypeReference import org.jetbrains.kotlin.psi.KtTypeReference
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
val SUPPRESS_DIAGNOSTICS_IN_DEBUG_MODE: Key<Boolean> = Key.create<Boolean>("SUPPRESS_DIAGNOSTICS_IN_DEBUG_MODE") val SUPPRESS_DIAGNOSTICS_IN_DEBUG_MODE: Key<Boolean> = Key.create<Boolean>("SUPPRESS_DIAGNOSTICS_IN_DEBUG_MODE")
fun KtElement.suppressDiagnosticsInDebugMode(): Boolean {
return if (this is KtFile) {
this.suppressDiagnosticsInDebugMode
}
else {
val file = this.containingFile
file is KtFile && file.suppressDiagnosticsInDebugMode
}
}
var KtFile.suppressDiagnosticsInDebugMode: Boolean var KtFile.suppressDiagnosticsInDebugMode: Boolean
get() = when (this) { get() = when (this) {
is KtCodeFragment -> true is KtCodeFragment -> true
@@ -78,7 +78,9 @@ class CandidateResolver(
return return
} }
checkVisibility() if (!context.isDebuggerContext) {
checkVisibility()
}
when (checkArguments) { when (checkArguments) {
CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS -> CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS ->
@@ -42,11 +42,12 @@ public class BasicCallResolutionContext extends CallResolutionContext<BasicCallR
@NotNull CallChecker callChecker, @NotNull CallChecker callChecker,
@NotNull StatementFilter statementFilter, @NotNull StatementFilter statementFilter,
boolean isAnnotationContext, boolean isAnnotationContext,
boolean isDebuggerContext,
boolean collectAllCandidates, boolean collectAllCandidates,
@NotNull CallPosition callPosition @NotNull CallPosition callPosition
) { ) {
super(trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments, resolutionResultsCache, super(trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments, resolutionResultsCache,
dataFlowInfoForArguments, callChecker, statementFilter, isAnnotationContext, collectAllCandidates, callPosition); dataFlowInfoForArguments, callChecker, statementFilter, isAnnotationContext, isDebuggerContext, collectAllCandidates, callPosition);
} }
@NotNull @NotNull
@@ -63,7 +64,7 @@ public class BasicCallResolutionContext extends CallResolutionContext<BasicCallR
) { ) {
return new BasicCallResolutionContext(trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments, return new BasicCallResolutionContext(trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments,
new ResolutionResultsCacheImpl(), null, new ResolutionResultsCacheImpl(), null,
callChecker, StatementFilter.NONE, isAnnotationContext, false, callChecker, StatementFilter.NONE, isAnnotationContext, false, false,
CallPosition.Unknown.INSTANCE); CallPosition.Unknown.INSTANCE);
} }
@@ -76,7 +77,7 @@ public class BasicCallResolutionContext extends CallResolutionContext<BasicCallR
context.trace, context.scope, call, context.expectedType, context.dataFlowInfo, context.contextDependency, checkArguments, context.trace, context.scope, call, context.expectedType, context.dataFlowInfo, context.contextDependency, checkArguments,
context.resolutionResultsCache, dataFlowInfoForArguments, context.resolutionResultsCache, dataFlowInfoForArguments,
context.callChecker, context.callChecker,
context.statementFilter, context.isAnnotationContext, context.collectAllCandidates, context.callPosition); context.statementFilter, context.isAnnotationContext, context.isDebuggerContext, context.collectAllCandidates, context.callPosition);
} }
@NotNull @NotNull
@@ -100,14 +101,14 @@ public class BasicCallResolutionContext extends CallResolutionContext<BasicCallR
) { ) {
return new BasicCallResolutionContext( return new BasicCallResolutionContext(
trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments, resolutionResultsCache, trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments, resolutionResultsCache,
dataFlowInfoForArguments, callChecker, statementFilter, isAnnotationContext, collectAllCandidates, callPosition); dataFlowInfoForArguments, callChecker, statementFilter, isAnnotationContext, isDebuggerContext, collectAllCandidates, callPosition);
} }
@NotNull @NotNull
public BasicCallResolutionContext replaceCall(@NotNull Call newCall) { public BasicCallResolutionContext replaceCall(@NotNull Call newCall) {
return new BasicCallResolutionContext( return new BasicCallResolutionContext(
trace, scope, newCall, expectedType, dataFlowInfo, contextDependency, checkArguments, resolutionResultsCache, trace, scope, newCall, expectedType, dataFlowInfo, contextDependency, checkArguments, resolutionResultsCache,
dataFlowInfoForArguments, callChecker, statementFilter, isAnnotationContext, collectAllCandidates, callPosition); dataFlowInfoForArguments, callChecker, statementFilter, isAnnotationContext, isDebuggerContext, collectAllCandidates, callPosition);
} }
public void performContextDependentCallChecks(@NotNull ResolvedCall<?> resolvedCall) { public void performContextDependentCallChecks(@NotNull ResolvedCall<?> resolvedCall) {
@@ -58,11 +58,12 @@ public final class CallCandidateResolutionContext<D extends CallableDescriptor>
@Nullable Receiver explicitExtensionReceiverForInvoke, @Nullable Receiver explicitExtensionReceiverForInvoke,
@NotNull CandidateResolveMode candidateResolveMode, @NotNull CandidateResolveMode candidateResolveMode,
boolean isAnnotationContext, boolean isAnnotationContext,
boolean isDebuggerContext,
boolean collectAllCandidates, boolean collectAllCandidates,
@NotNull CallPosition callPosition @NotNull CallPosition callPosition
) { ) {
super(trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments, resolutionResultsCache, super(trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments, resolutionResultsCache,
dataFlowInfoForArguments, callChecker, statementFilter, isAnnotationContext, dataFlowInfoForArguments, callChecker, statementFilter, isAnnotationContext, isDebuggerContext,
collectAllCandidates, callPosition); collectAllCandidates, callPosition);
this.candidateCall = candidateCall; this.candidateCall = candidateCall;
this.tracing = tracing; this.tracing = tracing;
@@ -80,7 +81,7 @@ public final class CallCandidateResolutionContext<D extends CallableDescriptor>
context.dataFlowInfo, context.contextDependency, context.checkArguments, context.dataFlowInfo, context.contextDependency, context.checkArguments,
context.resolutionResultsCache, context.dataFlowInfoForArguments, context.resolutionResultsCache, context.dataFlowInfoForArguments,
context.callChecker, context.statementFilter, explicitExtensionReceiverForInvoke, context.callChecker, context.statementFilter, explicitExtensionReceiverForInvoke,
candidateResolveMode, context.isAnnotationContext, context.collectAllCandidates, context.callPosition); candidateResolveMode, context.isAnnotationContext, context.isDebuggerContext, context.collectAllCandidates, context.callPosition);
} }
@NotNull @NotNull
@@ -91,7 +92,7 @@ public final class CallCandidateResolutionContext<D extends CallableDescriptor>
candidateCall, tracing, context.trace, context.scope, context.call, context.expectedType, candidateCall, tracing, context.trace, context.scope, context.call, context.expectedType,
context.dataFlowInfo, context.contextDependency, context.checkArguments, context.resolutionResultsCache, context.dataFlowInfo, context.contextDependency, context.checkArguments, context.resolutionResultsCache,
context.dataFlowInfoForArguments, context.callChecker, context.statementFilter, context.dataFlowInfoForArguments, context.callChecker, context.statementFilter,
null, CandidateResolveMode.FULLY, context.isAnnotationContext, context.collectAllCandidates, null, CandidateResolveMode.FULLY, context.isAnnotationContext, context.isDebuggerContext, context.collectAllCandidates,
context.callPosition); context.callPosition);
} }
@@ -110,6 +111,6 @@ public final class CallCandidateResolutionContext<D extends CallableDescriptor>
return new CallCandidateResolutionContext<D>( return new CallCandidateResolutionContext<D>(
candidateCall, tracing, trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments, candidateCall, tracing, trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments,
resolutionResultsCache, dataFlowInfoForArguments, callChecker, statementFilter, resolutionResultsCache, dataFlowInfoForArguments, callChecker, statementFilter,
explicitExtensionReceiverForInvoke, candidateResolveMode, isAnnotationContext, collectAllCandidates, callPosition); explicitExtensionReceiverForInvoke, candidateResolveMode, isAnnotationContext, isDebuggerContext, collectAllCandidates, callPosition);
} }
} }
@@ -50,11 +50,12 @@ public abstract class CallResolutionContext<Context extends CallResolutionContex
@NotNull CallChecker callChecker, @NotNull CallChecker callChecker,
@NotNull StatementFilter statementFilter, @NotNull StatementFilter statementFilter,
boolean isAnnotationContext, boolean isAnnotationContext,
boolean isDebuggerContext,
boolean collectAllCandidates, boolean collectAllCandidates,
@NotNull CallPosition callPosition @NotNull CallPosition callPosition
) { ) {
super(trace, scope, expectedType, dataFlowInfo, contextDependency, resolutionResultsCache, callChecker, super(trace, scope, expectedType, dataFlowInfo, contextDependency, resolutionResultsCache, callChecker,
statementFilter, isAnnotationContext, collectAllCandidates, callPosition); statementFilter, isAnnotationContext, isDebuggerContext, collectAllCandidates, callPosition);
this.call = call; this.call = call;
this.checkArguments = checkArguments; this.checkArguments = checkArguments;
if (dataFlowInfoForArguments != null) { if (dataFlowInfoForArguments != null) {
@@ -52,6 +52,8 @@ public abstract class ResolutionContext<Context extends ResolutionContext<Contex
public final boolean isAnnotationContext; public final boolean isAnnotationContext;
public final boolean isDebuggerContext;
public final boolean collectAllCandidates; public final boolean collectAllCandidates;
@NotNull @NotNull
@@ -67,6 +69,7 @@ public abstract class ResolutionContext<Context extends ResolutionContext<Contex
@NotNull CallChecker callChecker, @NotNull CallChecker callChecker,
@NotNull StatementFilter statementFilter, @NotNull StatementFilter statementFilter,
boolean isAnnotationContext, boolean isAnnotationContext,
boolean isDebuggerContext,
boolean collectAllCandidates, boolean collectAllCandidates,
@NotNull CallPosition callPosition @NotNull CallPosition callPosition
) { ) {
@@ -79,6 +82,7 @@ public abstract class ResolutionContext<Context extends ResolutionContext<Contex
this.callChecker = callChecker; this.callChecker = callChecker;
this.statementFilter = statementFilter; this.statementFilter = statementFilter;
this.isAnnotationContext = isAnnotationContext; this.isAnnotationContext = isAnnotationContext;
this.isDebuggerContext = isDebuggerContext;
this.collectAllCandidates = collectAllCandidates; this.collectAllCandidates = collectAllCandidates;
this.callPosition = callPosition; this.callPosition = callPosition;
} }
@@ -58,11 +58,12 @@ public class ResolutionTask<D extends CallableDescriptor, F extends D> extends C
@NotNull StatementFilter statementFilter, @NotNull StatementFilter statementFilter,
@NotNull Collection<MutableResolvedCall<F>> resolvedCalls, @NotNull Collection<MutableResolvedCall<F>> resolvedCalls,
boolean isAnnotationContext, boolean isAnnotationContext,
boolean isDebuggerContext,
boolean collectAllCandidates, boolean collectAllCandidates,
@NotNull CallPosition callPosition @NotNull CallPosition callPosition
) { ) {
super(trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments, resolutionResultsCache, super(trace, scope, call, expectedType, dataFlowInfo, contextDependency, checkArguments, resolutionResultsCache,
dataFlowInfoForArguments, callChecker, statementFilter, isAnnotationContext, collectAllCandidates, callPosition); dataFlowInfoForArguments, callChecker, statementFilter, isAnnotationContext, isDebuggerContext, collectAllCandidates, callPosition);
this.lazyCandidates = lazyCandidates; this.lazyCandidates = lazyCandidates;
this.resolvedCalls = resolvedCalls; this.resolvedCalls = resolvedCalls;
this.tracing = tracing; this.tracing = tracing;
@@ -79,7 +80,7 @@ public class ResolutionTask<D extends CallableDescriptor, F extends D> extends C
context.resolutionResultsCache, context.dataFlowInfoForArguments, context.resolutionResultsCache, context.dataFlowInfoForArguments,
context.callChecker, context.callChecker,
context.statementFilter, new SmartList<MutableResolvedCall<F>>(), context.statementFilter, new SmartList<MutableResolvedCall<F>>(),
context.isAnnotationContext, context.collectAllCandidates, context.callPosition); context.isAnnotationContext, context.isDebuggerContext, context.collectAllCandidates, context.callPosition);
} }
@NotNull @NotNull
@@ -113,7 +114,7 @@ public class ResolutionTask<D extends CallableDescriptor, F extends D> extends C
resolutionResultsCache, dataFlowInfoForArguments, resolutionResultsCache, dataFlowInfoForArguments,
callChecker, callChecker,
statementFilter, resolvedCalls, statementFilter, resolvedCalls,
isAnnotationContext, collectAllCandidates, callPosition); isAnnotationContext, isDebuggerContext, collectAllCandidates, callPosition);
} }
@Override @Override
@@ -21,13 +21,11 @@ import org.jetbrains.kotlin.incremental.components.LookupLocation
import org.jetbrains.kotlin.resolve.calls.context.ResolutionContext import org.jetbrains.kotlin.resolve.calls.context.ResolutionContext
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory
import org.jetbrains.kotlin.resolve.scopes.ImportingScope
import org.jetbrains.kotlin.resolve.scopes.LexicalScope import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.resolve.scopes.MemberScope import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.resolve.scopes.SyntheticScopes import org.jetbrains.kotlin.resolve.scopes.SyntheticScopes
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.kotlin.resolve.scopes.utils.getImplicitReceiversHierarchy import org.jetbrains.kotlin.resolve.scopes.utils.getImplicitReceiversHierarchy
import org.jetbrains.kotlin.resolve.scopes.utils.parentsWithSelf
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.containsError import org.jetbrains.kotlin.types.typeUtil.containsError
import org.jetbrains.kotlin.utils.addToStdlib.check import org.jetbrains.kotlin.utils.addToStdlib.check
@@ -51,6 +49,8 @@ internal class ScopeTowerImpl(
override val implicitReceivers = resolutionContext.scope.getImplicitReceiversHierarchy(). override val implicitReceivers = resolutionContext.scope.getImplicitReceiversHierarchy().
mapNotNull { it.value.check { !it.type.containsError() } } mapNotNull { it.value.check { !it.type.containsError() } }
val isDebuggerContext = resolutionContext.isDebuggerContext
} }
private class DataFlowDecoratorImpl(private val resolutionContext: ResolutionContext<*>): DataFlowDecorator { private class DataFlowDecoratorImpl(private val resolutionContext: ResolutionContext<*>): DataFlowDecorator {
@@ -62,10 +62,13 @@ internal abstract class AbstractScopeTowerLevel(
if (descriptor.isSynthesized) diagnostics.add(SynthesizedDescriptorDiagnostic) if (descriptor.isSynthesized) diagnostics.add(SynthesizedDescriptorDiagnostic)
if (dispatchReceiverSmartCastType != null) diagnostics.add(UsedSmartCastForDispatchReceiver(dispatchReceiverSmartCastType)) if (dispatchReceiverSmartCastType != null) diagnostics.add(UsedSmartCastForDispatchReceiver(dispatchReceiverSmartCastType))
Visibilities.findInvisibleMember( val shouldSkipVisibilityCheck = scopeTower is ScopeTowerImpl && scopeTower.isDebuggerContext
dispatchReceiver, descriptor, if (!shouldSkipVisibilityCheck) {
scopeTower.lexicalScope.ownerDescriptor Visibilities.findInvisibleMember(
)?.let { diagnostics.add(VisibilityError(it)) } dispatchReceiver, descriptor,
scopeTower.lexicalScope.ownerDescriptor
)?.let { diagnostics.add(VisibilityError(it)) }
}
} }
return CandidateWithBoundDispatchReceiverImpl(dispatchReceiver, descriptor, diagnostics) return CandidateWithBoundDispatchReceiverImpl(dispatchReceiver, descriptor, diagnostics)
} }
@@ -58,7 +58,18 @@ public class ExpressionTypingContext extends ResolutionContext<ExpressionTypingC
context.contextDependency, context.resolutionResultsCache, context.contextDependency, context.resolutionResultsCache,
context.callChecker, context.callChecker,
context.statementFilter, context.statementFilter,
context.isAnnotationContext, context.collectAllCandidates, context.isAnnotationContext, context.isDebuggerContext, context.collectAllCandidates,
context.callPosition);
}
@NotNull
public static ExpressionTypingContext newContext(@NotNull ResolutionContext context, boolean isDebuggerContext) {
return new ExpressionTypingContext(
context.trace, context.scope, context.dataFlowInfo, context.expectedType,
context.contextDependency, context.resolutionResultsCache,
context.callChecker,
context.statementFilter,
context.isAnnotationContext, isDebuggerContext, context.collectAllCandidates,
context.callPosition); context.callPosition);
} }
@@ -76,7 +87,7 @@ public class ExpressionTypingContext extends ResolutionContext<ExpressionTypingC
) { ) {
return new ExpressionTypingContext( return new ExpressionTypingContext(
trace, scope, dataFlowInfo, expectedType, contextDependency, resolutionResultsCache, callChecker, trace, scope, dataFlowInfo, expectedType, contextDependency, resolutionResultsCache, callChecker,
statementFilter, isAnnotationContext, false, CallPosition.Unknown.INSTANCE); statementFilter, isAnnotationContext, false, false, CallPosition.Unknown.INSTANCE);
} }
private ExpressionTypingContext( private ExpressionTypingContext(
@@ -89,12 +100,13 @@ public class ExpressionTypingContext extends ResolutionContext<ExpressionTypingC
@NotNull CallChecker callChecker, @NotNull CallChecker callChecker,
@NotNull StatementFilter statementFilter, @NotNull StatementFilter statementFilter,
boolean isAnnotationContext, boolean isAnnotationContext,
boolean isDebuggerContext,
boolean collectAllCandidates, boolean collectAllCandidates,
@NotNull CallPosition callPosition @NotNull CallPosition callPosition
) { ) {
super(trace, scope, expectedType, dataFlowInfo, contextDependency, resolutionResultsCache, super(trace, scope, expectedType, dataFlowInfo, contextDependency, resolutionResultsCache,
callChecker, callChecker,
statementFilter, isAnnotationContext, collectAllCandidates, callPosition); statementFilter, isAnnotationContext, isDebuggerContext, collectAllCandidates, callPosition);
} }
@Override @Override
@@ -112,6 +124,6 @@ public class ExpressionTypingContext extends ResolutionContext<ExpressionTypingC
return new ExpressionTypingContext(trace, scope, dataFlowInfo, return new ExpressionTypingContext(trace, scope, dataFlowInfo,
expectedType, contextDependency, resolutionResultsCache, expectedType, contextDependency, resolutionResultsCache,
callChecker, callChecker,
statementFilter, isAnnotationContext, collectAllCandidates, callPosition); statementFilter, isAnnotationContext, isDebuggerContext, collectAllCandidates, callPosition);
} }
} }
@@ -25,7 +25,11 @@ import org.jetbrains.kotlin.diagnostics.DiagnosticUtils;
import org.jetbrains.kotlin.diagnostics.Errors; import org.jetbrains.kotlin.diagnostics.Errors;
import org.jetbrains.kotlin.incremental.components.LookupTracker; import org.jetbrains.kotlin.incremental.components.LookupTracker;
import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.resolve.*; import org.jetbrains.kotlin.psi.codeFragmentUtil.CodeFragmentUtilKt;
import org.jetbrains.kotlin.resolve.AnnotationChecker;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.BindingContextUtils;
import org.jetbrains.kotlin.resolve.DeclarationsCheckerBuilder;
import org.jetbrains.kotlin.resolve.bindingContextUtil.BindingContextUtilsKt; import org.jetbrains.kotlin.resolve.bindingContextUtil.BindingContextUtilsKt;
import org.jetbrains.kotlin.resolve.calls.context.CallPosition; import org.jetbrains.kotlin.resolve.calls.context.CallPosition;
import org.jetbrains.kotlin.resolve.scopes.LexicalScopeKind; import org.jetbrains.kotlin.resolve.scopes.LexicalScopeKind;
@@ -144,8 +148,12 @@ public abstract class ExpressionTypingVisitorDispatcher extends KtVisitor<Kotlin
@Override @Override
@NotNull @NotNull
public final KotlinTypeInfo getTypeInfo(@NotNull KtExpression expression, ExpressionTypingContext context, boolean isStatement) { public final KotlinTypeInfo getTypeInfo(@NotNull KtExpression expression, ExpressionTypingContext context, boolean isStatement) {
if (!isStatement) return getTypeInfo(expression, context); ExpressionTypingContext newContext = context;
return getTypeInfo(expression, context, getStatementVisitor(context)); if (CodeFragmentUtilKt.suppressDiagnosticsInDebugMode(expression)) {
newContext = ExpressionTypingContext.newContext(context, true);
}
if (!isStatement) return getTypeInfo(expression, newContext);
return getTypeInfo(expression, newContext, getStatementVisitor(newContext));
} }
protected ExpressionTypingVisitorForStatements createStatementVisitor(ExpressionTypingContext context) { protected ExpressionTypingVisitorForStatements createStatementVisitor(ExpressionTypingContext context) {
@@ -29,10 +29,7 @@ class DiagnosticSuppressorForDebugger : DiagnosticSuppressor {
if (containingFile is KtFile && containingFile.suppressDiagnosticsInDebugMode) { if (containingFile is KtFile && containingFile.suppressDiagnosticsInDebugMode) {
val diagnosticFactory = diagnostic.factory val diagnosticFactory = diagnostic.factory
return diagnosticFactory == Errors.INVISIBLE_MEMBER || return diagnosticFactory == Errors.UNSAFE_CALL
diagnosticFactory == Errors.INVISIBLE_REFERENCE ||
diagnosticFactory == Errors.INVISIBLE_SETTER ||
diagnosticFactory == Errors.UNSAFE_CALL
} }
return false return false