Ruby: desugar safe navigation calls

This commit is contained in:
Arthur Baars
2022-04-29 14:01:43 +02:00
parent c9f7568ca3
commit 76f806159c
7 changed files with 203 additions and 32 deletions

View File

@@ -111,10 +111,10 @@ class IfExpr extends ConditionalExpr, TIfExpr {
}
}
private class If extends IfExpr, TIf {
private class IfReal extends IfExpr, TIfReal {
private Ruby::If g;
If() { this = TIf(g) }
IfReal() { this = TIfReal(g) }
final override Expr getCondition() { toGenerated(result) = g.getCondition() }
@@ -125,6 +125,18 @@ private class If extends IfExpr, TIf {
final override string toString() { result = "if ..." }
}
private class IfSynth extends IfExpr, TIfSynth {
IfSynth() { this = TIfSynth(_, _) }
final override Expr getCondition() { synthChild(this, 0, result) }
final override Stmt getThen() { synthChild(this, 1, result) }
final override Stmt getElse() { synthChild(this, 2, result) }
final override string toString() { result = "if ..." }
}
private class Elsif extends IfExpr, TElsif {
private Ruby::Elsif g;

View File

@@ -162,7 +162,8 @@ private module Cached {
} or
THereDoc(Ruby::HeredocBeginning g) or
TIdentifierMethodCall(Ruby::Identifier g) { isIdentifierMethodCall(g) } or
TIf(Ruby::If g) or
TIfReal(Ruby::If g) or
TIfSynth(AST::AstNode parent, int i) { mkSynthChild(IfKind(), parent, i) } or
TIfModifierExpr(Ruby::IfModifier g) or
TInClause(Ruby::InClause g) or
TInstanceVariableAccessReal(Ruby::InstanceVariable g, AST::InstanceVariable v) {
@@ -214,7 +215,8 @@ private module Cached {
TMulExprSynth(AST::AstNode parent, int i) { mkSynthChild(MulExprKind(), parent, i) } or
TNEExpr(Ruby::Binary g) { g instanceof @ruby_binary_bangequal } or
TNextStmt(Ruby::Next g) or
TNilLiteral(Ruby::Nil g) or
TNilLiteralReal(Ruby::Nil g) or
TNilLiteralSynth(AST::AstNode parent, int i) { mkSynthChild(NilLiteralKind(), parent, i) } or
TNoRegExpMatchExpr(Ruby::Binary g) { g instanceof @ruby_binary_bangtilde } or
TNotExpr(Ruby::Unary g) { g instanceof @ruby_unary_bang or g instanceof @ruby_unary_not } or
TOptionalParameter(Ruby::OptionalParameter g) or
@@ -347,35 +349,36 @@ private module Cached {
TFalseLiteral or TFile or TFindPattern or TFloatLiteral or TForExpr or TForwardParameter or
TForwardArgument or TGEExpr or TGTExpr or TGlobalVariableAccessReal or
THashKeySymbolLiteral or THashLiteral or THashPattern or THashSplatExpr or
THashSplatNilParameter or THashSplatParameter or THereDoc or TIdentifierMethodCall or TIf or
TIfModifierExpr or TInClause or TInstanceVariableAccessReal or TIntegerLiteralReal or
TKeywordParameter or TLEExpr or TLShiftExprReal or TLTExpr or TLambda or
TLeftAssignmentList or TLine or TLocalVariableAccessReal or TLogicalAndExprReal or
TLogicalOrExprReal or TMethod or TModuleDeclaration or TModuloExprReal or TMulExprReal or
TNEExpr or TNextStmt or TNilLiteral or TNoRegExpMatchExpr or TNotExpr or
TOptionalParameter or TPair or TParenthesizedExpr or TParenthesizedPattern or
TRShiftExprReal or TRangeLiteralReal or TRationalLiteral or TRedoStmt or TRegExpLiteral or
TRegExpMatchExpr or TRegularArrayLiteral or TRegularMethodCall or TRegularStringLiteral or
TRegularSuperCall or TRescueClause or TRescueModifierExpr or TRetryStmt or TReturnStmt or
TScopeResolutionConstantAccess or TSelfReal or TSimpleParameterReal or
TSimpleSymbolLiteral or TSingletonClass or TSingletonMethod or TSpaceshipExpr or
TSplatExprReal or TSplatParameter or TStringArrayLiteral or TStringConcatenation or
TStringEscapeSequenceComponent or TStringInterpolationComponent or TStringTextComponent or
TSubExprReal or TSubshellLiteral or TSymbolArrayLiteral or TTernaryIfExpr or TThen or
TTokenConstantAccess or TTokenMethodName or TTokenSuperCall or TToplevel or TTrueLiteral or
TUnaryMinusExpr or TUnaryPlusExpr or TUndefStmt or TUnlessExpr or TUnlessModifierExpr or
TUntilExpr or TUntilModifierExpr or TReferencePattern or TWhenClause or TWhileExpr or
THashSplatNilParameter or THashSplatParameter or THereDoc or TIdentifierMethodCall or
TIfReal or TIfModifierExpr or TInClause or TInstanceVariableAccessReal or
TIntegerLiteralReal or TKeywordParameter or TLEExpr or TLShiftExprReal or TLTExpr or
TLambda or TLeftAssignmentList or TLine or TLocalVariableAccessReal or
TLogicalAndExprReal or TLogicalOrExprReal or TMethod or TModuleDeclaration or
TModuloExprReal or TMulExprReal or TNEExpr or TNextStmt or TNilLiteralReal or
TNoRegExpMatchExpr or TNotExpr or TOptionalParameter or TPair or TParenthesizedExpr or
TParenthesizedPattern or TRShiftExprReal or TRangeLiteralReal or TRationalLiteral or
TRedoStmt or TRegExpLiteral or TRegExpMatchExpr or TRegularArrayLiteral or
TRegularMethodCall or TRegularStringLiteral or TRegularSuperCall or TRescueClause or
TRescueModifierExpr or TRetryStmt or TReturnStmt or TScopeResolutionConstantAccess or
TSelfReal or TSimpleParameterReal or TSimpleSymbolLiteral or TSingletonClass or
TSingletonMethod or TSpaceshipExpr or TSplatExprReal or TSplatParameter or
TStringArrayLiteral or TStringConcatenation or TStringEscapeSequenceComponent or
TStringInterpolationComponent or TStringTextComponent or TSubExprReal or TSubshellLiteral or
TSymbolArrayLiteral or TTernaryIfExpr or TThen or TTokenConstantAccess or
TTokenMethodName or TTokenSuperCall or TToplevel or TTrueLiteral or TUnaryMinusExpr or
TUnaryPlusExpr or TUndefStmt or TUnlessExpr or TUnlessModifierExpr or TUntilExpr or
TUntilModifierExpr or TReferencePattern or TWhenClause or TWhileExpr or
TWhileModifierExpr or TYieldCall;
class TAstNodeSynth =
TAddExprSynth or TAssignExprSynth or TBitwiseAndExprSynth or TBitwiseOrExprSynth or
TBitwiseXorExprSynth or TBraceBlockSynth or TClassVariableAccessSynth or
TConstantReadAccessSynth or TDivExprSynth or TExponentExprSynth or
TGlobalVariableAccessSynth or TInstanceVariableAccessSynth or TIntegerLiteralSynth or
TLShiftExprSynth or TLocalVariableAccessSynth or TLogicalAndExprSynth or
TLogicalOrExprSynth or TMethodCallSynth or TModuloExprSynth or TMulExprSynth or
TRShiftExprSynth or TRangeLiteralSynth or TSelfSynth or TSimpleParameterSynth or
TSplatExprSynth or TStmtSequenceSynth or TSubExprSynth;
TGlobalVariableAccessSynth or TIfSynth or TInstanceVariableAccessSynth or
TIntegerLiteralSynth or TLShiftExprSynth or TLocalVariableAccessSynth or
TLogicalAndExprSynth or TLogicalOrExprSynth or TMethodCallSynth or TModuloExprSynth or
TMulExprSynth or TNilLiteralSynth or TRShiftExprSynth or TRangeLiteralSynth or TSelfSynth or
TSimpleParameterSynth or TSplatExprSynth or TStmtSequenceSynth or TSubExprSynth;
/**
* Gets the underlying TreeSitter entity for a given AST node. This does not
@@ -457,7 +460,7 @@ private module Cached {
n = THereDoc(result) or
n = TIdentifierMethodCall(result) or
n = TIfModifierExpr(result) or
n = TIf(result) or
n = TIfReal(result) or
n = TInClause(result) or
n = TInstanceVariableAccessReal(result, _) or
n = TIntegerLiteralReal(result) or
@@ -477,7 +480,7 @@ private module Cached {
n = TMulExprReal(result) or
n = TNEExpr(result) or
n = TNextStmt(result) or
n = TNilLiteral(result) or
n = TNilLiteralReal(result) or
n = TNoRegExpMatchExpr(result) or
n = TNotExpr(result) or
n = TOptionalParameter(result) or
@@ -568,6 +571,8 @@ private module Cached {
or
result = TGlobalVariableAccessSynth(parent, i, _)
or
result = TIfSynth(parent, i)
or
result = TInstanceVariableAccessSynth(parent, i, _)
or
result = TIntegerLiteralSynth(parent, i, _)
@@ -586,6 +591,8 @@ private module Cached {
or
result = TMulExprSynth(parent, i)
or
result = TNilLiteralSynth(parent, i)
or
result = TRangeLiteralSynth(parent, i, _)
or
result = TRShiftExprSynth(parent, i)
@@ -672,6 +679,8 @@ class TControlExpr = TConditionalExpr or TCaseExpr or TCaseMatch or TLoop;
class TConditionalExpr =
TIfExpr or TUnlessExpr or TIfModifierExpr or TUnlessModifierExpr or TTernaryIfExpr;
class TIf = TIfReal or TIfSynth;
class TIfExpr = TIf or TElsif;
class TConditionalLoop = TWhileExpr or TUntilExpr or TWhileModifierExpr or TUntilModifierExpr;
@@ -695,6 +704,8 @@ class TStmtSequence =
class TBodyStmt = TBeginExpr or TModuleBase or TMethod or TLambda or TDoBlock or TSingletonMethod;
class TNilLiteral = TNilLiteralReal or TNilLiteralSynth;
class TLiteral =
TEncoding or TFile or TLine or TNumericLiteral or TNilLiteral or TBooleanLiteral or
TStringlikeLiteral or TCharacterLiteral or TArrayLiteral or THashLiteral or TRangeLiteral or

View File

@@ -111,12 +111,18 @@ class ComplexLiteralImpl extends Expr, TComplexLiteral {
}
}
class NilLiteralImpl extends Expr, TNilLiteral {
abstract class NilLiteralImpl extends Expr, TNilLiteral {
final override string toString() { result = "nil" }
}
class NilLiteralReal extends NilLiteralImpl, TNilLiteralReal {
private Ruby::Nil g;
NilLiteralImpl() { this = TNilLiteral(g) }
NilLiteralReal() { this = TNilLiteralReal(g) }
}
final override string toString() { result = g.getValue() }
class NilLiteralSynth extends NilLiteralImpl, TNilLiteralSynth {
NilLiteralSynth() { this = TNilLiteralSynth(_, _) }
}
abstract class BooleanLiteralImpl extends Expr, TBooleanLiteral {

View File

@@ -21,6 +21,7 @@ newtype SynthKind =
DivExprKind() or
ExponentExprKind() or
GlobalVariableAccessKind(GlobalVariable v) or
IfKind() or
InstanceVariableAccessKind(InstanceVariable v) or
IntegerLiteralKind(int i) { i in [-1000 .. 1000] } or
LShiftExprKind() or
@@ -33,6 +34,7 @@ newtype SynthKind =
} or
ModuloExprKind() or
MulExprKind() or
NilLiteralKind() or
RangeLiteralKind(boolean inclusive) { inclusive in [false, true] } or
RShiftExprKind() or
SimpleParameterKind() or
@@ -1083,3 +1085,123 @@ private module AnonymousBlockParameterSynth {
}
}
}
private module SafeNavigationCallDesugar {
/**
* ```rb
* receiver&.method(args) { ... }
* ```
* desugars to
*
* ```rb
* __synth__0 = receiver
* if nil == __synth__0 then nil else __synth__0.method(args) {...} end
* ```
*/
pragma[nomagic]
private predicate safeNavigationCallSynthesis(AstNode parent, int i, Child child) {
exists(RegularMethodCall call, LocalVariableAccessSynthKind local |
call.isSafeNavigationImpl() and
local = LocalVariableAccessSynthKind(TLocalVariableSynth(call.getReceiverImpl(), 0))
|
parent = call and
i = -1 and
child = SynthChild(StmtSequenceKind())
or
exists(TStmtSequenceSynth seq | seq = TStmtSequenceSynth(call, -1) |
parent = seq and
(
child = SynthChild(AssignExprKind()) and i = 0
or
child = SynthChild(IfKind()) and i = 1
)
or
parent = TAssignExprSynth(seq, 0) and
(
child = SynthChild(local) and
i = 0
or
child = childRef(call.getReceiverImpl()) and i = 1
)
or
exists(TIfSynth ifExpr | ifExpr = TIfSynth(seq, 1) |
parent = ifExpr and
(
child = SynthChild(MethodCallKind("==", false, 2)) and
i = 0
or
child = SynthChild(NilLiteralKind()) and i = 1
or
child =
SynthChild(MethodCallKind(call.getMethodNameImpl(), false,
call.getNumberOfArgumentsImpl())) and
i = 2
)
or
parent = TMethodCallSynth(ifExpr, 0, _, _, _) and
(
child = SynthChild(NilLiteralKind()) and i = 0
or
child = SynthChild(local) and
i = 1
)
or
parent = TMethodCallSynth(ifExpr, 2, _, _, _) and
(
i = 0 and
child = SynthChild(local)
or
child = childRef(call.getArgumentImpl(i - 1))
or
child = childRef(call.getBlockImpl()) and i = -2
)
)
)
)
}
private class SafeNavigationCallSynthesis extends Synthesis {
final override predicate child(AstNode parent, int i, Child child) {
safeNavigationCallSynthesis(parent, i, child)
}
final override predicate methodCall(string name, boolean setter, int arity) {
exists(RegularMethodCall call |
call.isSafeNavigationImpl() and
name = call.getMethodNameImpl() and
setter = false and
arity = call.getNumberOfArgumentsImpl()
)
or
name = "==" and setter = false and arity = 2
}
final override predicate localVariable(AstNode n, int i) {
i = 0 and n = any(RegularMethodCall c | c.isSafeNavigationImpl()).getReceiverImpl()
}
override predicate location(AstNode n, Location l) {
exists(RegularMethodCall call, StmtSequence seq |
call.isSafeNavigationImpl() and seq = call.getDesugared()
|
n = seq.getStmt(0) and
hasLocation(call.getReceiverImpl(), l)
or
n = seq.getStmt(1) and
l = toGenerated(call).(Ruby::Call).getOperator().getLocation()
or
n = seq.getStmt(1).(IfExpr).getCondition().(MethodCall).getArgument(0) and
hasLocation(call.getReceiverImpl(), l)
or
n = seq.getStmt(1).(IfExpr).getThen() and
hasLocation(call.getReceiverImpl(), l)
or
n = seq.getStmt(1).(IfExpr).getElse() and
hasLocation(call, l)
or
n = seq.getStmt(1).(IfExpr).getElse().(MethodCall).getReceiver() and
hasLocation(call.getReceiverImpl(), l)
)
}
}
}

View File

@@ -279,6 +279,19 @@ calls/calls.rb:
# 340| getArgument: [IntegerLiteral] 4
# 340| getArgument: [IntegerLiteral] 5
# 340| getArgument: [IntegerLiteral] 6
# 362| [MethodCall] call to empty?
# 362| getDesugared: [StmtSequence] ...
# 362| getStmt: [AssignExpr] ... = ...
# 362| getAnOperand/getRightOperand: [MethodCall] call to list
# 362| getReceiver: [SelfVariableAccess] self
# 362| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
# 362| getStmt: [IfExpr] if ...
# 362| getBranch/getElse: [MethodCall] call to empty?
# 362| getReceiver: [LocalVariableAccess] __synth__0__1
# 362| getBranch/getThen: [NilLiteral] nil
# 362| getCondition: [MethodCall] call to ==
# 362| getArgument: [LocalVariableAccess] __synth__0__1
# 362| getReceiver: [NilLiteral] nil
control/cases.rb:
# 90| [ArrayLiteral] %w(...)
# 90| getDesugared: [MethodCall] call to []

View File

@@ -74,6 +74,8 @@ exprValue
| calls/calls.rb:346:8:346:9 | 42 | 42 | int |
| calls/calls.rb:347:5:347:5 | :X | :X | symbol |
| calls/calls.rb:350:5:350:5 | 1 | 1 | int |
| calls/calls.rb:362:1:362:4 | nil | nil | nil |
| calls/calls.rb:362:5:362:6 | nil | nil | nil |
| constants/constants.rb:3:19:3:27 | "const_a" | const_a | string |
| constants/constants.rb:6:15:6:23 | "const_b" | const_b | string |
| constants/constants.rb:17:12:17:18 | "Hello" | Hello | string |
@@ -963,6 +965,8 @@ exprCfgNodeValue
| calls/calls.rb:346:8:346:9 | 42 | 42 | int |
| calls/calls.rb:347:5:347:5 | :X | :X | symbol |
| calls/calls.rb:350:5:350:5 | 1 | 1 | int |
| calls/calls.rb:362:1:362:4 | nil | nil | nil |
| calls/calls.rb:362:5:362:6 | nil | nil | nil |
| constants/constants.rb:3:19:3:27 | "const_a" | const_a | string |
| constants/constants.rb:6:15:6:23 | "const_b" | const_b | string |
| constants/constants.rb:17:12:17:18 | "Hello" | Hello | string |

View File

@@ -114,6 +114,7 @@ callsWithArguments
| calls.rb:346:1:346:10 | call to foo | foo | 0 | calls.rb:346:5:346:9 | Pair |
| calls.rb:347:1:347:7 | call to foo | foo | 0 | calls.rb:347:5:347:6 | Pair |
| calls.rb:352:13:352:17 | call to foo | foo | 0 | calls.rb:352:17:352:17 | x |
| calls.rb:362:5:362:6 | call to == | == | 0 | calls.rb:362:1:362:4 | __synth__0__1 |
callsWithReceiver
| calls.rb:2:1:2:5 | call to foo | calls.rb:2:1:2:5 | self |
| calls.rb:5:1:5:10 | call to bar | calls.rb:5:1:5:3 | Foo |
@@ -375,7 +376,9 @@ callsWithReceiver
| calls.rb:361:1:361:4 | call to list | calls.rb:361:1:361:4 | self |
| calls.rb:361:1:361:11 | call to empty? | calls.rb:361:1:361:4 | call to list |
| calls.rb:362:1:362:4 | call to list | calls.rb:362:1:362:4 | self |
| calls.rb:362:1:362:12 | call to empty? | calls.rb:362:1:362:4 | __synth__0__1 |
| calls.rb:362:1:362:12 | call to empty? | calls.rb:362:1:362:4 | call to list |
| calls.rb:362:5:362:6 | call to == | calls.rb:362:5:362:6 | nil |
| calls.rb:363:1:363:4 | call to list | calls.rb:363:1:363:4 | self |
| calls.rb:363:1:363:12 | call to empty? | calls.rb:363:1:363:4 | call to list |
callsWithBlock