K1: report a warning for invisible setter accessed from a derived class

The issue is that during binding fake overrides, the compiler doesn't
 differ setters from its properties, so the compiler uses the same
 visibility for setter and entire property.

 Changing logic at the binding stage can cause some unpredictable consequences so
 the fix is to do this differentiation right at the reporting stage

 ^KT-56662 Fixed

Merge-request: KT-MR-9565
Merged-by: Michail Zarečenskij <Mikhail.Zarechenskiy@jetbrains.com>
This commit is contained in:
Mikhail Zarechenskiy
2023-04-17 11:08:16 +00:00
committed by Space Team
parent d8f253d07b
commit fc37885d6d
14 changed files with 332 additions and 9 deletions
@@ -47,6 +47,7 @@ import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
import org.jetbrains.kotlin.resolve.calls.util.*
import org.jetbrains.kotlin.resolve.checkers.PlatformDiagnosticSuppressor
import org.jetbrains.kotlin.resolve.descriptorUtil.firstOverridden
import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyExternal
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver
@@ -494,15 +495,19 @@ class ControlFlowInformationProviderImpl private constructor(
if (DescriptorVisibilityUtils.isVisible(receiverValue, variableDescriptor, descriptor, languageVersionSettings)
&& setterDescriptor != null
&& !DescriptorVisibilityUtils.isVisible(receiverValue, setterDescriptor, descriptor, languageVersionSettings)
) {
report(
Errors.INVISIBLE_SETTER.on(
expression, variableDescriptor, setterDescriptor.visibility,
setterDescriptor
), ctxt
)
return true
if (!DescriptorVisibilityUtils.isVisible(receiverValue, setterDescriptor, descriptor, languageVersionSettings)) {
report(
INVISIBLE_SETTER.on(
expression, variableDescriptor, setterDescriptor.visibility,
setterDescriptor
), ctxt
)
return true
} else {
// don't return anything as only warning is reported (not error), so further diagnostics are also important
reportVisibilityWarningForInternalFakeSetterOverride(setterDescriptor, expression, variableDescriptor, ctxt)
}
}
}
val isThisOrNoDispatchReceiver = PseudocodeUtil.isThisOrNoDispatchReceiver(writeValueInstruction, trace.bindingContext)
@@ -560,6 +565,35 @@ class ControlFlowInformationProviderImpl private constructor(
return false
}
private fun reportVisibilityWarningForInternalFakeSetterOverride(
setterDescriptor: PropertySetterDescriptor,
expression: KtExpression,
variableDescriptor: PropertyDescriptor,
ctxt: VariableInitContext
) {
if (setterDescriptor.kind.isReal) return
if (setterDescriptor.visibility.isPublicAPI) return
val containingClass = setterDescriptor.containingDeclaration as? ClassDescriptor ?: return
val firstRealOverridden = setterDescriptor.firstOverridden { it.kind.isReal } ?: return
val visibleOverrides = OverridingUtil.filterVisibleFakeOverrides(containingClass, listOf(firstRealOverridden))
if (visibleOverrides.isEmpty()) {
val diagnostic =
when (languageVersionSettings.supportsFeature(LanguageFeature.ProhibitAccessToInvisibleSetterFromDerivedClass)) {
true -> INVISIBLE_SETTER
else -> INVISIBLE_SETTER_FROM_DERIVED
}
report(
diagnostic.on(
expression, variableDescriptor, setterDescriptor.visibility,
setterDescriptor
), ctxt
)
}
}
private fun reportValReassigned(expression: KtExpression, variableDescriptor: VariableDescriptor, ctxt: VariableInitContext) {
report(VAL_REASSIGNMENT_VIA_BACKING_FIELD.on(languageVersionSettings, expression, variableDescriptor), ctxt)
}
@@ -1204,6 +1204,8 @@ public interface Errors {
DiagnosticFactory3<PsiElement, DeclarationDescriptor, DescriptorVisibility, DeclarationDescriptor> INVISIBLE_SETTER =
DiagnosticFactory3.create(ERROR);
DiagnosticFactory3<PsiElement, DeclarationDescriptor, DescriptorVisibility, DeclarationDescriptor> INVISIBLE_SETTER_FROM_DERIVED =
DiagnosticFactory3.create(WARNING);
DiagnosticFactory1<PsiElement, KtKeywordToken> VAL_OR_VAR_ON_LOOP_PARAMETER = DiagnosticFactory1.create(ERROR);
DiagnosticFactory1<PsiElement, KtKeywordToken> VAL_OR_VAR_ON_FUN_PARAMETER = DiagnosticFactory1.create(ERROR);
@@ -414,6 +414,8 @@ public class DefaultErrorMessages {
MAP.put(SETTER_PROJECTED_OUT, "Setter for ''{0}'' is removed by type projection", NAME);
MAP.put(INVISIBLE_SETTER, "Cannot assign to ''{0}'': the setter is {1} in {2}", NAME, VISIBILITY,
NAME_OF_CONTAINING_DECLARATION_OR_FILE);
MAP.put(INVISIBLE_SETTER_FROM_DERIVED, "Cannot assign to ''{0}'': the setter is {1} in {2}. This warning will be an error soon. See https://youtrack.jetbrains.com/issue/KT-56662 for details", NAME, VISIBILITY,
NAME_OF_CONTAINING_DECLARATION_OR_FILE);
MAP.put(INITIALIZATION_BEFORE_DECLARATION, "Variable cannot be initialized before declaration", NAME);
MAP.put(VARIABLE_EXPECTED, "Variable expected");