forked from sorbet/sorbet
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
-p autogen-subclasses
to print all descendants of specified sup…
…erclasses (sorbet#1040) * Add `-p autogen-subclasses` to print all superclasses and their subclasses Fix CLI help test Minor refactors Allow passes that are run after the one you need Enable printing subclasses to output file Add path-exclude filters Minor refactors, remove unnecessary membership checks Comment out filepath-excludes to see if affects perf Ignore definitions that don't define classes Include Definition::type in `-p subclasses` output `InheritedClassesStep` traverses inheritance through modules (to find "class Foo which includes module Bar which includes module Baz" as a descendant of Baz) but does not include modules in SUBCLASSES (i.e., does not list "Bar" as a descendant of Baz). Print Definition type as human-readable string Ignore pay-server files we don't care about ...but don't ignore the Sorbet CLI test files Move path exclusion to the right spot * Add options for ignoring files and selecting parent classes * Generate the equivalent of DescendantsMap * Minor refactors * Add and test manual overrides * Test file ignoring * Remove extraneous `typeAsStringView` * Simplify options handling autogen-subclasses doesn't actually need resolver, just namer. * Move autogen-subclasses logic out of realmain.cc * Don't store capturing lambdas * Fix test * Return stuff from ParsedFile methods instead of using &out refs * Use UnorderedMap and UnorderedSet (i.e., abseil flat hashes) * Prefer emplace_back over push_back * More `const` * Change method names to actions instead of nouns * Even more `const` * Fix CLI test for `--help` * Re-run clang format * Remove unnecessary `const core::Context` * Use `shortName` instead of `show` * Pull AutogenSubclasses stuff into a new autogen::Subclasses class * Move subclasses-related stuff to new subclasses.{h, cc} * Destructure `Entry`s for better readability * Cargo-cult a modded `isFileIgnored` to avoid abusing the original * Check UnorderedMap membership more idiomatically * Fix --autogen-subclasses-ignore test * Test some edge cases 1) User asking for class that doesn't exist 2) User asking for class that is never subclassed * Refactor descendantsOf to return a value * Kill `maybeInsertChild`, which didn't need to be it's own method * Make `serializeSubclassMap` private * Add some comments * Remove some TODOs * Add trailing comma to BUILD file * Split up `matchIsFolderOrFile` into multiple methods
- Loading branch information
Showing
18 changed files
with
404 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
#include "main/autogen/subclasses.h" | ||
#include "common/FileOps.h" | ||
|
||
using namespace std; | ||
namespace sorbet::autogen { | ||
|
||
// Analogue of sorbet::FileOps::isFileIgnored that doesn't take a basePath, since | ||
// we don't need one here, and using FileOps' version meant passing some weird, | ||
// hard-to-understand arguments to mimic how other callers use it. | ||
bool Subclasses::isFileIgnored(const std::string &path, const std::vector<std::string> &absoluteIgnorePatterns, | ||
const std::vector<std::string> &relativeIgnorePatterns) { | ||
for (auto &p : absoluteIgnorePatterns) { | ||
if (path.substr(0, p.length()) == p && | ||
(sorbet::FileOps::isFile(path, p, 0) || sorbet::FileOps::isFolder(path, p, 0))) { | ||
return true; | ||
} | ||
} | ||
for (auto &p : relativeIgnorePatterns) { | ||
// See if /pattern is in string, and that it matches a whole folder or file name. | ||
int pos = 0; | ||
while (true) { | ||
pos = path.find(p, pos); | ||
if (pos == string_view::npos) { | ||
break; | ||
} else if (sorbet::FileOps::isFile(path, p, pos) || sorbet::FileOps::isFolder(path, p, pos)) { | ||
return true; | ||
} | ||
pos += p.length(); | ||
} | ||
} | ||
return false; | ||
}; | ||
|
||
// Get all subclasses defined in a particular ParsedFile | ||
optional<Subclasses::Map> Subclasses::listAllSubclasses(core::Context ctx, ParsedFile &pf, | ||
const vector<string> &absoluteIgnorePatterns, | ||
const vector<string> &relativeIgnorePatterns) { | ||
// Prepend "/" because absoluteIgnorePatterns and relativeIgnorePatterns are always "/"-prefixed | ||
if (isFileIgnored(fmt::format("/{}", pf.path), absoluteIgnorePatterns, relativeIgnorePatterns)) { | ||
return nullopt; | ||
} | ||
|
||
Subclasses::Map out; | ||
for (const Reference &ref : pf.refs) { | ||
DefinitionRef defn = ref.parent_of; | ||
if (!defn.exists()) { | ||
// This is just a random constant reference and doesn't | ||
// define a Child < Parent relationship. | ||
continue; | ||
} | ||
|
||
// Get fully-qualified parent name as string | ||
string parentName = | ||
fmt::format("{}", fmt::map_join(ref.resolved, "::", [&ctx](const core::NameRef &nm) -> string { | ||
return nm.data(ctx)->show(ctx); | ||
})); | ||
|
||
// Add child class to the set identified by its parent | ||
string childName = fmt::format( | ||
"{}", fmt::map_join(pf.showFullName(ctx, defn), | ||
"::", [&ctx](const core::NameRef &nm) -> string { return nm.data(ctx)->show(ctx); })); | ||
|
||
out[parentName].insert(make_pair(childName, defn.data(pf).type)); | ||
} | ||
|
||
return out; | ||
} | ||
|
||
// Generate all descendants of a parent class | ||
// Recursively walks `childMap`, which stores the IMMEDIATE children of subclassed class. | ||
optional<Subclasses::Entries> Subclasses::descendantsOf(const Subclasses::Map &childMap, const string &parentName) { | ||
auto fnd = childMap.find(parentName); | ||
if (fnd == childMap.end()) { | ||
return nullopt; | ||
} | ||
const Subclasses::Entries children = fnd->second; | ||
|
||
Subclasses::Entries out; | ||
out.insert(children.begin(), children.end()); | ||
for (const auto &[name, _type] : children) { | ||
auto descendants = Subclasses::descendantsOf(childMap, name); | ||
if (descendants) { | ||
out.insert(descendants->begin(), descendants->end()); | ||
} | ||
} | ||
|
||
return out; | ||
} | ||
|
||
// Manually patch the child map to account for inheritance that happens at runtime `self.included` | ||
// Please do not add to this list. | ||
void Subclasses::patchChildMap(Subclasses::Map &childMap) { | ||
childMap["Opus::SafeMachine"].insert(childMap["Opus::Risk::Model::Mixins::RiskSafeMachine"].begin(), | ||
childMap["Opus::Risk::Model::Mixins::RiskSafeMachine"].end()); | ||
|
||
childMap["Chalk::SafeMachine"].insert(childMap["Opus::SafeMachine"].begin(), childMap["Opus::SafeMachine"].end()); | ||
|
||
childMap["Chalk::ODM::Model"].insert(make_pair("Chalk::ODM::Private::Lock", autogen::Definition::Type::Class)); | ||
} | ||
|
||
vector<string> Subclasses::serializeSubclassMap(const Subclasses::Map &descendantsMap, | ||
const vector<string> &parentNames) { | ||
vector<string> descendantsMapSerialized; | ||
|
||
for (const string &parentName : parentNames) { | ||
auto fnd = descendantsMap.find(parentName); | ||
if (fnd == descendantsMap.end()) { | ||
continue; | ||
} | ||
const Subclasses::Entries children = fnd->second; | ||
|
||
descendantsMapSerialized.emplace_back(parentName); | ||
|
||
vector<string> serializedChildren; | ||
for (const auto &[name, type] : children) { | ||
// Ignore Modules | ||
if (type != autogen::Definition::Type::Class) { | ||
continue; | ||
} | ||
serializedChildren.emplace_back(fmt::format(" {}", name)); | ||
} | ||
|
||
fast_sort(serializedChildren); | ||
descendantsMapSerialized.insert(descendantsMapSerialized.end(), make_move_iterator(serializedChildren.begin()), | ||
make_move_iterator(serializedChildren.end())); | ||
} | ||
|
||
return descendantsMapSerialized; | ||
} | ||
|
||
// Generate a list of strings representing the descendants of a given list of parent classes | ||
// | ||
// e.g. | ||
// Parent1 | ||
// Child1 | ||
// Parent2 | ||
// Child2 | ||
// Child3 | ||
// | ||
// This effectively replaces pay-server's `DescendantsMap` in `InheritedClassesStep` with a much | ||
// faster implementation. | ||
vector<string> Subclasses::genDescendantsMap(Subclasses::Map &childMap, vector<string> &parentNames) { | ||
Subclasses::patchChildMap(childMap); | ||
|
||
// Generate descendants for each passed-in superclass | ||
fast_sort(parentNames); | ||
Subclasses::Map descendantsMap; | ||
for (const string &parentName : parentNames) { | ||
// Skip parents that the user asked for but which don't | ||
// exist or are never subclassed. | ||
auto fnd = childMap.find(parentName); | ||
if (fnd == childMap.end()) { | ||
continue; | ||
} | ||
|
||
auto descendants = Subclasses::descendantsOf(childMap, parentName); | ||
if (!descendants) { | ||
descendantsMap[parentName]; | ||
} | ||
|
||
descendantsMap.emplace(parentName, *descendants); | ||
} | ||
|
||
return Subclasses::serializeSubclassMap(descendantsMap, parentNames); | ||
}; | ||
|
||
} // namespace sorbet::autogen |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
#ifndef AUTOGEN_SUBCLASSES_H | ||
#define AUTOGEN_SUBCLASSES_H | ||
#include "common/common.h" | ||
#include "main/autogen/autogen.h" | ||
|
||
namespace sorbet::autogen { | ||
|
||
class Subclasses final { | ||
public: | ||
typedef std::pair<std::string, Definition::Type> Entry; | ||
typedef UnorderedSet<Entry> Entries; | ||
typedef UnorderedMap<std::string, Entries> Map; | ||
|
||
static std::optional<Subclasses::Map> listAllSubclasses(core::Context ctx, ParsedFile &pf, | ||
const std::vector<std::string> &absoluteIgnorePatterns, | ||
const std::vector<std::string> &relativeIgnorePatterns); | ||
static std::vector<std::string> genDescendantsMap(Subclasses::Map &childMap, std::vector<std::string> &parentNames); | ||
|
||
private: | ||
static void patchChildMap(Subclasses::Map &childMap); | ||
static bool isFileIgnored(const std::string &path, const std::vector<std::string> &absoluteIgnorePatterns, | ||
const std::vector<std::string> &relativeIgnorePatterns); | ||
static std::optional<Entries> descendantsOf(const Subclasses::Map &childMap, const std::string &parents); | ||
static std::vector<std::string> serializeSubclassMap(const Subclasses::Map &descendantsMap, | ||
const std::vector<std::string> &parentNames); | ||
}; | ||
|
||
} // namespace sorbet::autogen | ||
#endif // AUTOGEN_SUBCLASSES_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.