Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1034,8 +1034,23 @@ trait RustVisitor(implicit withSchemaValidation: ValidationMode) { this: AstCrea
// Attr* start:Expr? op:('..' | '..=') end:Expr?
private def visitRangeExpr(rangeExpr: RangeExpr): Ast = {
(rangeExpr.start, rangeExpr.end, rangeExpr.isInclusive) match {
// a..b becomes Range { start, end }
case (Some(start), Some(end), false) =>
// NB: RangeInclusive (a..=b) has private start/end fields + a `new` constructor, whereas
// the remaining ranges have no constructor but public start/end fields. Hence the special
// treatment here.
case (Some(start), Some(end), true) =>
// TODO(rust_ast_gen) matches the stdlib type, but it ideally would come from rust_ast_gen.
val methodFullName = "core::ops::range::RangeInclusive<Idx>::new"
val call = callNode(
rangeExpr,
code(rangeExpr),
"new",
methodFullName,
DispatchTypes.STATIC_DISPATCH,
None,
Some(typeFullNameForExpr(rangeExpr))
)
callAst(call, Seq(visitExpr(start), visitExpr(end)))
case (start, end, _) =>
val rangeType = typeFullNameForExpr(rangeExpr)
val tmpName = "tmp"
allocBlockAst(rangeExpr, tmpName, rangeType) { mktmpIdent =>
Expand All @@ -1044,10 +1059,8 @@ trait RustVisitor(implicit withSchemaValidation: ValidationMode) { this: AstCrea
val lhs = fieldAccessAst(expr, expr, mktmpIdent(expr), s"$tmpName.$fieldName", fieldName, fieldType)
callAst(assignmentNode(expr, s"$tmpName.$fieldName = ${code(expr)}"), Seq(lhs, visitExpr(expr)))
}
Seq(mkAssign("start", start), mkAssign("end", end))
(start.map(mkAssign("start", _)) ++ end.map(mkAssign("end", _))).toSeq
}

case _ => notHandledYet(rangeExpr)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,171 @@ class RangeTests extends Rust2CpgSuite {
}
}
}

"`a..`" should {
val cpg = code("""
|fn main() {
| 1..;
|}
|""".stripMargin)

"lower into a block with appropriate type and number of children" in {
inside(cpg.method.name("main").body.astChildren.isBlock.l) { case block :: Nil =>
block.code shouldBe "1.."
block.typeFullName shouldBe "core::ops::range::RangeFrom<i32>"
block.astChildren.size shouldBe 4
}
}

"the block's third child is a field assignment (start)" in {
inside(cpg.block.codeExact("1..").astChildren.order(3).l) { case (assign: Call) :: Nil =>
assign.code shouldBe "tmp.start = 1"

inside(assign.argument(1)) { case fieldAccess: Call =>
fieldAccess.code shouldBe "tmp.start"

inside(fieldAccess.argument(2)) { case fieldIdent: FieldIdentifier =>
fieldIdent.canonicalName shouldBe "start"
}
}

inside(assign.argument(2)) { case lit: Literal =>
lit.code shouldBe "1"
lit.typeFullName shouldBe "i32"
}
}
}
}

"`..b`" should {
val cpg = code("""
|fn main() {
| ..2;
|}
|""".stripMargin)

"lower into a block with appropriate type and number of children" in {
inside(cpg.method.name("main").body.astChildren.isBlock.l) { case block :: Nil =>
block.code shouldBe "..2"
block.typeFullName shouldBe "core::ops::range::RangeTo<i32>"
block.astChildren.size shouldBe 4
}
}

"the block's third child is a field assignment (end)" in {
inside(cpg.block.codeExact("..2").astChildren.order(3).l) { case (assign: Call) :: Nil =>
assign.code shouldBe "tmp.end = 2"

inside(assign.argument(1)) { case fieldAccess: Call =>
fieldAccess.code shouldBe "tmp.end"

inside(fieldAccess.argument(2)) { case fieldIdent: FieldIdentifier =>
fieldIdent.canonicalName shouldBe "end"
}
}

inside(assign.argument(2)) { case lit: Literal =>
lit.code shouldBe "2"
lit.typeFullName shouldBe "i32"
}
}
}
}

"`..=b`" should {
val cpg = code("""
|fn main() {
| ..=2;
|}
|""".stripMargin)

"lower into a block with appropriate type and number of children" in {
inside(cpg.method.name("main").body.astChildren.isBlock.l) { case block :: Nil =>
block.code shouldBe "..=2"
block.typeFullName shouldBe "core::ops::range::RangeToInclusive<i32>"
block.astChildren.size shouldBe 4
}
}

"the block's third child is a field assignment (end)" in {
inside(cpg.block.codeExact("..=2").astChildren.order(3).l) { case (assign: Call) :: Nil =>
assign.code shouldBe "tmp.end = 2"

inside(assign.argument(2)) { case lit: Literal =>
lit.code shouldBe "2"
lit.typeFullName shouldBe "i32"
}
}
}
}

"`..`" should {
val cpg = code("""
|fn main() {
| ..;
|}
|""".stripMargin)

"lower into a block with appropriate type and number of children" in {
inside(cpg.method.name("main").body.astChildren.isBlock.l) { case block :: Nil =>
block.code shouldBe ".."
block.typeFullName shouldBe "core::ops::range::RangeFull"
block.astChildren.size shouldBe 3
}
}

"the block's first child is a LOCAL declaration" in {
inside(cpg.block.codeExact("..").astChildren.order(1).l) { case (local: Local) :: Nil =>
local.name shouldBe "tmp"
local.typeFullName shouldBe "core::ops::range::RangeFull"
}
}

"the block's second child is an alloc assignment" in {
inside(cpg.block.codeExact("..").astChildren.order(2).l) { case (assign: Call) :: Nil =>
assign.code shouldBe s"tmp = ${Operators.alloc}"
}
}

"the block's third child is an identifier" in {
inside(cpg.block.codeExact("..").astChildren.order(3).l) { case (ident: Identifier) :: Nil =>
ident.name shouldBe "tmp"
ident.typeFullName shouldBe "core::ops::range::RangeFull"
}
}
}

"`a..=b`" should {
val cpg = code("""
|fn main() {
| let r = 1..=2;
|}
|""".stripMargin)

"lower the assigned value into a static call to RangeInclusive::new" in {
inside(cpg.assignment.argument(2).l) { case (call: Call) :: Nil =>
call.name shouldBe "new"
call.code shouldBe "1..=2"
call.methodFullName shouldBe "core::ops::range::RangeInclusive<Idx>::new"
call.typeFullName shouldBe "core::ops::range::RangeInclusive<i32>"
call.dispatchType shouldBe DispatchTypes.STATIC_DISPATCH
}
}

"the call's first argument is the start operand" in {
inside(cpg.call.methodFullNameExact("core::ops::range::RangeInclusive<Idx>::new").argument(1).l) {
case (lit: Literal) :: Nil =>
lit.code shouldBe "1"
lit.typeFullName shouldBe "i32"
}
}

"the call's second argument is the end operand" in {
inside(cpg.call.methodFullNameExact("core::ops::range::RangeInclusive<Idx>::new").argument(2).l) {
case (lit: Literal) :: Nil =>
lit.code shouldBe "2"
lit.typeFullName shouldBe "i32"
}
}
}
}
Loading