forked from sorbet/sorbet
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDelegate.cc
130 lines (111 loc) · 4.19 KB
/
Delegate.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#include "rewriter/Delegate.h"
#include "ast/Helpers.h"
#include "core/GlobalState.h"
#include "rewriter/Util.h"
#include <optional>
using namespace std;
namespace sorbet::rewriter {
static bool literalSymbolEqual(const core::GlobalState &gs, const ast::ExpressionPtr &node, core::NameRef name) {
if (auto lit = ast::cast_tree<ast::Literal>(node)) {
return lit->isSymbol(gs) && lit->asSymbol(gs) == name;
}
return false;
}
static bool isLiteralTrue(const core::GlobalState &gs, const ast::ExpressionPtr &node) {
if (auto lit = ast::cast_tree<ast::Literal>(node)) {
return lit->isTrue(gs);
}
return false;
}
static optional<core::NameRef> stringOrSymbolNameRef(const core::GlobalState &gs, const ast::ExpressionPtr &node) {
auto lit = ast::cast_tree<ast::Literal>(node);
if (!lit) {
return nullopt;
}
if (lit->isSymbol(gs)) {
return lit->asSymbol(gs);
} else if (lit->isString(gs)) {
return lit->asString(gs);
} else {
return nullopt;
}
}
vector<ast::ExpressionPtr> Delegate::run(core::MutableContext ctx, const ast::Send *send) {
vector<ast::ExpressionPtr> empty;
auto loc = send->loc;
if (send->fun != core::Names::delegate()) {
return empty;
}
if (send->args.empty()) {
return empty;
}
auto optionsTree = ASTUtil::mkKwArgsHash(send);
auto options = ast::cast_tree<ast::Hash>(optionsTree);
if (!options) {
return empty;
}
if (send->numPosArgs == 0) {
// there has to be at least one positional argument
return empty;
}
ast::ExpressionPtr const *prefixNode = nullptr;
core::NameRef toName;
{
optional<core::NameRef> to;
for (int i = 0; i < options->keys.size(); i++) {
if (literalSymbolEqual(ctx, options->keys[i], core::Names::to())) {
to = stringOrSymbolNameRef(ctx, options->values[i]);
}
if (literalSymbolEqual(ctx, options->keys[i], core::Names::prefix())) {
prefixNode = &options->values[i];
}
}
if (to) {
toName = *to;
} else {
return empty;
}
}
string beforeUnderscore;
bool useToAsPrefix = false;
if (prefixNode) {
if (isLiteralTrue(ctx, *prefixNode)) {
beforeUnderscore = toName.shortName(ctx);
useToAsPrefix = true;
} else if (auto result = stringOrSymbolNameRef(ctx, *prefixNode)) {
beforeUnderscore = result->shortName(ctx);
} else {
return empty;
}
}
vector<ast::ExpressionPtr> methodStubs;
for (int i = 0; i < send->numPosArgs; i++) {
auto *lit = ast::cast_tree<ast::Literal>(send->args[i]);
if (!lit || !lit->isSymbol(ctx)) {
return empty;
}
core::NameRef methodName;
if (prefixNode) {
if (useToAsPrefix && (beforeUnderscore.empty() || beforeUnderscore[0] == '@')) {
// Active Support raises at runtime for these cases
return empty;
}
methodName =
ctx.state.enterNameUTF8(fmt::format("{}_{}", beforeUnderscore, lit->asSymbol(ctx).shortName(ctx)));
} else {
methodName = lit->asSymbol(ctx);
}
// sig {params(arg0: T.untyped, blk: Proc).returns(T.untyped)}
auto sigArgs = ast::MK::SendArgs(ast::MK::Symbol(loc, core::Names::arg0()), ast::MK::Untyped(loc),
ast::MK::Symbol(loc, core::Names::blkArg()),
ast::MK::Nilable(loc, ast::MK::Constant(loc, core::Symbols::Proc())));
methodStubs.push_back(ast::MK::Sig(loc, std::move(sigArgs), ast::MK::Untyped(loc)));
// def $methodName(*arg0, &blk); end
ast::MethodDef::ARGS_store args;
args.emplace_back(ast::MK::RestArg(loc, ast::MK::Local(loc, core::Names::arg0())));
args.emplace_back(ast::make_expression<ast::BlockArg>(loc, ast::MK::Local(loc, core::Names::blkArg())));
methodStubs.push_back(ast::MK::SyntheticMethod(loc, loc, methodName, std::move(args), ast::MK::EmptyTree()));
}
return methodStubs;
}
} // namespace sorbet::rewriter