[Platform API] Introduce fundamental abstraction of Platform

This is a large commit, which introduces general API for working with
abstraction of Platform.

- Add new abstraction to 'core' - SimplePlatform - which represents
exactly one platform
  - Clients are strongly prohibited to create instances of SimplePlatform
  by hand, instead, corresponding *Platforms abstraction should be used
  (e.g. JvmPlatforms, JsPlatforms, KonanPlatforms)

- Move TargetPlatform to 'core', it represents now a collection of
SimplePlatforms
  - Clients are strongly encouraged to use TargetPlatform
    (not SimplePlatform) in API, to enforce checks for multiplatform

- Provide a helper-extensions to work with TargetPlatform
(in particular, for getting a specific component platform)

- Remove MultiTargetPlatform in favour of TargetPlatform
  - Notably, this commit leaves another widely used duplicated abstraction,
    namely, IdePlatform. For the sake sanity, removal of IdePlatform is
    extracted in the separate commit.
This commit is contained in:
Dmitry Savvinov
2019-04-24 11:59:07 +03:00
parent 451d14e504
commit d5fbe59a3e
162 changed files with 832 additions and 711 deletions
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.descriptors
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.platform.TargetPlatform
interface ModuleDescriptor : DeclarationDescriptor {
override fun getContainingDeclaration(): DeclarationDescriptor? = null
@@ -30,6 +31,10 @@ interface ModuleDescriptor : DeclarationDescriptor {
*/
val stableName: Name?
// NB: this field should actually be non-null, but making it so implies a LOT of work, so we postpone it for a moment
// TODO: make it non-null
val platform: TargetPlatform?
fun shouldSeeInternalsOf(targetModule: ModuleDescriptor): Boolean
override fun <R, D> accept(visitor: DeclarationDescriptorVisitor<R, D>, data: D): R {
@@ -24,7 +24,7 @@ import org.jetbrains.kotlin.descriptors.PackageViewDescriptor
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.MultiTargetPlatform
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.storage.StorageManager
import org.jetbrains.kotlin.utils.sure
import java.lang.IllegalArgumentException
@@ -34,8 +34,8 @@ class ModuleDescriptorImpl @JvmOverloads constructor(
private val storageManager: StorageManager,
override val builtIns: KotlinBuiltIns,
// May be null in compiler context, should be not-null in IDE context
multiTargetPlatform: MultiTargetPlatform? = null,
capabilities: Map<ModuleDescriptor.Capability<*>, Any?> = emptyMap(),
override val platform: TargetPlatform? = null,
private val capabilities: Map<ModuleDescriptor.Capability<*>, Any?> = emptyMap(),
override val stableName: Name? = null
) : DeclarationDescriptorImpl(Annotations.EMPTY, moduleName), ModuleDescriptor {
init {
@@ -44,8 +44,6 @@ class ModuleDescriptorImpl @JvmOverloads constructor(
}
}
private val capabilities = capabilities + (multiTargetPlatform?.let { mapOf(MultiTargetPlatform.CAPABILITY to it) } ?: emptyMap())
private var dependencies: ModuleDependencies? = null
private var packageFragmentProviderForModuleContent: PackageFragmentProvider? = null
@@ -0,0 +1,68 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.platform
/**
* Core abstraction of Platform API, represents a collection of platforms.
*
* This is the primarily abstraction intended to use in the most part of API, as, usually,
* pretty much anything that may have a platform, may have a several platforms as well in the
* context of multiplatform projects.
*
* Please, use it over the [SimplePlatform] unless you're absolutely sure what you're doing.
*
* NB. Even in cases, where some part of logic makes sense only for a particular platform (e.g., JVM),
* it still can be applicable for [TargetPlatform]s with [componentPlatforms] > 1, e.g. when it consists
* of two version of JDK, JDK and Android, several versions of Android API, etc.
*/
data class TargetPlatform(val componentPlatforms: Set<SimplePlatform>) : Collection<SimplePlatform> by componentPlatforms {
init {
if (componentPlatforms.isEmpty()) throw IllegalArgumentException("Don't instantiate TargetPlatform with empty set of platforms")
}
override fun toString(): String = presentableDescription
}
/**
* Core abstraction of Platform API, represents exactly one platform.
*
* API guarantees:
*
* - direct inheritors are well-known and represent three major platforms supported at the moment (JVM, JS, Native)
*
* - exact enumeration of all inheritors isn't available at the compile time, see [CommonPlatforms]
*
* - each implementation should support equality in a broad sense of "absolutely the same platform"
*
* - it is _prohibited_ to create instances of [SimplePlatform] in the client's code, use respective factory instance (e.g., [JvmPlatforms])
* to get instances of platforms
*
* Ideally, each specific subtype should be either a data class or singleton.
*/
abstract class SimplePlatform(val platformName: String) {
override fun toString(): String = platformName
/** See KDoc for [TargetPlatform.oldFashionedDescription] */
abstract val oldFashionedDescription: String
// FIXME(dsavvinov): hack to allow injection inject JvmTarget into container.
// Proper fix would be to rewrite clients to get JdkPlatform from container, and pull JvmTarget from it
// (this will also remove need in TargetPlatformVersion as the whole, and, in particular, ugly passing
// of TargetPlatformVersion.NoVersion in non-JVM code)
open val targetPlatformVersion: TargetPlatformVersion = TargetPlatformVersion.NoVersion
}
interface TargetPlatformVersion {
val description: String
object NoVersion : TargetPlatformVersion {
override val description = ""
}
}
fun TargetPlatform?.isCommon(): Boolean = this != null && this.size > 1
fun SimplePlatform.toTargetPlatform(): TargetPlatform = TargetPlatform(setOf(this))
@@ -0,0 +1,38 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.platform
inline fun <reified T : SimplePlatform> TargetPlatform.subplatformOfType(): T? = componentPlatforms.filterIsInstance<T>().singleOrNull()
fun <T> TargetPlatform.subplatformOfType(klass: Class<T>): T? = componentPlatforms.filterIsInstance(klass).singleOrNull()
inline fun <reified T : SimplePlatform> TargetPlatform?.has(): Boolean = this != null && subplatformOfType<T>() != null
fun <T> TargetPlatform?.has(klass: Class<T>): Boolean = this != null && subplatformOfType(klass) != null
/**
* Returns human-readable description, mapping multiplatform to 'Common (experimental)',
* as well as maintaining some quirks of the previous representation, like trailing whitespaces
*
* It is needed mainly for backwards compatibility, because some subsystem actually
* managed to rely on the format of that string. In particular, 'facetSerialization.kt' uses
* those string as keys in serialized `.iml`-file, and changing format of that string (including
* trimming pointless whitespaces) leads to incorrectly deserialized facets.
*
* New clients are encouraged to use [presentableDescription] description instead, as it
* also provides better description for multiplatforms.
*/
val TargetPlatform.oldFashionedDescription: String
get() = if (this.isCommon()) "Common (experimental) " else this.single().oldFashionedDescription
/**
* Renders multiplatform in form
* '$PLATFORM_1 / $PLATFORM_2 / ...'
* e.g.
* 'JVM (1.8) / JS / Native'
*/
val TargetPlatform.presentableDescription: String
get() = componentPlatforms.joinToString(separator = "/")
@@ -19,6 +19,7 @@ import org.jetbrains.kotlin.name.FqNameUnsafe
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils.getContainingClass
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.resolve.constants.ConstantValue
import org.jetbrains.kotlin.resolve.constants.EnumValue
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
@@ -57,6 +58,9 @@ val DeclarationDescriptor.isExtension: Boolean
val DeclarationDescriptor.module: ModuleDescriptor
get() = DescriptorUtils.getContainingModule(this)
val DeclarationDescriptor.platform: TargetPlatform?
get() = module.platform
fun ModuleDescriptor.resolveTopLevelClass(topLevelClassFqName: FqName, location: LookupLocation): ClassDescriptor? {
assert(!topLevelClassFqName.isRoot)
return getPackage(topLevelClassFqName.parent()).memberScope.getContributedClassifier(
@@ -1,47 +0,0 @@
/*
* Copyright 2010-2017 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 org.jetbrains.kotlin.resolve
import org.jetbrains.kotlin.descriptors.MemberDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.resolve.descriptorUtil.module
sealed class MultiTargetPlatform : Comparable<MultiTargetPlatform> {
object Common : MultiTargetPlatform() {
override fun compareTo(other: MultiTargetPlatform): Int =
if (other is Common) 0 else -1
}
data class Specific(val platform: String) : MultiTargetPlatform() {
override fun compareTo(other: MultiTargetPlatform): Int =
when (other) {
is Common -> 1
is Specific -> platform.compareTo(other.platform)
}
}
companion object {
@JvmField
val CAPABILITY = ModuleDescriptor.Capability<MultiTargetPlatform>("MULTI_TARGET_PLATFORM")
}
}
fun ModuleDescriptor.getMultiTargetPlatform(): MultiTargetPlatform? =
module.getCapability(MultiTargetPlatform.CAPABILITY)
fun MemberDescriptor.getMultiTargetPlatform(): String? =
(module.getMultiTargetPlatform() as? MultiTargetPlatform.Specific)?.platform
@@ -30,6 +30,7 @@ import org.jetbrains.kotlin.descriptors.impl.TypeParameterDescriptorImpl;
import org.jetbrains.kotlin.incremental.components.LookupLocation;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.platform.TargetPlatform;
import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter;
import org.jetbrains.kotlin.resolve.scopes.MemberScope;
@@ -80,6 +81,12 @@ public class ErrorUtils {
return Name.special("<ERROR MODULE>");
}
@Nullable
@Override
public TargetPlatform getPlatform() {
return null;
}
@NotNull
@Override
public PackageViewDescriptor getPackage(@NotNull FqName fqName) {