Skip to content

Commit

Permalink
add type processing
Browse files Browse the repository at this point in the history
  • Loading branch information
stackoverflow committed May 16, 2024
1 parent fa46ea7 commit 82b7633
Show file tree
Hide file tree
Showing 8 changed files with 1,089 additions and 20 deletions.
6 changes: 6 additions & 0 deletions pkl-lsp/src/main/kotlin/org/pkl/lsp/LSPUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,9 @@ object LSPUtil {
return firstOrNull { it is T } as T?
}
}

class UnexpectedTypeError(message: String) : AssertionError(message)

fun unexpectedType(obj: Any?): Nothing {
throw UnexpectedTypeError(obj?.javaClass?.typeName ?: "null")
}
79 changes: 79 additions & 0 deletions pkl-lsp/src/main/kotlin/org/pkl/lsp/PklBaseModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package org.pkl.lsp

import org.pkl.core.Release
import org.pkl.core.Version
import org.pkl.lsp.ast.*
import org.pkl.lsp.type.Type

Expand Down Expand Up @@ -55,6 +57,83 @@ class PklBaseModule private constructor() {
this.methods = methods
}

val pklVersion: Version
get() = Release.current().version()

val listConstructor: ClassMethod = method("List")
val setConstructor: ClassMethod = method("Set")
val mapConstructor: ClassMethod = method("Map")

val regexConstructor: ClassMethod = method("Regex")

val anyType: Type.Class = classType("Any")
val nullType: Type.Class = classType("Null")
val booleanType: Type.Class = classType("Boolean")
val numberType: Type.Class = classType("Number")
val intType: Type.Class = classType("Int")
val floatType: Type.Class = classType("Float")
val durationType: Type.Class = classType("Duration")
val dataSizeType: Type.Class = classType("DataSize")
val stringType: Type.Class = classType("String")
val pairType: Type.Class = classType("Pair")
val listType: Type.Class = classType("List")
val setType: Type.Class = classType("Set")
val collectionType: Type.Class = classType("Collection")
val mapType: Type.Class = classType("Map")
val intSeqType: Type.Class = classType("IntSeq")
val listingType: Type.Class = classType("Listing")
val mappingType: Type.Class = classType("Mapping")
val dynamicType: Type.Class = classType("Dynamic")
val typedType: Type.Class = classType("Typed")
val objectType: Type = classType("Object")
val classType: Type.Class = classType("Class")
val typeAliasType: Type.Class = classType("TypeAlias")
val moduleType: Type.Class = classType("Module")
val annotationType: Type.Class = classType("Annotation")
val deprecatedType: Type.Class = classType("Deprecated")
val sourceCodeType: Type.Class = classType("SourceCode")
val functionType: Type.Class = classType("Function")
val function0Type: Type.Class = classType("Function0")
val function1Type: Type.Class = classType("Function1")
val function2Type: Type.Class = classType("Function2")
val function3Type: Type.Class = classType("Function3")
val function4Type: Type.Class = classType("Function4")
val function5Type: Type.Class = classType("Function5")
val mixinType: Type.Alias = aliasType("Mixin")
val varArgsType: Type.Class = classType("VarArgs")
val resourceType: Type.Class = classType("Resource")
val moduleInfoType: Type.Class = classType("ModuleInfo")
val regexType: Type.Class = classType("Regex")
val valueRenderer: Type.Class = classType("ValueRenderer")

val comparableType: Type = aliasType("Comparable")

private fun method(name: String): ClassMethod =
methods[name]
// The only known case where a non-optional pkl.base method or class can legitimately be missing
// is when editing pkl.base in the Pkl project (e.g., pkl.base may not fully parse while being
// edited).
// However, a few users have reported the same problem, and presumably they weren't editing
// pkl.base.
// Since resolution and (to some extent) cause are unknown, throw an error (with some extra
// info) for now.
?: throw AssertionError(
"Cannot find stdlib method `base.$name`."
)

private fun classType(name: String): Type.Class =
types[name] as Type.Class?
// see comment for `method()`
?: throw AssertionError(
"Cannot find stdlib class `base.$name`."
)

private fun aliasType(name: String): Type.Alias =
types[name] as Type.Alias?
?: throw AssertionError(
"Cannot find stdlib alias `base.$name`."
)

companion object {
val instance: PklBaseModule by lazy { PklBaseModule() }
}
Expand Down
1 change: 1 addition & 0 deletions pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Basic.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class QualifiedIdentifierImpl(
override val ctx: QualifiedIdentifierContext
) : AbstractNode(parent, ctx), QualifiedIdentifier {
override val identifiers: List<Terminal> by lazy { getChildren(Terminal::class)!! }
override val fullName: String by lazy { identifiers.joinToString(".") { it.text } }
}

class StringConstantImpl(override val parent: Node, override val ctx: StringConstantContext) :
Expand Down
95 changes: 95 additions & 0 deletions pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Extensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
*/
package org.pkl.lsp.ast

import org.pkl.lsp.PklBaseModule
import org.pkl.lsp.type.Type

val Clazz.supertype: PklType?
get() = classHeader.extends

Expand Down Expand Up @@ -49,3 +52,95 @@ val Clazz.supermodule: PklModule?

// TODO
fun TypeName.resolve(): Node? = TODO("not implemented")

fun SimpleTypeName.resolve(): Node? = TODO("not implemented")

fun Clazz.isSubclassOf(other: Clazz): Boolean {
// optimization
if (this === other) return true

// optimization
// TODO: check if this works
if (!other.isAbstractOrOpen) return this == other

var clazz: Clazz? = this
while (clazz != null) {
// TODO: check if this works
if (clazz == other) return true
if (clazz.supermodule != null) {
return PklBaseModule.instance.moduleType.ctx.isSubclassOf(other)
}
clazz = clazz.superclass
}
return false
}

fun Clazz.isSubclassOf(other: PklModule): Boolean {
// optimization
if (!other.isAbstractOrOpen) return false

var clazz = this
var superclass = clazz.superclass
while (superclass != null) {
clazz = superclass
superclass = superclass.superclass
}
var module = clazz.supermodule
while (module != null) {
// TODO: check if this works
if (module == other) return true
module = module.supermodule
}
return false
}

// TODO: actually escape the text
fun StringConstant.escapedText(): String? = this.value

fun TypeAlias.isRecursive(seen: MutableSet<TypeAlias>): Boolean =
!seen.add(this) || type.isRecursive(seen)

private fun PklType?.isRecursive(seen: MutableSet<TypeAlias>): Boolean =
when (this) {
is DeclaredPklType -> {
val resolved = name.resolve()
resolved is TypeAlias && resolved.isRecursive(seen)
}
is NullablePklType -> type.isRecursive(seen)
is DefaultUnionPklType -> type.isRecursive(seen)
is UnionPklType -> leftType.isRecursive(seen) || rightType.isRecursive(seen)
is ConstrainedPklType -> type.isRecursive(seen)
is ParenthesizedPklType -> type.isRecursive(seen)
else -> false
}

val Node.isInPklBaseModule: Boolean
get() = enclosingModule?.declaration?.moduleHeader?.qualifiedIdentifier?.fullName == "pkl.base"

interface TypeNameRenderer {
fun render(name: TypeName, appendable: Appendable)

fun render(type: Type.Class, appendable: Appendable)

fun render(type: Type.Alias, appendable: Appendable)

fun render(type: Type.Module, appendable: Appendable)
}

object DefaultTypeNameRenderer : TypeNameRenderer {
override fun render(name: TypeName, appendable: Appendable) {
appendable.append(name.simpleTypeName.identifier?.text)
}

override fun render(type: Type.Class, appendable: Appendable) {
appendable.append(type.ctx.name)
}

override fun render(type: Type.Alias, appendable: Appendable) {
appendable.append(type.ctx.name)
}

override fun render(type: Type.Module, appendable: Appendable) {
appendable.append(type.referenceName)
}
}
2 changes: 2 additions & 0 deletions pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/ModuleOrClass.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ class PklModuleImpl(override val ctx: PklParser.ModuleContext) :
override val supermodule: PklModule? by lazy { null }

override val cache: ModuleMemberCache by lazy { ModuleMemberCache.create(this) }

override val modifiers: List<Terminal>? by lazy { terminals.takeWhile { it.isModifier } }
}

class AnnotationImpl(override val parent: Node, override val ctx: PklParser.AnnotationContext) :
Expand Down
53 changes: 45 additions & 8 deletions pkl-lsp/src/main/kotlin/org/pkl/lsp/ast/Node.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ interface Node {

interface QualifiedIdentifier : Node {
val identifiers: List<Terminal>
val fullName: String
}

interface StringConstant : Node {
Expand Down Expand Up @@ -86,7 +87,7 @@ interface DocCommentOwner : Node {
get() = (children.firstOrNull() as? Terminal)?.also { assert(it.type == TokenType.DocComment) }
}

interface PklModule : Node {
interface PklModule : Node, ModifierListOwner {
val isAmend: Boolean
val declaration: ModuleDeclaration?
val imports: List<ImportClause>?
Expand Down Expand Up @@ -222,7 +223,15 @@ interface TypeParameter : Node {

interface ParameterList : Node

interface TypeAlias : IdentifierOwner, ModifierListOwner, TypeDef
interface TypeAlias : TypeDef {
val typeAliasHeader: TypeAliasHeader
val type: PklType
val isRecursive: Boolean
}

interface TypeAliasHeader : IdentifierOwner, ModifierListOwner {
val typeParameterList: TypeParameterList?
}

interface Terminal : Node {
val type: TokenType
Expand Down Expand Up @@ -329,21 +338,46 @@ interface NothingPklType : PklType

interface ModulePklType : PklType

interface StringLiteralPklType : PklType
interface StringLiteralPklType : PklType {
val stringConstant: StringConstant
}

interface DeclaredPklType : PklType {
val name: TypeName
val typeArgumentList: TypeArgumentList?
}

interface TypeArgumentList : Node {
val types: List<PklType>
}

interface ParenthesizedPklType : PklType
interface ParenthesizedPklType : PklType {
val type: PklType
}

interface NullablePklType : PklType
interface NullablePklType : PklType {
val type: PklType
}

interface ConstrainedPklType : PklType
interface ConstrainedPklType : PklType {
val type: PklType?
val exprs: List<Expr>
}

interface UnionPklType : PklType
interface UnionPklType : PklType {
val typeList: List<PklType>
val leftType: PklType
val rightType: PklType
}

interface FunctionPklType : PklType
interface FunctionPklType : PklType {
val parameterList: List<PklType>
val returnType: PklType
}

interface DefaultUnionPklType : PklType {
val type: PklType
}

abstract class AbstractNode(override val parent: Node?, protected open val ctx: ParseTree) : Node {
private val childrenByType: Map<KClass<out Node>, List<Node>> by lazy {
Expand Down Expand Up @@ -430,6 +464,7 @@ fun List<ParseTree>.toNode(parent: Node?, idx: Int): Node? {
is NullableTypeContext -> NullablePklTypeImpl(parent!!, parseTree)
is ConstrainedTypeContext -> ConstrainedPklTypeImpl(parent!!, parseTree)
is UnionTypeContext -> UnionPklTypeImpl(parent!!, parseTree)
is DefaultUnionTypeContext -> DefaultUnionPklTypeImpl(parent!!, parseTree)
is FunctionTypeContext -> FunctionPklTypeImpl(parent!!, parseTree)
is ThisExprContext -> ThisExprImpl(parent!!, parseTree)
is OuterExprContext -> OuterExprImpl(parent!!, parseTree)
Expand Down Expand Up @@ -481,6 +516,8 @@ fun List<ParseTree>.toNode(parent: Node?, idx: Int): Node? {
is ObjectSpreadContext -> ObjectSpreadImpl(parent!!, parseTree)
is TypeParameterContext -> TypeParameterImpl(parent!!, parseTree)
is TypeParameterListContext -> TypeParameterListImpl(parent!!, parseTree)
is TypeAliasHeaderContext -> TypeAliasHeaderImpl(parent!!, parseTree)
is TypeAliasContext -> TypeAliasImpl(parent!!, parseTree)
// treat modifiers as terminals; matches how we do it in pkl-intellij
is ModifierContext -> {
val terminalNode =
Expand Down
Loading

0 comments on commit 82b7633

Please sign in to comment.