Skip to content

Commit 15bc556

Browse files
authored
--gen-packages mode for imports (sorbet#9080)
* --gen-packages * track package references * combined autocorrect * docs * stop after resolving * test * switch to multiplexJob instead of Parallel::iterate (which uses multiplexJobWait) * one more early continue and de-indent * only run package reference tracking when the flag is enabled * don't allow both --gen-packages and --lsp * Speed up untracking package references On the slow path, the package references sets will start off empty, so looping over them should be fast. However, because calls to untrack and track were interleaved, the untrack calls would encounter the entries added by the track calls and do extra work that's not needed (since the track calls would a file A, but the untrack call would be for some other file B). By doing all the untracking upfront, we can make it faster. * test for invalid CLI options * fix non determinism in test * keep global state param const and pass in nonConstPackageDB instead * store unique_ptr instead of optional in the queue * Revert "store unique_ptr instead of optional in the queue" This reverts commit 02d2c57.
1 parent f4f0560 commit 15bc556

29 files changed

+438
-53
lines changed

core/GlobalState.cc

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2073,9 +2073,8 @@ unique_ptr<GlobalState> GlobalState::copyForIndexThread(
20732073
const vector<string> &extraPackageFilesDirectorySlashDeprecatedPrefixes,
20742074
const vector<string> &extraPackageFilesDirectorySlashPrefixes,
20752075
const vector<string> &packageSkipRBIExportEnforcementDirs, const vector<string> &allowRelaxedPackagerChecksFor,
2076-
const vector<string> &packagerLayers, string errorHint) const {
2076+
const vector<string> &packagerLayers, string errorHint, bool genPackages) const {
20772077
ENFORCE(fileTableFrozen);
2078-
20792078
auto result = make_unique<GlobalState>(this->errorQueue, this->epochManager);
20802079

20812080
result->initEmpty();
@@ -2094,7 +2093,7 @@ unique_ptr<GlobalState> GlobalState::copyForIndexThread(
20942093
result->setPackagerOptions(extraPackageFilesDirectoryUnderscorePrefixes,
20952094
extraPackageFilesDirectorySlashDeprecatedPrefixes,
20962095
extraPackageFilesDirectorySlashPrefixes, packageSkipRBIExportEnforcementDirs,
2097-
allowRelaxedPackagerChecksFor, packagerLayers, errorHint);
2096+
allowRelaxedPackagerChecksFor, packagerLayers, errorHint, genPackages);
20982097
}
20992098

21002099
return result;
@@ -2105,7 +2104,7 @@ unique_ptr<GlobalState> GlobalState::copyForLSPTypechecker(
21052104
const vector<string> &extraPackageFilesDirectorySlashDeprecatedPrefixes,
21062105
const vector<string> &extraPackageFilesDirectorySlashPrefixes,
21072106
const vector<string> &packageSkipRBIExportEnforcementDirs, const vector<string> &allowRelaxedPackagerChecksFor,
2108-
const vector<string> &packagerLayers, string errorHint) const {
2107+
const vector<string> &packagerLayers, string errorHint, bool genPackages) const {
21092108
auto result = make_unique<GlobalState>(this->errorQueue, this->epochManager);
21102109

21112110
result->initEmpty();
@@ -2121,7 +2120,7 @@ unique_ptr<GlobalState> GlobalState::copyForLSPTypechecker(
21212120
result->setPackagerOptions(extraPackageFilesDirectoryUnderscorePrefixes,
21222121
extraPackageFilesDirectorySlashDeprecatedPrefixes,
21232122
extraPackageFilesDirectorySlashPrefixes, packageSkipRBIExportEnforcementDirs,
2124-
allowRelaxedPackagerChecksFor, packagerLayers, errorHint);
2123+
allowRelaxedPackagerChecksFor, packagerLayers, errorHint, genPackages);
21252124
}
21262125

21272126
return result;
@@ -2132,7 +2131,7 @@ GlobalState::copyForSlowPath(const vector<string> &extraPackageFilesDirectoryUnd
21322131
const vector<string> &extraPackageFilesDirectorySlashPrefixes,
21332132
const vector<string> &packageSkipRBIExportEnforcementDirs,
21342133
const vector<string> &allowRelaxedPackagerChecksFor, const vector<string> &packagerLayers,
2135-
string errorHint) const {
2134+
string errorHint, bool genPackages) const {
21362135
auto result = make_unique<GlobalState>(this->errorQueue, this->epochManager);
21372136

21382137
// We omit a call to `initEmpty` here, as the only intended use of this function is to have its symbol table
@@ -2164,7 +2163,7 @@ GlobalState::copyForSlowPath(const vector<string> &extraPackageFilesDirectoryUnd
21642163
result->setPackagerOptions(extraPackageFilesDirectoryUnderscorePrefixes,
21652164
extraPackageFilesDirectorySlashDeprecatedPrefixes,
21662165
extraPackageFilesDirectorySlashPrefixes, packageSkipRBIExportEnforcementDirs,
2167-
allowRelaxedPackagerChecksFor, packagerLayers, errorHint);
2166+
allowRelaxedPackagerChecksFor, packagerLayers, errorHint, genPackages);
21682167
}
21692168

21702169
return result;
@@ -2315,10 +2314,11 @@ void GlobalState::setPackagerOptions(const vector<string> &extraPackageFilesDire
23152314
const vector<string> &extraPackageFilesDirectorySlashPrefixes,
23162315
const vector<string> &packageSkipRBIExportEnforcementDirs,
23172316
const vector<string> &allowRelaxedPackagerChecksFor,
2318-
const vector<string> &packagerLayers, string errorHint) {
2317+
const vector<string> &packagerLayers, string errorHint, bool genPackages) {
23192318
ENFORCE_NO_TIMER(!packageDB_.frozen);
23202319

23212320
packageDB_.enabled_ = true;
2321+
packageDB_.genPackages_ = genPackages;
23222322
packageDB_.extraPackageFilesDirectoryUnderscorePrefixes_ = extraPackageFilesDirectoryUnderscorePrefixes;
23232323
packageDB_.extraPackageFilesDirectorySlashDeprecatedPrefixes_ = extraPackageFilesDirectorySlashDeprecatedPrefixes;
23242324
packageDB_.extraPackageFilesDirectorySlashPrefixes_ = extraPackageFilesDirectorySlashPrefixes;

core/GlobalState.h

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ class GlobalState final {
362362
const std::vector<std::string> &extraPackageFilesDirectorySlashPrefixes,
363363
const std::vector<std::string> &packageSkipRBIExportEnforcementDirs,
364364
const std::vector<std::string> &skipImportVisibilityCheckFor,
365-
const std::vector<std::string> &packagerLayers, std::string errorHint);
365+
const std::vector<std::string> &packagerLayers, std::string errorHint, bool genPackages);
366366
packages::UnfreezePackages unfreezePackages();
367367

368368
NameRef nextMangledName(ClassOrModuleRef owner, NameRef origName);
@@ -481,19 +481,18 @@ class GlobalState final {
481481
const std::vector<std::string> &extraPackageFilesDirectorySlashPrefixes,
482482
const std::vector<std::string> &packageSkipRBIExportEnforcementDirs,
483483
const std::vector<std::string> &allowRelaxedPackagerChecksFor,
484-
const std::vector<std::string> &packagerLayers, std::string errorHint) const;
484+
const std::vector<std::string> &packagerLayers, std::string errorHint, bool genPackages) const;
485485

486486
// Minimally copy the global state, including the file table, to initialize the LSPTypechecker.
487487
// NOTE: this very intentionally will not copy the symbol or name tables. The symbol tables aren't used or populated
488488
// during indexing, and the name tables will only be written to.
489-
std::unique_ptr<GlobalState>
490-
copyForLSPTypechecker(const bool packagerEnabled,
491-
const std::vector<std::string> &extraPackageFilesDirectoryUnderscorePrefixes,
492-
const std::vector<std::string> &extraPackageFilesDirectorySlashDeprecatedPrefixes,
493-
const std::vector<std::string> &extraPackageFilesDirectorySlashPrefixes,
494-
const std::vector<std::string> &packageSkipRBIExportEnforcementDirs,
495-
const std::vector<std::string> &allowRelaxedPackagerChecksFor,
496-
const std::vector<std::string> &packagerLayers, std::string errorHint) const;
489+
std::unique_ptr<GlobalState> copyForLSPTypechecker(
490+
const bool packagerEnabled, const std::vector<std::string> &extraPackageFilesDirectoryUnderscorePrefixes,
491+
const std::vector<std::string> &extraPackageFilesDirectorySlashDeprecatedPrefixes,
492+
const std::vector<std::string> &extraPackageFilesDirectorySlashPrefixes,
493+
const std::vector<std::string> &packageSkipRBIExportEnforcementDirs,
494+
const std::vector<std::string> &allowRelaxedPackagerChecksFor, const std::vector<std::string> &packagerLayers,
495+
std::string errorHint, bool genPackages) const;
497496

498497
// Copy the name table, file table and other parts of GlobalState that are required to start the slow path.
499498
// NOTE: this very intentionally will not copy the symbol table, and the expectation is that the symbol table will
@@ -504,7 +503,7 @@ class GlobalState final {
504503
const std::vector<std::string> &extraPackageFilesDirectorySlashPrefixes,
505504
const std::vector<std::string> &packageSkipRBIExportEnforcementDirs,
506505
const std::vector<std::string> &allowRelaxedPackagerChecksFor,
507-
const std::vector<std::string> &packagerLayers, std::string errorHint) const;
506+
const std::vector<std::string> &packagerLayers, std::string errorHint, bool genPackages) const;
508507

509508
// Contains a path prefix that should be stripped from all printed paths.
510509
std::string pathPrefix;

core/errors/packager.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,6 @@ constexpr ErrorClass InvalidMinTypedLevel{3729, StrictLevel::False};
3838
constexpr ErrorClass PreludePackageImport{3731, StrictLevel::False};
3939
// constexpr ErrorClass NoExplicitPreludeImport{3732, StrictLevel::False};
4040
// constexpr ErrorClass PreludeLowestLayer{3733, StrictLevel::False};
41+
constexpr ErrorClass IncorrectImportList{3734, StrictLevel::False};
4142
} // namespace sorbet::core::errors::Packager
4243
#endif

core/packages/PackageDB.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ PackageDB PackageDB::deepCopy() const {
172172

173173
// --- options ---
174174
result.enabled_ = this->enabled_;
175+
result.genPackages_ = this->genPackages_;
175176
result.extraPackageFilesDirectoryUnderscorePrefixes_ = this->extraPackageFilesDirectoryUnderscorePrefixes_;
176177
result.extraPackageFilesDirectorySlashDeprecatedPrefixes_ =
177178
this->extraPackageFilesDirectorySlashDeprecatedPrefixes_;

core/packages/PackageDB.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ class PackageDB final {
6060
return this->enabled_;
6161
}
6262

63+
// Whether the --gen-packages mode is active.
64+
bool genPackages() const {
65+
return this->genPackages_;
66+
}
67+
6368
absl::Span<const std::string> extraPackageFilesDirectoryUnderscorePrefixes() const;
6469
absl::Span<const std::string> extraPackageFilesDirectorySlashDeprecatedPrefixes() const;
6570
absl::Span<const std::string> extraPackageFilesDirectorySlashPrefixes() const;
@@ -92,6 +97,7 @@ class PackageDB final {
9297

9398
private:
9499
bool enabled_ = false;
100+
bool genPackages_ = false;
95101
std::vector<std::string> extraPackageFilesDirectoryUnderscorePrefixes_;
96102
std::vector<std::string> extraPackageFilesDirectorySlashDeprecatedPrefixes_;
97103
std::vector<std::string> extraPackageFilesDirectorySlashPrefixes_;

core/packages/PackageInfo.cc

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "core/packages/PackageInfo.h"
22
#include "absl/strings/str_join.h"
3+
#include "common/sort/sort.h"
34
#include "core/GlobalState.h"
45
#include "core/Loc.h"
56
#include "core/NameRef.h"
@@ -466,6 +467,97 @@ optional<string> PackageInfo::pathTo(const core::GlobalState &gs, const MangledN
466467
return nullopt;
467468
}
468469

470+
void PackageInfo::trackPackageReference(const core::FileRef file, const core::packages::MangledName package,
471+
const PackageReferenceInfo packageReferenceInfo) {
472+
auto r = referencedPackages.find(package);
473+
if (r != referencedPackages.end()) {
474+
r->second.first.insert(file);
475+
} else {
476+
referencedPackages[package] = {{file}, packageReferenceInfo};
477+
}
478+
}
479+
480+
void PackageInfo::untrackPackageReferencesFor(const core::FileRef file) {
481+
for (auto &[_, v] : referencedPackages) {
482+
auto files = v.first;
483+
auto it = files.find(file);
484+
if (it == files.end()) {
485+
continue;
486+
}
487+
files.erase(it);
488+
}
489+
erase_if(referencedPackages, [](const auto &x) { return x.second.first.empty(); });
490+
}
491+
492+
namespace {
493+
core::packages::ImportType fileToImportType(const core::GlobalState &gs, core::FileRef file) {
494+
if (file.data(gs).isPackagedTestHelper()) {
495+
return core::packages::ImportType::TestHelper;
496+
} else if (file.data(gs).isPackagedTest()) {
497+
return core::packages::ImportType::TestUnit;
498+
} else {
499+
return core::packages::ImportType::Normal;
500+
}
501+
}
502+
503+
core::packages::ImportType broadestImportType(const core::GlobalState &gs, const UnorderedSet<core::FileRef> &files) {
504+
auto broadestImport = core::packages::ImportType::TestUnit;
505+
for (auto f : files) {
506+
auto importType = fileToImportType(gs, f);
507+
if (importType < broadestImport) {
508+
broadestImport = importType;
509+
}
510+
}
511+
return broadestImport;
512+
}
513+
514+
void mergeAdjacentEdits(std::vector<core::AutocorrectSuggestion::Edit> &edits) {
515+
fast_sort(edits, [](const auto &lhs, const auto &rhs) {
516+
if (lhs.loc == rhs.loc) {
517+
return lhs.replacement < rhs.replacement;
518+
}
519+
return lhs.loc.beginPos() < rhs.loc.beginPos();
520+
});
521+
auto i = 0;
522+
while (edits.size() > 0 && i < edits.size() - 1) {
523+
if (edits[i].loc.beginPos() == edits[i + 1].loc.beginPos() && edits[i].loc.empty() &&
524+
edits[i + 1].loc.empty()) {
525+
// If we're inserting 2 imports at the same location, combine them into a single edit.
526+
// TODO(neil): maybe this logic should be moved/added to AutocorrectSuggestion::apply, to handle other
527+
// cases where 2 autocorrects add at the same loc?
528+
edits[i].replacement += edits[i + 1].replacement;
529+
edits.erase(edits.begin() + i + 1);
530+
} else {
531+
i++;
532+
}
533+
}
534+
}
535+
536+
}; // namespace
537+
538+
std::optional<core::AutocorrectSuggestion> PackageInfo::aggregateMissingImports(const core::GlobalState &gs) const {
539+
std::vector<core::AutocorrectSuggestion::Edit> allEdits;
540+
for (auto &[p, value] : referencedPackages) {
541+
auto &pkgInfo = gs.packageDB().getPackageInfo(p);
542+
auto files = value.first;
543+
auto packageReferenceInfo = value.second;
544+
if (!packageReferenceInfo.importNeeded || packageReferenceInfo.causesModularityError || !pkgInfo.exists()) {
545+
continue;
546+
}
547+
auto importType = broadestImportType(gs, files);
548+
auto autocorrect = this->addImport(gs, pkgInfo, importType);
549+
if (autocorrect.has_value()) {
550+
allEdits.insert(allEdits.end(), make_move_iterator(autocorrect.value().edits.begin()),
551+
make_move_iterator(autocorrect.value().edits.end()));
552+
}
553+
}
554+
if (allEdits.size() == 0) {
555+
return nullopt;
556+
}
557+
mergeAdjacentEdits(allEdits);
558+
return core::AutocorrectSuggestion{"Add missing imports", std::move(allEdits)};
559+
}
560+
469561
bool PackageInfo::operator==(const PackageInfo &rhs) const {
470562
return mangledName() == rhs.mangledName();
471563
}

core/packages/PackageInfo.h

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ struct VisibleTo {
8383
: mangledName(mangledName), type(type), loc(loc){};
8484
};
8585

86+
struct PackageReferenceInfo {
87+
bool importNeeded;
88+
bool causesModularityError;
89+
};
90+
8691
class PackageInfo {
8792
public:
8893
MangledName mangledName() const {
@@ -177,9 +182,14 @@ class PackageInfo {
177182

178183
StrictDependenciesLevel strictDependenciesLevel = StrictDependenciesLevel::None;
179184

185+
// Map from pkg -> {list of files in this package that reference pkg, whether this package is missing an import
186+
// for pkg and whether importing this package would cause a modularity error}
187+
UnorderedMap<core::packages::MangledName, std::pair<UnorderedSet<core::FileRef>, PackageReferenceInfo>>
188+
referencedPackages = {};
189+
180190
// The id of the SCC that this package's normal imports belong to.
181191
//
182-
// WARNING: Modifying the contents of the package DB after this operation will cause this id to go out of
192+
// WARNING: Modifying the contents of the package DB after ComputePackageSCCs will cause this id to go out of
183193
// date.
184194
std::optional<int> sccID() const {
185195
ENFORCE(exists());
@@ -189,7 +199,7 @@ class PackageInfo {
189199
// The ID of the SCC that this package's tests belong to. This ID is only useful in the context of the package graph
190200
// condensation graph.
191201
//
192-
// WARNING: Modifying the contents of the package DB after this operation will cause this id to go out of
202+
// WARNING: Modifying the contents of the package DB after ComputePackageSCCs will cause this id to go out of
193203
// date.
194204
std::optional<int> testSccID() const {
195205
ENFORCE(exists());
@@ -249,8 +259,18 @@ class PackageInfo {
249259
bool isPreludePackage() const {
250260
return this->isPreludePackage_;
251261
}
262+
263+
// Track that this package references `package` in `file`
264+
void trackPackageReference(const core::FileRef file, const core::packages::MangledName package,
265+
const PackageReferenceInfo packageReferenceInfo);
266+
267+
// Remove knowledge of what this package is in `file`.
268+
// We do this so that when VisibilityChecker is re-run over `file`, we can delete stale information.
269+
void untrackPackageReferencesFor(const core::FileRef file);
270+
271+
std::optional<core::AutocorrectSuggestion> aggregateMissingImports(const core::GlobalState &gs) const;
252272
};
253-
CheckSize(PackageInfo, 208, 8);
273+
CheckSize(PackageInfo, 240, 8);
254274

255275
} // namespace sorbet::core::packages
256276
#endif

main/lsp/LSPIndexer.cc

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -271,18 +271,19 @@ TypecheckingPath LSPIndexer::getTypecheckingPath(const vector<shared_ptr<core::F
271271
}
272272

273273
void LSPIndexer::transferInitializeState(InitializedTask &task) {
274+
ENFORCE(!this->config->opts.genPackages);
274275
// Copying the global state here means that we snapshot before any files have been loaded. That means that the
275276
// indexer and typechecker's file tables will almost immediately diverge, but that's not an issue as we don't share
276277
// `core::FileRef` values between the two.
277-
auto typecheckerGS = std::exchange(
278-
this->gs,
279-
this->gs->copyForLSPTypechecker(this->config->opts.cacheSensitiveOptions.sorbetPackages,
280-
this->config->opts.extraPackageFilesDirectoryUnderscorePrefixes,
281-
this->config->opts.extraPackageFilesDirectorySlashDeprecatedPrefixes,
282-
this->config->opts.extraPackageFilesDirectorySlashPrefixes,
283-
this->config->opts.packageSkipRBIExportEnforcementDirs,
284-
this->config->opts.allowRelaxedPackagerChecksFor,
285-
this->config->opts.packagerLayers, this->config->opts.sorbetPackagesHint));
278+
auto typecheckerGS =
279+
std::exchange(this->gs, this->gs->copyForLSPTypechecker(
280+
this->config->opts.cacheSensitiveOptions.sorbetPackages,
281+
this->config->opts.extraPackageFilesDirectoryUnderscorePrefixes,
282+
this->config->opts.extraPackageFilesDirectorySlashDeprecatedPrefixes,
283+
this->config->opts.extraPackageFilesDirectorySlashPrefixes,
284+
this->config->opts.packageSkipRBIExportEnforcementDirs,
285+
this->config->opts.allowRelaxedPackagerChecksFor, this->config->opts.packagerLayers,
286+
this->config->opts.sorbetPackagesHint, false));
286287

287288
task.setGlobalState(std::move(typecheckerGS));
288289
task.setKeyValueStore(std::move(this->kvstore));

main/options/options.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,7 @@ buildOptions(const vector<pipeline::semantic_extension::SemanticExtensionProvide
425425
"Remove the provided <prefix> from all printed paths. Defaults to the input "
426426
"directory passed to Sorbet, if any.",
427427
cxxopts::value<string>()->default_value(empty.pathPrefix), "<prefix>");
428+
options.add_options(section)("gen-packages", "Generate package information", cxxopts::value<bool>());
428429
// }}}
429430

430431
// ----- AUTOCORRECTS ------------------------------------------------- {{{
@@ -1273,6 +1274,15 @@ void readOptions(Options &opts,
12731274
}
12741275
}
12751276

1277+
opts.genPackages = raw["gen-packages"].as<bool>();
1278+
if (opts.genPackages && !opts.cacheSensitiveOptions.sorbetPackages) {
1279+
logger->error("--gen-packages can only be used when --stripe-packages is also enabled");
1280+
throw EarlyReturnWithCode(1);
1281+
}
1282+
if (opts.genPackages && opts.runLSP) {
1283+
logger->error("--gen-packages can not be used when --lsp is also enabled");
1284+
throw EarlyReturnWithCode(1);
1285+
}
12761286
if (raw.count("allow-relaxed-packager-checks-for")) {
12771287
if (!opts.cacheSensitiveOptions.sorbetPackages) {
12781288
logger->error("--allow-relaxed-packager-checks-for can only be specified in --sorbet-packages mode");

0 commit comments

Comments
 (0)