Skip to content

Commit

Permalink
Ensure global classes (i.e. built-ins) can be extended.
Browse files Browse the repository at this point in the history
Closes #55
  • Loading branch information
Benjamin-Dobell committed Jan 16, 2023
1 parent 5d721ed commit 5db79fb
Show file tree
Hide file tree
Showing 13 changed files with 46 additions and 37 deletions.
Expand Up @@ -26,7 +26,6 @@ import com.tang.intellij.lua.psi.LuaCallExpr
import com.tang.intellij.lua.psi.LuaExpression
import com.tang.intellij.lua.psi.LuaIndexExpr
import com.tang.intellij.lua.psi.LuaVisitor
import com.tang.intellij.lua.search.ProjectSearchContext
import com.tang.intellij.lua.search.PsiSearchContext
import com.tang.intellij.lua.search.SearchContext
import com.tang.intellij.lua.ty.*
Expand Down
Expand Up @@ -35,7 +35,7 @@ class UndeclaredMemberInspection : StrictInspection() {
val memberName = o.name

Ty.eachResolved(context, prefix) { prefixTy ->
if (!prefixTy.isGlobal && !(prefixTy.isUnknown && LuaSettings.instance.isUnknownIndexable)) {
if ((!prefixTy.isGlobal && !prefixTy.isUnknown) || !LuaSettings.instance.isUnknownIndexable) {
if (memberName != null) {
if (prefixTy.guessMemberType(context, memberName) == null) {
myHolder.registerProblem(o, "No such member '%s' found on type '%s'".format(memberName, prefixTy))
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/tang/intellij/lua/lang/LuaLanguage.java
Expand Up @@ -24,7 +24,7 @@
*/
public class LuaLanguage extends Language {

public static final int INDEX_VERSION = 70;
public static final int INDEX_VERSION = 71;

public static final LuaLanguage INSTANCE = new LuaLanguage();

Expand Down
Expand Up @@ -52,11 +52,6 @@ class LuaClassMethodType : LuaStubElementType<LuaClassMethodDefStatStub, LuaClas
}
}

if (classNameSet.isEmpty()) {
classNameSet.add(createSerializedClass(expr.text))
}


val visibility = def.visibility
val isStatic = methodName.dot != null
val isDeprecated = def.isDeprecated
Expand Down
Expand Up @@ -116,6 +116,6 @@ class LuaDocTagClassStubImpl(override val className: String,
override val classType: TyClass
get() {
val flags = if (isShape) TyFlags.SHAPE else 0
return createSerializedClass(className, params, className, superClass, signatures, aliasName, flags)
return createSerializedClass(className, params, null, superClass, signatures, aliasName, flags)
}
}
32 changes: 18 additions & 14 deletions src/main/java/com/tang/intellij/lua/ty/Expressions.kt
Expand Up @@ -378,7 +378,7 @@ private fun LuaNameExpr.infer(context: SearchContext): ITy? {
}
}

withSearchGuard(this) {
var ty = withSearchGuard(this) {
val multiResolve = multiResolve(context, this)
var maxTimes = 10

Expand All @@ -400,6 +400,19 @@ private fun LuaNameExpr.infer(context: SearchContext): ITy? {

type
} ?: getType(context, this)

// Global
if (ty == null && context.isDumb && this.isGlobal()) {
// In order to facilitate the extension of globals without needing to *explicitly* refer to the
// underlying global variable's class by name. Since we can't look up / resolve the global's variable's
// type during indexing, we instead attach members to a global type (identified by variable name). When
// we process a class' members later, we also process against its aliasName i.e. name of global variable.
// NOTE: We only need to hit this code path in "dumb mode" (i.e. during stub indexing) since that's where
// we create/index class members. All other times, we'll leave the ty as nil (unknown).
ty = TyClass.createGlobalType(this)
}

ty
})
}

Expand Down Expand Up @@ -429,26 +442,17 @@ private fun getType(context: SearchContext, def: PsiElement): ITy? {
}
}
}

//Global
if (type != null && isGlobal(def) && def.docTy == null && type !is ITyPrimitive) {
// Explicitly instantiating a union (not calling the type.union()) as the global type resolves to type,
// and hence we would have just got type back. We're creating a union because we need to ensure members
// are indexed against the global name (for completion) as well as the other type (for type resolution).
type = TyUnion(listOf(type, TyClass.createGlobalType(def)))
}

type
}
is LuaPsiTypeGuessable -> def.guessType(context)
else -> null
}
}

private fun isGlobal(nameExpr: LuaNameExpr): Boolean {
val minx = nameExpr as LuaNameExprMixin
val gs = minx.greenStub
return gs?.isGlobal ?: (resolveLocal(null, nameExpr) == null)
fun LuaNameExpr.isGlobal(): Boolean {
val mixin = this as LuaNameExprMixin
val greenStub = mixin.greenStub
return greenStub?.isGlobal ?: (resolveLocal(null, this) == null)
}

fun LuaLiteralExpr.infer(): ITy {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/tang/intellij/lua/ty/Ty.kt
Expand Up @@ -588,7 +588,7 @@ abstract class Ty(override val kind: TyKind) : ITy {
override fun toString() = displayName

override fun contravariantOf(context: SearchContext, other: ITy, varianceFlags: Int): Boolean {
if ((other.kind == TyKind.Unknown && varianceFlags and TyVarianceFlags.STRICT_UNKNOWN == 0)
if (((other.kind == TyKind.Unknown || other.isGlobal) && varianceFlags and TyVarianceFlags.STRICT_UNKNOWN == 0)
|| (other.kind == TyKind.Nil && varianceFlags and TyVarianceFlags.STRICT_NIL == 0 && !LuaSettings.instance.isNilStrict)
) {
return true
Expand Down
12 changes: 6 additions & 6 deletions src/main/java/com/tang/intellij/lua/ty/TyClass.kt
Expand Up @@ -38,7 +38,7 @@ import com.tang.intellij.lua.stubs.*

interface ITyClass : ITyResolvable {
val className: String
val varName: String
val varName: String?

var superClass: ITy?
var aliasName: String?
Expand Down Expand Up @@ -168,7 +168,7 @@ private fun equalToShape(context: SearchContext, target: ITy, source: ITy): Bool

abstract class TyClass(override val className: String,
override var params: Array<TyGenericParameter>? = null,
override val varName: String = "",
override val varName: String? = "",
override var superClass: ITy? = null,
override var signatures: Array<IFunSignature>? = null
) : Ty(TyKind.Class), ITyClass {
Expand Down Expand Up @@ -407,7 +407,7 @@ abstract class TyClass(override val className: String,
class TyPsiDocClass(override val psi: LuaDocTagClass) : TyClass(
psi.name,
psi.genericDefList.map { TyGenericParameter(it) }.toTypedArray(),
"",
null,
psi.superClass?.getType(),
psi.overloads
), IPsiTy<LuaDocTagClass> {
Expand All @@ -425,7 +425,7 @@ class TyPsiDocClass(override val psi: LuaDocTagClass) : TyClass(

open class TySerializedClass(name: String,
params: Array<TyGenericParameter>? = null,
varName: String = name,
varName: String? = null,
superClass: ITy? = null,
signatures: Array<IFunSignature>? = null,
alias: String? = null,
Expand Down Expand Up @@ -467,7 +467,7 @@ class TyLazyClass(name: String, val psi: PsiElement? = null) : TySerializedClass

fun createSerializedClass(name: String,
params: Array<TyGenericParameter>? = null,
varName: String = name,
varName: String? = null,
superClass: ITy? = null,
signatures: Array<IFunSignature>? = null,
alias: String? = null,
Expand Down Expand Up @@ -619,7 +619,7 @@ fun getConcreteGenericParameterName(genericParam: TyGenericParameter): String {
}

fun getGlobalTypeName(text: String): String {
return if (text == Constants.WORD_G) text else "$$text"
return if (text == Constants.WORD_G) text else "${Constants.WORD_G}.$text"
}

fun getGlobalTypeName(nameExpr: LuaNameExpr): String {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/tang/intellij/lua/ty/TyGeneric.kt
Expand Up @@ -30,7 +30,7 @@ fun genericParameterName(def: LuaDocGenericDef): String {
return "${def.id.text}@${def.node.startOffset}@${def.containingFile.getFileIdentifier()}"
}

class TyGenericParameter(name: String, varName: String, superClass: ITy? = null) : TySerializedClass(name, emptyArray(), varName, superClass, null) {
class TyGenericParameter(name: String, override val varName: String, superClass: ITy? = null) : TySerializedClass(name, emptyArray(), varName, superClass, null) {
constructor(def: LuaDocGenericDef) : this(genericParameterName(def), def.id.text, def.superClass?.getType())

override fun equals(other: Any?): Boolean {
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/com/tang/intellij/lua/ty/TyRenderer.kt
Expand Up @@ -332,10 +332,10 @@ open class TyRenderer : TyVisitor(), ITyRenderer {
}
clazz.isAnonymous -> {
if (isSuffixedClass(clazz)) {
clazz.varName
} else {
"[local ${clazz.varName}]"
clazz.varName?.let { return it }
}

"[local ${clazz.varName}]"
}
clazz.isGlobal -> "[global ${clazz.varName}]"
else -> "${clazz.className}${renderGenericParams(clazz.params?.map { it.toString() })}"
Expand Down
7 changes: 7 additions & 0 deletions src/test/resources/inspections/global_definitions.lua
Expand Up @@ -16,3 +16,10 @@ function GlobalAnonymousClass.doSomethingElse()
end

GlobalAnonymousClass.anotherNumber = 2

--- Extend built-in global without explicitly referring to its type

---@param a number
function math.extension(a)
return a
end
6 changes: 5 additions & 1 deletion src/test/resources/inspections/global_usage.lua
Expand Up @@ -16,5 +16,9 @@ aNumber = GlobalAnonymousClass.anotherNumber
aString = <error descr="Type mismatch. Required: 'string' Found: 'number'">GlobalClass.someNumber</error>
aString = <error descr="Type mismatch. Required: 'string' Found: 'number'">GlobalAnonymousClass.anotherNumber</error>

---@type number
local fromStdLib = math.pi
aNumber = fromStdLib
aString = <error descr="Type mismatch. Required: 'string' Found: 'number'">fromStdLib</error>

aNumber = math.extension(aNumber)
aString = <error descr="Type mismatch. Required: 'string' Found: 'number'">math.extension(aNumber)</error>
4 changes: 2 additions & 2 deletions src/test/resources/inspections/self.lua
Expand Up @@ -28,7 +28,7 @@ end
---@return self
function SelfA.dotMethod()
---@type self
local selfTypedVar = <warning descr="Undeclared variable 'self'.">self</warning>
local selfTypedVar

---@type SelfA
local someSelfA
Expand All @@ -46,7 +46,7 @@ end
---@return self
SelfA.lambdaMethod = function()
---@type self
local selfTypedVar = <warning descr="Undeclared variable 'self'.">self</warning>
local selfTypedVar

---@type SelfA
local someSelfA
Expand Down

0 comments on commit 5db79fb

Please sign in to comment.