From 3cd8cf4a51022178382654e201d3828dfea96c7f Mon Sep 17 00:00:00 2001 From: Maksim Kurnikov Date: Mon, 15 Jan 2024 14:54:59 +0300 Subject: [PATCH] for-loop name resolution and inference support --- src/main/grammars/MoveParser.bnf | 17 +++- .../kotlin/org/move/ide/presentation/ty.kt | 2 +- .../kotlin/org/move/lang/core/MvTokenType.kt | 6 +- .../org/move/lang/core/psi/MvLoopLike.kt | 6 ++ .../lang/core/resolve/LexicalDeclarations.kt | 8 ++ .../lang/core/types/infer/InferenceContext.kt | 2 +- .../core/types/infer/TypeInferenceWalker.kt | 85 ++++++++++++------- .../ty/{TyIntegerRange.kt => TyRange.kt} | 3 +- .../annotator/HighlightingAnnotatorTest.kt | 9 ++ .../inspections/MvTypeCheckInspectionTest.kt | 8 ++ .../move/lang/resolve/ResolveVariablesTest.kt | 12 +++ .../move/lang/types/ExpressionTypesTest.kt | 35 +++++++- 12 files changed, 151 insertions(+), 42 deletions(-) create mode 100644 src/main/kotlin/org/move/lang/core/psi/MvLoopLike.kt rename src/main/kotlin/org/move/lang/core/types/ty/{TyIntegerRange.kt => TyRange.kt} (82%) diff --git a/src/main/grammars/MoveParser.bnf b/src/main/grammars/MoveParser.bnf index 0e56c0550..9effb59d0 100644 --- a/src/main/grammars/MoveParser.bnf +++ b/src/main/grammars/MoveParser.bnf @@ -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 } diff --git a/src/main/kotlin/org/move/ide/presentation/ty.kt b/src/main/kotlin/org/move/ide/presentation/ty.kt index d1741eb37..1b6bb6180 100644 --- a/src/main/kotlin/org/move/ide/presentation/ty.kt +++ b/src/main/kotlin/org/move/ide/presentation/ty.kt @@ -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)}" diff --git a/src/main/kotlin/org/move/lang/core/MvTokenType.kt b/src/main/kotlin/org/move/lang/core/MvTokenType.kt index 407249515..e3304b206 100644 --- a/src/main/kotlin/org/move/lang/core/MvTokenType.kt +++ b/src/main/kotlin/org/move/lang/core/MvTokenType.kt @@ -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, diff --git a/src/main/kotlin/org/move/lang/core/psi/MvLoopLike.kt b/src/main/kotlin/org/move/lang/core/psi/MvLoopLike.kt new file mode 100644 index 000000000..0efacb459 --- /dev/null +++ b/src/main/kotlin/org/move/lang/core/psi/MvLoopLike.kt @@ -0,0 +1,6 @@ +package org.move.lang.core.psi + +interface MvLoopLike: MvElement { + val codeBlock: MvCodeBlock? + val inlineBlock: MvInlineBlock? +} \ No newline at end of file diff --git a/src/main/kotlin/org/move/lang/core/resolve/LexicalDeclarations.kt b/src/main/kotlin/org/move/lang/core/resolve/LexicalDeclarations.kt index 2c9c89c5d..d1be1e506 100644 --- a/src/main/kotlin/org/move/lang/core/resolve/LexicalDeclarations.kt +++ b/src/main/kotlin/org/move/lang/core/resolve/LexicalDeclarations.kt @@ -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) { diff --git a/src/main/kotlin/org/move/lang/core/types/infer/InferenceContext.kt b/src/main/kotlin/org/move/lang/core/types/infer/InferenceContext.kt index b3bfa97c9..7ad697b17 100644 --- a/src/main/kotlin/org/move/lang/core/types/infer/InferenceContext.kt +++ b/src/main/kotlin/org/move/lang/core/types/infer/InferenceContext.kt @@ -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 diff --git a/src/main/kotlin/org/move/lang/core/types/infer/TypeInferenceWalker.kt b/src/main/kotlin/org/move/lang/core/types/infer/TypeInferenceWalker.kt index 677ff96c6..54c632851 100644 --- a/src/main/kotlin/org/move/lang/core/types/infer/TypeInferenceWalker.kt +++ b/src/main/kotlin/org/move/lang/core/types/infer/TypeInferenceWalker.kt @@ -59,7 +59,8 @@ class TypeInferenceWalker( "${bindingContext.elementType} binding is not inferred", TyUnknown ) - } } + } + } this.ctx.writePatTy(binding, ty) } } @@ -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) @@ -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 @@ -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 } @@ -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 } } @@ -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 { @@ -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 { @@ -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) diff --git a/src/main/kotlin/org/move/lang/core/types/ty/TyIntegerRange.kt b/src/main/kotlin/org/move/lang/core/types/ty/TyRange.kt similarity index 82% rename from src/main/kotlin/org/move/lang/core/types/ty/TyIntegerRange.kt rename to src/main/kotlin/org/move/lang/core/types/ty/TyRange.kt index cbbef5660..0d0cb7e4e 100644 --- a/src/main/kotlin/org/move/lang/core/types/ty/TyIntegerRange.kt +++ b/src/main/kotlin/org/move/lang/core/types/ty/TyRange.kt @@ -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.all() override fun toString(): String = tyToString(this) } diff --git a/src/test/kotlin/org/move/ide/annotator/HighlightingAnnotatorTest.kt b/src/test/kotlin/org/move/ide/annotator/HighlightingAnnotatorTest.kt index 0a0fb42a8..2fb5a05fe 100644 --- a/src/test/kotlin/org/move/ide/annotator/HighlightingAnnotatorTest.kt +++ b/src/test/kotlin/org/move/ide/annotator/HighlightingAnnotatorTest.kt @@ -278,4 +278,13 @@ class HighlightingAnnotatorTest : AnnotatorTestCase(HighlightingAnnotator::class } } """) + + fun `test for loop keywords`() = checkHighlighting(""" + module 0x1::m { + fun main() { + let for = 1; + for (i in 1..10) {}; + } + } + """) } diff --git a/src/test/kotlin/org/move/ide/inspections/MvTypeCheckInspectionTest.kt b/src/test/kotlin/org/move/ide/inspections/MvTypeCheckInspectionTest.kt index 5b124e31c..8877489b3 100644 --- a/src/test/kotlin/org/move/ide/inspections/MvTypeCheckInspectionTest.kt +++ b/src/test/kotlin/org/move/ide/inspections/MvTypeCheckInspectionTest.kt @@ -1470,4 +1470,12 @@ module 0x1::pool { } } """) + + fun `test range expr second has different type`() = checkByText(""" + module 0x1::m { + fun main() { + let a = 1..true; + } + } + """) } diff --git a/src/test/kotlin/org/move/lang/resolve/ResolveVariablesTest.kt b/src/test/kotlin/org/move/lang/resolve/ResolveVariablesTest.kt index 7a179ddd8..8537dd22e 100644 --- a/src/test/kotlin/org/move/lang/resolve/ResolveVariablesTest.kt +++ b/src/test/kotlin/org/move/lang/resolve/ResolveVariablesTest.kt @@ -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; + //^ + } + } + } + """) } diff --git a/src/test/kotlin/org/move/lang/types/ExpressionTypesTest.kt b/src/test/kotlin/org/move/lang/types/ExpressionTypesTest.kt index 4cf86b761..0ae40c42c 100644 --- a/src/test/kotlin/org/move/lang/types/ExpressionTypesTest.kt +++ b/src/test/kotlin/org/move/lang/types/ExpressionTypesTest.kt @@ -541,7 +541,7 @@ class ExpressionTypesTest : TypificationTestCase() { fun main() { let a = while (true) { 1; }; a; - //^ () + //^ } } """ @@ -1757,4 +1757,37 @@ module 0x1::main { } } """) + + fun `test for expr index partial`() = testExpr(""" + module 0x1::m { + fun main() { + for (i in ) { + i; + //^ + }; + } + } + """) + + 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 + }; + } + } + """) }