diff --git a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/PythonSdkConfigurator.kt b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/PythonSdkConfigurator.kt index 696a9145e096..5a49272a5101 100644 --- a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/PythonSdkConfigurator.kt +++ b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/PythonSdkConfigurator.kt @@ -51,6 +51,8 @@ import java.nio.file.Path class PythonSdkConfigurator : DirectoryProjectConfigurator { + private val logger = thisLogger() + init { // new SDK configurator obsoletes this engine if (enableSDKAutoConfigurator) { @@ -241,7 +243,7 @@ class PythonSdkConfigurator : DirectoryProjectConfigurator { if (sdkCreator == null) { return false } - sdkCreator(true).orLogException(thisLogger()) + sdkCreator(true).orLogException(logger) return true } diff --git a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/sdkEnvUtil.kt b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/sdkEnvUtil.kt index e759ae15ce59..9571dbc9fc12 100644 --- a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/sdkEnvUtil.kt +++ b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/sdkEnvUtil.kt @@ -8,7 +8,7 @@ import com.intellij.python.community.execService.python.validatePythonAndGetInfo import com.jetbrains.python.PythonBinary import com.jetbrains.python.PythonInfo import com.jetbrains.python.errorProcessing.PyResult -import com.jetbrains.python.orLogExceptionAsWarn +import com.jetbrains.python.orLogException import com.jetbrains.python.sdk.asBinToExecute import com.jetbrains.python.sdk.configuration.EnvCheckerResult @@ -18,5 +18,7 @@ internal suspend fun PythonBinary.findEnvOrNull(@IntentionName intentionName: St internal suspend fun Sdk.findEnvOrNull(@IntentionName intentionName: String): EnvCheckerResult.EnvFound? = asBinToExecute().validatePythonAndGetInfo().findEnvOrNull(intentionName) -internal fun PyResult.findEnvOrNull(@IntentionName intentionName: String): EnvCheckerResult.EnvFound? = - orLogExceptionAsWarn(fileLogger())?.let { EnvCheckerResult.EnvFound(it, intentionName) } \ No newline at end of file +internal fun PyResult.findEnvOrNull(@IntentionName intentionName: String): EnvCheckerResult.EnvFound? { + return orLogException(logger)?.let { EnvCheckerResult.EnvFound(it, intentionName) } +} +private val logger = fileLogger() diff --git a/python/openapi/src/com/jetbrains/python/Result.kt b/python/openapi/src/com/jetbrains/python/Result.kt index d0b62ad3d865..cb5ec1db5953 100644 --- a/python/openapi/src/com/jetbrains/python/Result.kt +++ b/python/openapi/src/com/jetbrains/python/Result.kt @@ -14,6 +14,11 @@ import org.jetbrains.annotations.Nls * Operation result to be used as `Maybe` instead of checked exceptions. * Unlike Kotlin `Result`, [ERR] could be anything (i.e [String]). * + * Warning: + * This class is for *user* errors (IO errors like: no Internet conn., file provided by user is malformed, external process died). + * Code bugs (NPE, CNFE, AOOBE e.t.c.) are *developer* errors, and must be represented as exceptions. + * Do not wrap every single `Throwable` into this class. + * * Typical usages: * * ```kotlin @@ -91,12 +96,20 @@ sealed class Result { is Failure -> Failure(onErr(error)) } + /** + * Returns `null` if error, effectively *discarding* an error. + * Use with care, only if: + * 1. Error is insignificant (most errors are worth reporting to user!) + * 2. You are sure this error has already been reported by other parts of the code + */ val successOrNull: SUCC? get() = if (this is Success) result else null val errorOrNull: ERR? get() = if (this is Failure) error else null /** - * Like Rust `unwrap`: returns result or throws exception. Use when error is unexpected + * Like Rust `unwrap`: returns result or throws an exception. Use when error is unexpected (you are 100% it simply can't happen). + * I.e: you access a file which is a part of the bundle. Unexistence is a serious bug. + * This function is also good for tests. */ fun orThrow(onError: (ERR) -> Throwable = { e -> if (e is Throwable) e else AssertionError(e) }): SUCC { when (this) { @@ -128,8 +141,24 @@ fun Result.mapResult(map: (SUCC) -> Result this } -fun Result.orLogException(logger: Logger): T? = orLogExceptionImpl(logger, false) -fun Result.orLogExceptionAsWarn(logger: Logger): T? = orLogExceptionImpl(logger, true) +/** + * Log an error to [logger] or return a result. + * [logAsError] is rarely needed flag to log using `ERR` level (`WARN` is used otherwise). + * + * Do you need this method? + * Can you show this error to a user? + * If no (i.e. your process is in the background or does some bulk processing) then use this method. + * + * [logAsError] or not? + * The default (`WARN`) level is for "rainy day", but still fully supported scenarios. + * `WARN` logs are usually read by support engineers in their attempts to find user environment problem or misconfiguration. + * Think: No Internet connection, external process died unexpectedly, file can't be accessed, provided `pyproject.toml` is malformed. + * + * `ERR` mode is reported to the exception analyzer, and it is almost always a *developer* problem. This is an issue *we* must fix. + * In most cases exceptions/errors are the best tools for such cases, but sometimes we can __live with this bug for some time__ i.e: + * IO access from EDT. It can also be used in tests. + */ +fun Result.orLogException(logger: Logger, logAsError: Boolean = false): T? = orLogExceptionImpl(logger, asWarn = !logAsError) private fun Result.orLogExceptionImpl(logger: Logger, asWarn: Boolean): T? = when (val r = this) { @@ -179,8 +208,16 @@ inline fun Result.mapError(code: (E) -> E2): Result = } // aliases to drop-in replace for kotlin Result + +/** + * Read [Result.successOrNull] first! + */ fun Result.getOrNull(): S? = this.successOrNull val Result.isFailure: Boolean get() = this is Failure val Result.isSuccess: Boolean get() = this is Success + +/** + * Read [Result.orThrow] first! + */ fun Result.getOrThrow(): S = orThrow()