Skip to content

Commit

Permalink
for-loop name resolution and inference support
Browse files Browse the repository at this point in the history
  • Loading branch information
mkurnikov committed Jan 15, 2024
1 parent 99c6802 commit 3cd8cf4
Show file tree
Hide file tree
Showing 12 changed files with 151 additions and 42 deletions.
17 changes: 13 additions & 4 deletions src/main/grammars/MoveParser.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -948,10 +948,19 @@ private ConditionBody_recover ::= !')'

ElseBlock ::= else AnyBlock { pin = 1 }

LoopExpr ::= loop AnyBlock { pin = 1 }
WhileExpr ::= while Condition AnyBlock { pin = 1 }
ForExpr ::= for ForIterCondition AnyBlock { pin = 1 }
ForIterCondition ::= '(' BindingPat in Expr ')' {
LoopExpr ::= loop AnyBlock {
pin = 1
implements = [ "org.move.lang.core.psi.MvLoopLike" ]
}
WhileExpr ::= while Condition AnyBlock {
pin = 1
implements = [ "org.move.lang.core.psi.MvLoopLike" ]
}
ForExpr ::= for ForIterCondition AnyBlock {
pin = 1
implements = [ "org.move.lang.core.psi.MvLoopLike" ]
}
ForIterCondition ::= '(' BindingPat in RangeExpr ')' {
pin = 1
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/org/move/ide/presentation/ty.kt
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ private fun render(
}
is TyTuple -> ty.types.joinToString(", ", "(", ")", transform = r)
is TyVector -> "vector<${r(ty.item)}>"
is TyIntegerRange -> "range"
is TyRange -> "range"
is TyReference -> {
val prefix = if (ty.permissions.contains(RefPermissions.WRITE)) "&mut " else "&"
"$prefix${r(ty.referenced)}"
Expand Down
6 changes: 3 additions & 3 deletions src/main/kotlin/org/move/lang/core/MvTokenType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ class MvTokenType(debugName: String) : IElementType(debugName, MoveLanguage)
fun tokenSetOf(vararg tokens: IElementType) = TokenSet.create(*tokens)

val MOVE_KEYWORDS = tokenSetOf(
LET, MUT, ABORT, BREAK, CONTINUE, IF, ELSE, LOOP, RETURN, AS, WHILE,
LET, MUT, ABORT, BREAK, CONTINUE, IF, ELSE, LOOP, RETURN, AS, WHILE, FOR,
SCRIPT_KW, ADDRESS, MODULE_KW, PUBLIC, FUN, STRUCT_KW, ACQUIRES, USE, HAS, PHANTOM,
MOVE, CONST_KW, NATIVE, FRIEND, ENTRY,
ASSUME, ASSERT, REQUIRES, ENSURES, INVARIANT, MODIFIES, PRAGMA, INCLUDE, ABORTS_IF, WITH,
MOVE, CONST_KW, NATIVE, FRIEND, ENTRY, INLINE,
ASSUME, ASSERT, REQUIRES, ENSURES, INVARIANT, MODIFIES, PRAGMA, INCLUDE, ABORTS_IF, WITH, UPDATE, DECREASES,
SPEC, SCHEMA_KW, GLOBAL, LOCAL,
EMITS, APPLY, TO, EXCEPT, INTERNAL,
FORALL, EXISTS, IN, WHERE,
Expand Down
6 changes: 6 additions & 0 deletions src/main/kotlin/org/move/lang/core/psi/MvLoopLike.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.move.lang.core.psi

interface MvLoopLike: MvElement {
val codeBlock: MvCodeBlock?
val inlineBlock: MvInlineBlock?
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ fun processItemsInScope(
is MvScript -> processor.matchAll(contextScopeInfo, scope.consts())
is MvFunctionLike -> processor.matchAll(contextScopeInfo, scope.allParamsAsBindings)
is MvLambdaExpr -> processor.matchAll(contextScopeInfo, scope.bindingPatList)
is MvForExpr -> {
val iterConditionBindingPat = scope.forIterCondition?.bindingPat
if (iterConditionBindingPat != null) {
processor.match(iterConditionBindingPat)
} else {
false
}
}
is MvItemSpec -> {
val item = scope.item
when (item) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ class InferenceContext(
ty1msl is TyPrimitive && ty2msl is TyPrimitive && ty1msl.name == ty2msl.name -> Ok(Unit)

ty1msl is TyVector && ty2msl is TyVector -> combineTypes(ty1msl.item, ty2msl.item)
ty1msl is TyIntegerRange && ty2msl is TyIntegerRange -> Ok(Unit)
ty1msl is TyRange && ty2msl is TyRange -> Ok(Unit)

ty1msl is TyReference && ty2msl is TyReference
// inferredTy permissions should be a superset of expectedTy permissions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ class TypeInferenceWalker(
"${bindingContext.elementType} binding is not inferred",
TyUnknown
)
} }
}
}
this.ctx.writePatTy(binding, ty)
}
}
Expand Down Expand Up @@ -211,7 +212,7 @@ class TypeInferenceWalker(

is MvDotExpr -> inferDotExprTy(expr)
is MvDerefExpr -> inferDerefExprTy(expr)
is MvLitExpr -> inferLitExprTy(expr)
is MvLitExpr -> inferLitExprTy(expr, expected)
is MvTupleLitExpr -> inferTupleLitExprTy(expr, expected)
is MvLambdaExpr -> inferLambdaExpr(expr, expected)

Expand Down Expand Up @@ -239,6 +240,7 @@ class TypeInferenceWalker(
is MvIfExpr -> inferIfExprTy(expr, expected)
is MvWhileExpr -> inferWhileExpr(expr)
is MvLoopExpr -> inferLoopExpr(expr)
is MvForExpr -> inferForExpr(expr)
is MvReturnExpr -> {
expr.expr?.inferTypeCoercableTo(returnTy)
TyNever
Expand Down Expand Up @@ -559,7 +561,7 @@ class TypeInferenceWalker(
val posExpr = indexExpr.exprList.drop(1).first()
val posTy = posExpr.inferType()
return when (posTy) {
is TyIntegerRange -> (receiverTy as? TyVector) ?: TyUnknown
is TyRange -> (receiverTy as? TyVector) ?: TyUnknown
is TyNum -> (receiverTy as? TyVector)?.item ?: TyUnknown
else -> TyUnknown
}
Expand All @@ -582,7 +584,7 @@ class TypeInferenceWalker(
val rangeTy = quantBinding.expr?.inferType()
when (rangeTy) {
is TyVector -> rangeTy.item
is TyIntegerRange -> TyInteger.DEFAULT
is TyRange -> TyInteger.DEFAULT
else -> TyUnknown
}
}
Expand All @@ -593,9 +595,11 @@ class TypeInferenceWalker(
}

private fun inferRangeExprTy(rangeExpr: MvRangeExpr): Ty {
rangeExpr.exprList.firstOrNull()?.inferTypeCoercableTo(TyInteger.DEFAULT)
rangeExpr.exprList.drop(1).firstOrNull()?.inferTypeCoercableTo(TyInteger.DEFAULT)
return TyIntegerRange
val leftTy = rangeExpr.exprList.firstOrNull()?.inferType() ?: TyUnknown
// rangeExpr.exprList.firstOrNull()?.inferTypeCoercableTo(TyInteger.DEFAULT)
rangeExpr.exprList.drop(1).firstOrNull()?.inferType(expected = leftTy)
// rangeExpr.exprList.drop(1).firstOrNull()?.inferTypeCoercableTo(TyInteger.DEFAULT)
return TyRange(leftTy)
}

private fun inferBinaryExprTy(binaryExpr: MvBinaryExpr): Ty {
Expand Down Expand Up @@ -768,19 +772,25 @@ class TypeInferenceWalker(
return TyTuple(types)
}

private fun inferLitExprTy(litExpr: MvLitExpr): Ty {
return when {
litExpr.boolLiteral != null -> TyBool
litExpr.addressLit != null -> TyAddress
litExpr.integerLiteral != null || litExpr.hexIntegerLiteral != null -> {
if (ctx.msl) return TyNum
val literal = (litExpr.integerLiteral ?: litExpr.hexIntegerLiteral)!!
return TyInteger.fromSuffixedLiteral(literal) ?: TyInfer.IntVar()
}
litExpr.byteStringLiteral != null -> TyByteString(ctx.msl)
litExpr.hexStringLiteral != null -> TyHexString(ctx.msl)
else -> TyUnknown
}
private fun inferLitExprTy(litExpr: MvLitExpr, expected: Expectation): Ty {
val litTy =
when {
litExpr.boolLiteral != null -> TyBool
litExpr.addressLit != null -> TyAddress
litExpr.integerLiteral != null || litExpr.hexIntegerLiteral != null -> {
if (ctx.msl) return TyNum
val literal = (litExpr.integerLiteral ?: litExpr.hexIntegerLiteral)!!
return TyInteger.fromSuffixedLiteral(literal) ?: TyInfer.IntVar()
}
litExpr.byteStringLiteral != null -> TyByteString(ctx.msl)
litExpr.hexStringLiteral != null -> TyHexString(ctx.msl)
else -> TyUnknown
}
expected.onlyHasTy(this.ctx)
?.let {
coerce(litExpr, litTy, it)
}
return litTy
}

private fun inferIfExprTy(ifExpr: MvIfExpr, expected: Expectation): Ty {
Expand Down Expand Up @@ -841,20 +851,35 @@ class TypeInferenceWalker(
if (conditionExpr != null) {
conditionExpr.inferTypeCoercableTo(TyBool)
}
val codeBlock = whileExpr.codeBlock
val inlineBlockExpr = whileExpr.inlineBlock?.expr
return inferLoopLike(whileExpr)
}

val expected = Expectation.maybeHasType(TyUnit)
when {
codeBlock != null -> codeBlock.inferBlockType(expected)
inlineBlockExpr != null -> inlineBlockExpr.inferType(expected)
private fun inferLoopExpr(loopExpr: MvLoopExpr): Ty {
return inferLoopLike(loopExpr)
}

private fun inferForExpr(forExpr: MvForExpr): Ty {
val iterCondition = forExpr.forIterCondition
if (iterCondition != null) {
val rangeExpr = iterCondition.rangeExpr
val bindingTy =
if (rangeExpr != null) {
val rangeTy = inferExprTy(rangeExpr) as? TyRange
rangeTy?.itemTy ?: TyUnknown
} else {
TyUnknown
}
val bindingPat = iterCondition.bindingPat
if (bindingPat != null) {
this.ctx.writePatTy(bindingPat, bindingTy)
}
}
return TyUnit
return inferLoopLike(forExpr)
}

private fun inferLoopExpr(loopExpr: MvLoopExpr): Ty {
val codeBlock = loopExpr.codeBlock
val inlineBlockExpr = loopExpr.inlineBlock?.expr
private fun inferLoopLike(loopLike: MvLoopLike): Ty {
val codeBlock = loopLike.codeBlock
val inlineBlockExpr = loopLike.inlineBlock?.expr
val expected = Expectation.maybeHasType(TyUnit)
when {
codeBlock != null -> codeBlock.inferBlockType(expected)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ package org.move.lang.core.types.ty

import org.move.ide.presentation.tyToString

/// msl only
object TyIntegerRange: Ty() {
data class TyRange(val itemTy: Ty): Ty() {
override fun abilities(): Set<Ability> = Ability.all()
override fun toString(): String = tyToString(this)
}
Original file line number Diff line number Diff line change
Expand Up @@ -278,4 +278,13 @@ class HighlightingAnnotatorTest : AnnotatorTestCase(HighlightingAnnotator::class
}
}
""")

fun `test for loop keywords`() = checkHighlighting("""
module 0x1::m {
fun main() {
let <VARIABLE>for</VARIABLE> = 1;
<KEYWORD>for</KEYWORD> (i <KEYWORD>in</KEYWORD> 1..10) {};
}
}
""")
}
Original file line number Diff line number Diff line change
Expand Up @@ -1470,4 +1470,12 @@ module 0x1::pool {
}
}
""")

fun `test range expr second has different type`() = checkByText("""
module 0x1::m {
fun main() {
let a = 1..<error descr="Incompatible type 'bool', expected 'integer'">true</error>;
}
}
""")
}
12 changes: 12 additions & 0 deletions src/test/kotlin/org/move/lang/resolve/ResolveVariablesTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -336,4 +336,16 @@ module 0x1::string_tests {
//^
}
""")

fun `test for loop name resolution`() = checkByCode("""
module 0x1::m {
fun main() {
for (ind in 0..10) {
//X
ind;
//^
}
}
}
""")
}
35 changes: 34 additions & 1 deletion src/test/kotlin/org/move/lang/types/ExpressionTypesTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,7 @@ class ExpressionTypesTest : TypificationTestCase() {
fun main() {
let a = while (true) { 1; };
a;
//^ ()
//^ <never>
}
}
"""
Expand Down Expand Up @@ -1757,4 +1757,37 @@ module 0x1::main {
}
}
""")

fun `test for expr index partial`() = testExpr("""
module 0x1::m {
fun main() {
for (i in ) {
i;
//^ <unknown>
};
}
}
""")

fun `test for expr index range expr int type`() = testExpr("""
module 0x1::m {
fun main() {
for (i in 1..10) {
i;
//^ integer
};
}
}
""")

fun `test for expr index range expr bool type`() = testExpr("""
module 0x1::m {
fun main() {
for (i in false..true) {
i;
//^ bool
};
}
}
""")
}

0 comments on commit 3cd8cf4

Please sign in to comment.