Skip to content

Commit

Permalink
Improve label by figuring out concrete type parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
jbartok committed Feb 28, 2025
1 parent 4622e76 commit c7fc40c
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import org.gradle.declarative.dsl.schema.DataTypeRef
import org.gradle.declarative.dsl.schema.EnumClass
import org.gradle.declarative.dsl.schema.FunctionSemantics
import org.gradle.declarative.dsl.schema.SchemaFunction
import org.gradle.declarative.dsl.schema.VarargParameter
import org.gradle.declarative.lsp.build.model.DeclarativeResourcesModel
import org.gradle.declarative.lsp.extension.indexBasedOverlayResultFromDocuments
import org.gradle.declarative.lsp.extension.toLspRange
Expand All @@ -60,6 +61,7 @@ import org.gradle.declarative.lsp.visitor.SemanticErrorToDiagnosticVisitor
import org.gradle.declarative.lsp.visitor.SyntaxErrorToDiagnosticVisitor
import org.gradle.declarative.lsp.visitor.visit
import org.gradle.internal.declarativedsl.analysis.SchemaTypeRefContext
import org.gradle.internal.declarativedsl.analysis.TypeRefContext
import org.gradle.internal.declarativedsl.dom.DeclarativeDocument
import org.gradle.internal.declarativedsl.dom.DocumentResolution
import org.gradle.internal.declarativedsl.dom.mutation.MutationParameterKind
Expand Down Expand Up @@ -390,13 +392,15 @@ private fun computePropertyByValueFactoryCompletions(
valueFactoryIndex: ValueFactoryIndex,
): List<CompletionItem> {
return dataClass.properties.flatMap { property ->
val resolvedType = SchemaTypeRefContext(schema).resolveRef(property.valueType)
val typeRefContext = SchemaTypeRefContext(schema)
val resolvedType = typeRefContext.resolveRef(property.valueType)
if (resolvedType is DataType.ClassDataType) {
val factoriesForProperty = valueFactoryIndex.factoriesForProperty(resolvedType.name)
factoriesForProperty
?.filter { true } // TODO: will need to match the properties type against the function return type parameters
?.filter { true } // TODO: need to filter out cases where type parameters don't match the properties type
?.map {
val completionLabel = "${it.namePrefix}${computeCompletionLabel(it.function)}" // TODO: will not be fixed for functions with parametrized return type
val genericTypeSubstitution = typeRefContext.computeGenericTypeSubstitution(property.valueType, it.function.returnValueType) ?: emptyMap()
val completionLabel = "${it.namePrefix}${computeCompletionLabel(it.function, genericTypeSubstitution)}"
val insertionText = "${it.namePrefix}${computeCompletionInsertText(it.function, schema)}"
CompletionItem("${property.name} = $completionLabel").apply {
kind = CompletionItemKind.Field
Expand All @@ -410,6 +414,57 @@ private fun computePropertyByValueFactoryCompletions(
}
}

internal fun TypeRefContext.computeGenericTypeSubstitution(
expectedType: DataTypeRef?,
actualType: DataTypeRef
): Map<DataType.TypeVariableUsage, DataType>? {
// TODO: copied from org.gradle.internal.declarativedsl.analysis.utils.kt, because it's not public there

val expected = resolveRef(expectedType ?: return emptyMap())
val actual = resolveRef(actualType)

var hasConflict = false
val result = buildMap {
fun recordEquivalence(typeVariableUsage: DataType.TypeVariableUsage, otherType: DataType) {
val mapping = getOrPut(typeVariableUsage) { otherType }
if (mapping != otherType) {
hasConflict = true
}
}

fun visitTypePair(left: DataType, right: DataType) {
when {
left is DataType.TypeVariableUsage -> recordEquivalence(left, right)
right is DataType.TypeVariableUsage -> recordEquivalence(right, left)

left is DataType.ParameterizedTypeInstance || right is DataType.ParameterizedTypeInstance -> {
if (left is DataType.ParameterizedTypeInstance && right is DataType.ParameterizedTypeInstance && left.typeArguments.size == right.typeArguments.size) {
left.typeArguments.zip(right.typeArguments).forEach { (leftArg, rightArg) ->
when (leftArg) {
is DataType.ParameterizedTypeInstance.TypeArgument.ConcreteTypeArgument -> {
if (rightArg is DataType.ParameterizedTypeInstance.TypeArgument.ConcreteTypeArgument) {
visitTypePair(resolveRef(leftArg.type), resolveRef(rightArg.type))
} else {
hasConflict = true
}
}

is DataType.ParameterizedTypeInstance.TypeArgument.StarProjection -> Unit
}
}
} else {
hasConflict = true
}
}
}
}

visitTypePair(expected, actual)
}

return result.takeIf { !hasConflict }
}

private fun computeFunctionCompletions(
dataClass: DataClass,
analysisSchema: AnalysisSchema
Expand Down Expand Up @@ -446,11 +501,11 @@ private fun computeTypedPlaceholder(
}
}

private fun computeCompletionLabel(function: SchemaFunction): String {
private fun computeCompletionLabel(function: SchemaFunction, genericTypeSubstitution: Map<DataType.TypeVariableUsage, DataType> = emptyMap()): String {
val functionName = function.simpleName
val parameterSignature = when (function.parameters.isEmpty()) {
true -> ""
false -> function.parameters.joinToString(",", "(", ")") { it.toSignatureLabel() }
false -> function.parameters.joinToString(",", "(", ")") { it.toSignatureLabel(genericTypeSubstitution) }
}
val configureBlockLabel = function.semantics.toBlockConfigurabilityLabel().orEmpty()

Expand Down Expand Up @@ -508,10 +563,33 @@ private class ValueFactoryIndexStore {

// Extension functions -------------------------------------------------------------------------------------------------

// TODO: this might not be the best way to resolve the type name, but it works for now
private fun DataTypeRef.toSimpleName(): String = this.toString()
private fun DataTypeRef.toSimpleName(genericTypeSubstitution: Map<DataType.TypeVariableUsage, DataType> = emptyMap()): String =
when (this) {
is DataTypeRef.Name -> fqName.simpleName
is DataTypeRef.NameWithArgs -> "${fqName.simpleName}<${typeArguments.joinToString { it.toSimpleName(genericTypeSubstitution) }}>"
is DataTypeRef.Type -> dataType.toString()
}

private fun DataParameter.toSignatureLabel() = "${this.name}: ${this.type.toSimpleName()}"
private fun DataType.ParameterizedTypeInstance.TypeArgument.toSimpleName(
genericTypeSubstitution: Map<DataType.TypeVariableUsage, DataType> = emptyMap()
) =
when (this) {
is DataType.ParameterizedTypeInstance.TypeArgument.ConcreteTypeArgument ->
if (type is DataTypeRef.Type && (type as DataTypeRef.Type).dataType is DataType.TypeVariableUsage) {
genericTypeSubstitution[(type as DataTypeRef.Type).dataType].toString()
} else {
this.toString()
}
is DataType.ParameterizedTypeInstance.TypeArgument.StarProjection -> this.toString()
}

private fun DataParameter.toSignatureLabel(
genericTypeSubstitution: Map<DataType.TypeVariableUsage, DataType> = emptyMap()
): String = "${this.name}: " +
when (this) {
is VarargParameter -> "vararg ${type.toSimpleName(genericTypeSubstitution)}"
else -> type.toSimpleName(genericTypeSubstitution)
}

private fun FunctionSemantics.toBlockConfigurabilityLabel(): String? = when (this) {
is FunctionSemantics.ConfigureSemantics -> when (this.configureBlockRequirement) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,14 @@ class DeclarativeTextDocumentServiceForBuildScriptsTest : AbstractDeclarativeTex
assertCompletion(
script(), 7, 21, """
|baselineProfile { this: BaselineProfile } --> baselineProfile {${'\n'}|${'\t'}${'$'}0${'\n'}|}
|defaultProguardFiles = listOf(elements: Array<DefaultTypeVariableUsage(variableId=0)>) --> defaultProguardFiles = listOf(${'$'}1)${'$'}0
|defaultProguardFiles = listOf(elements: vararg Array<ProguardFile>) --> defaultProguardFiles = listOf(${'$'}1)${'$'}0
|dependencies { this: AndroidLibraryDependencies } --> dependencies {${'\n'}|${'\t'}${'$'}0${'\n'}|}
|minify { this: Minify } --> minify {${'\n'}|${'\t'}${'$'}0${'\n'}|}
|proguardFile(arg0: String) --> proguardFile("${'$'}{1}")${'$'}0
|proguardFiles = listOf(elements: Array<DefaultTypeVariableUsage(variableId=0)>) --> proguardFiles = listOf(${'$'}1)${'$'}0
|proguardFiles = listOf(elements: vararg Array<ProguardFile>) --> proguardFiles = listOf(${'$'}1)${'$'}0
""".trimMargin()
)
// TODO: wtf is the singular proguardFile?
// TODO: lists are not yet ok
// TODO: why is the name for the parameter in the "proguardFile(String)" function not "name" as in the source?
}

}

0 comments on commit c7fc40c

Please sign in to comment.