Skip to content

Commit d53a88d

Browse files
committed
Stub out a tool that runs a script over a Swift AST.
The tool is currently hard-coded to find functions in the SwiftUI library that take parameters of type `(...) -> T` where `T: View` but where the parameter isn't annotated with `@ViewBuilder`. The long-term vision here, of course, is that this reads and interprets a script file, but that's quite a bit more work (especially to generate a million bindings to the AST). In the meantime, I think having a functional harness that people familiar with the C++ API can easily hack on to make their own tools is still pretty useful. The harness does try to open a script file and lex the first token of it, because that's exactly as far as I got before deciding to hard-code the query I wanted. Since this input is otherwise ignored, you can just point the tool at any old `.swift` file (or just an empty file) and it'll be fine.
1 parent 48024ec commit d53a88d

8 files changed

+538
-0
lines changed

tools/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ add_swift_tool_subdirectory(sil-nm)
2828
add_swift_tool_subdirectory(sil-passpipeline-dumper)
2929
add_swift_tool_subdirectory(swift-llvm-opt)
3030
add_swift_tool_subdirectory(swift-api-digester)
31+
add_swift_tool_subdirectory(swift-ast-script)
3132
add_swift_tool_subdirectory(swift-syntax-test)
3233
add_swift_tool_subdirectory(swift-refactor)
3334
if(SWIFT_BUILD_SYNTAXPARSERLIB)

tools/swift-ast-script/ASTScript.h

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//===--- ASTScript.h - AST script type --------------------------*- C++ -*-===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
//
13+
// The AST for a swift-ast-script script.
14+
//
15+
//===----------------------------------------------------------------------===//
16+
17+
#ifndef SWIFT_SCRIPTING_ASTSCRIPT_H
18+
#define SWIFT_SCRIPTING_ASTSCRIPT_H
19+
20+
#include "swift/Basic/LLVM.h"
21+
22+
namespace swift {
23+
namespace scripting {
24+
class ASTScriptConfiguration;
25+
26+
class ASTScript {
27+
ASTScriptConfiguration &Config;
28+
29+
public:
30+
ASTScript(ASTScriptConfiguration &config) : Config(config) {}
31+
32+
static std::unique_ptr<ASTScript> parse(ASTScriptConfiguration &config);
33+
34+
bool execute() const;
35+
};
36+
37+
}
38+
}
39+
40+
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
//===--- ASTScriptConfiguration.cpp ---------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
///
13+
/// Configuration parsing for an AST script.
14+
///
15+
//===----------------------------------------------------------------------===//
16+
17+
#include "ASTScriptConfiguration.h"
18+
19+
#include "swift/Basic/QuotedString.h"
20+
#include "llvm/ADT/ArrayRef.h"
21+
#include "llvm/Support/raw_ostream.h"
22+
23+
using namespace swift;
24+
using namespace scripting;
25+
26+
std::unique_ptr<ASTScriptConfiguration>
27+
ASTScriptConfiguration::parse(CompilerInstance &compiler,
28+
ArrayRef<const char *> args) {
29+
bool hadError = false;
30+
31+
std::unique_ptr<ASTScriptConfiguration> result(
32+
new ASTScriptConfiguration(compiler));
33+
34+
#define emitError(WHAT) do { \
35+
llvm::errs() << "error: " << WHAT << "\n"; \
36+
hadError = true; \
37+
} while (0)
38+
39+
auto popArg = [&](const char *what) -> StringRef {
40+
// Gracefully handle the case of running out of arguments.
41+
if (args.empty()) {
42+
assert(what && "expected explanation here!");
43+
emitError(what);
44+
return "";
45+
}
46+
47+
auto arg = args.front();
48+
args = args.slice(1);
49+
return arg;
50+
};
51+
52+
auto setScriptFile = [&](StringRef filename) {
53+
if (!result->ScriptFile.empty()) {
54+
emitError("multiple script files ("
55+
<< QuotedString(result->ScriptFile)
56+
<< ", "
57+
<< QuotedString(filename)
58+
<< ")");
59+
}
60+
result->ScriptFile = filename;
61+
};
62+
63+
// Parse the arguments.
64+
while (!hadError && !args.empty()) {
65+
StringRef arg = popArg(nullptr);
66+
if (!arg.startswith("-")) {
67+
setScriptFile(arg);
68+
} else if (arg == "-f") {
69+
StringRef filename = popArg("expected path after -f");
70+
if (!hadError)
71+
setScriptFile(filename);
72+
} else if (arg == "-h" || arg == "--help") {
73+
llvm::errs()
74+
<< "usage: swift-ast-script <script-args> -- <compiler-args\n"
75+
"accepted script arguments:\n"
76+
" <filename>\n"
77+
" -f <filename>\n"
78+
" Specify the file to use as the script; required argument\n";
79+
hadError = true;
80+
} else {
81+
emitError("unknown argument " << QuotedString(arg));
82+
}
83+
}
84+
85+
// Check well-formedness.
86+
if (!hadError) {
87+
if (result->ScriptFile.empty()) {
88+
emitError("script file is required");
89+
}
90+
}
91+
92+
if (hadError)
93+
result.reset();
94+
return result;
95+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//===--- ASTScriptConfiguration.h - AST script configuration ----*- C++ -*-===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
//
13+
// Types for configuring an AST script invocation.
14+
//
15+
//===----------------------------------------------------------------------===//
16+
17+
#ifndef SWIFT_SCRIPTING_ASTSCRIPTCONFIGURATION_H
18+
#define SWIFT_SCRIPTING_ASTSCRIPTCONFIGURATION_H
19+
20+
#include "swift/Basic/LLVM.h"
21+
#include "llvm/ADT/StringRef.h"
22+
23+
namespace swift {
24+
class CompilerInstance;
25+
26+
namespace scripting {
27+
28+
/// A configuration for working with an ASTScript.
29+
class ASTScriptConfiguration {
30+
ASTScriptConfiguration(CompilerInstance &compiler) : Compiler(compiler) {}
31+
public:
32+
CompilerInstance &Compiler;
33+
StringRef ScriptFile;
34+
35+
/// Attempt to parse this configuration.
36+
///
37+
/// Returns null if there's a problem.
38+
static std::unique_ptr<ASTScriptConfiguration>
39+
parse(CompilerInstance &compiler, ArrayRef<const char *> args);
40+
};
41+
42+
}
43+
}
44+
45+
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
//===--- ASTScriptEvaluator.cpp -------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
///
13+
/// AST script evaluation.
14+
///
15+
//===----------------------------------------------------------------------===//
16+
17+
#include "ASTScript.h"
18+
#include "ASTScriptConfiguration.h"
19+
20+
#include "swift/AST/ASTMangler.h"
21+
#include "swift/AST/ASTWalker.h"
22+
#include "swift/AST/Decl.h"
23+
#include "swift/AST/NameLookup.h"
24+
#include "swift/Frontend/Frontend.h"
25+
26+
using namespace swift;
27+
using namespace scripting;
28+
29+
namespace {
30+
31+
class ASTScriptWalker : public ASTWalker {
32+
const ASTScript &Script;
33+
ProtocolDecl *ViewProtocol;
34+
35+
public:
36+
ASTScriptWalker(const ASTScript &script, ProtocolDecl *viewProtocol)
37+
: Script(script), ViewProtocol(viewProtocol) {}
38+
39+
bool walkToDeclPre(Decl *D) override {
40+
visit(D);
41+
return true;
42+
}
43+
44+
void visit(const Decl *D) {
45+
auto fn = dyn_cast<AbstractFunctionDecl>(D);
46+
if (!fn) return;
47+
48+
// Suppress warnings.
49+
(void) Script;
50+
51+
for (auto param : *fn->getParameters()) {
52+
// The parameter must have function type.
53+
auto paramType = param->getInterfaceType();
54+
auto paramFnType = paramType->getAs<FunctionType>();
55+
if (!paramFnType) continue;
56+
57+
// The parameter function must return a type parameter that
58+
// conforms to SwiftUI.View.
59+
auto paramResultType = paramFnType->getResult();
60+
if (!paramResultType->isTypeParameter()) continue;
61+
auto sig = fn->getGenericSignature();
62+
if (!sig->conformsToProtocol(paramResultType, ViewProtocol)) continue;
63+
64+
// The parameter must not be a @ViewBuilder parameter.
65+
if (param->getFunctionBuilderType()) continue;
66+
67+
// Print the function.
68+
printDecl(fn);
69+
}
70+
}
71+
72+
void printDecl(const ValueDecl *decl) {
73+
// FIXME: there's got to be some better way to print an exact reference
74+
// to a declaration, including its context.
75+
printDecl(llvm::outs(), decl);
76+
llvm::outs() << "\n";
77+
}
78+
79+
void printDecl(llvm::raw_ostream &out, const ValueDecl *decl) {
80+
if (auto accessor = dyn_cast<AccessorDecl>(decl)) {
81+
printDecl(out, accessor->getStorage());
82+
out << ".(accessor)";
83+
} else {
84+
printDeclContext(out, decl->getDeclContext());
85+
out << decl->getFullName();
86+
}
87+
}
88+
89+
void printDeclContext(llvm::raw_ostream &out, const DeclContext *dc) {
90+
if (!dc) return;
91+
if (auto module = dyn_cast<ModuleDecl>(dc)) {
92+
out << module->getName() << ".";
93+
} else if (auto extension = dyn_cast<ExtensionDecl>(dc)) {
94+
printDecl(out, extension->getExtendedNominal());
95+
out << ".";
96+
} else if (auto decl = dyn_cast_or_null<ValueDecl>(dc->getAsDecl())) {
97+
printDecl(out, decl);
98+
out << ".";
99+
} else {
100+
printDeclContext(out, dc->getParent());
101+
}
102+
}
103+
};
104+
105+
}
106+
107+
bool ASTScript::execute() const {
108+
// Hardcode the actual query we want to execute here.
109+
110+
auto &ctx = Config.Compiler.getASTContext();
111+
auto swiftUI = ctx.getLoadedModule(ctx.getIdentifier("SwiftUI"));
112+
if (!swiftUI) {
113+
llvm::errs() << "error: SwiftUI module not loaded\n";
114+
return true;
115+
}
116+
117+
UnqualifiedLookup viewLookup(ctx.getIdentifier("View"), swiftUI, nullptr);
118+
auto viewProtocol =
119+
dyn_cast_or_null<ProtocolDecl>(viewLookup.getSingleTypeResult());
120+
if (!viewProtocol) {
121+
llvm::errs() << "error: couldn't find SwiftUI.View protocol\n";
122+
return true;
123+
}
124+
125+
SmallVector<Decl*, 128> topLevelDecls;
126+
swiftUI->getTopLevelDecls(topLevelDecls);
127+
128+
llvm::errs() << "found " << topLevelDecls.size() << " top-level declarations\n";
129+
130+
ASTScriptWalker walker(*this, viewProtocol);
131+
for (auto decl : topLevelDecls) {
132+
decl->walk(walker);
133+
}
134+
135+
return false;
136+
}

0 commit comments

Comments
 (0)