1 /**
2 * Copyright (c) 2023-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "packageImplicitImport.h"
17
18 namespace ark::es2panda::compiler {
19
MergeExternalFilesIntoCompiledProgram(parser::Program * const program,const ArenaVector<parser::Program * > & packagePrograms)20 static void MergeExternalFilesIntoCompiledProgram(parser::Program *const program,
21 const ArenaVector<parser::Program *> &packagePrograms)
22 {
23 for (auto *const extProg : packagePrograms) {
24 const auto extClassDecls = extProg->Ast()->Statements();
25 for (auto *const stmt : extClassDecls) {
26 if (stmt->IsETSPackageDeclaration()) {
27 continue;
28 }
29
30 stmt->SetParent(program->Ast());
31
32 // Because same package files must be in one folder, relative path references in an external
33 // source's import declaration certainly will be the same (and can be resolved) from the global program too
34 program->Ast()->Statements().emplace_back(stmt);
35 }
36 }
37 }
38
ValidateFolderContainOnlySamePackageFiles(const public_lib::Context * const ctx,const parser::Program * const program)39 static void ValidateFolderContainOnlySamePackageFiles(const public_lib::Context *const ctx,
40 const parser::Program *const program)
41 {
42 const auto throwErrorIfPackagesConflict = [&ctx](const parser::Program *const prog1,
43 const parser::Program *const prog2) {
44 if ((prog1 == prog2) || !(prog1->IsPackageModule() && prog2->IsPackageModule())) {
45 return;
46 }
47
48 if ((prog1->ModuleName() != prog2->ModuleName()) && (prog1->SourceFileFolder() == prog2->SourceFileFolder())) {
49 // There exist 2 files in the same folder, with different package names
50 //
51 // Showing the full path would be more informative, but it also leaks it to the stdout, which is
52 // not the best idea
53 ctx->parser->LogSyntaxError("Files '" + prog1->FileName().Mutf8() + "' and '" + prog2->FileName().Mutf8() +
54 "' are in the same folder, but have different package names.",
55 lexer::SourcePosition(0, 0));
56 }
57 };
58
59 for (const auto &srcIter : program->ExternalSources()) {
60 // in the external sources, all programs for a record in the map is in the same module,
61 // it's enough to check the first of them
62 const auto *const extSrc = std::get<1>(srcIter).front();
63 throwErrorIfPackagesConflict(program, extSrc);
64
65 for (const auto &srcIterCmp : program->ExternalSources()) {
66 const auto *const extSrcCpm = std::get<1>(srcIterCmp).front();
67 throwErrorIfPackagesConflict(extSrc, extSrcCpm);
68 }
69 }
70 }
71
ValidateImportDeclarationsSourcePath(const public_lib::Context * const ctx,const ArenaVector<parser::Program * > & packagePrograms,const std::vector<const ir::Statement * > & importDeclarations)72 static void ValidateImportDeclarationsSourcePath(const public_lib::Context *const ctx,
73 const ArenaVector<parser::Program *> &packagePrograms,
74 const std::vector<const ir::Statement *> &importDeclarations)
75 {
76 for (const auto *const stmt : importDeclarations) {
77 const bool doesImportFromPackage =
78 std::any_of(packagePrograms.cbegin(), packagePrograms.cend(), [&stmt](const parser::Program *const prog) {
79 return prog->SourceFilePath() == stmt->AsETSImportDeclaration()->ResolvedSource()->Str();
80 });
81 if (doesImportFromPackage) {
82 ctx->parser->LogSyntaxError("Package module cannot import from a file in it's own package", stmt->Start());
83 }
84 }
85 }
86
ValidateNoImportComesFromSamePackage(const public_lib::Context * const ctx,parser::Program * const program,ArenaVector<parser::Program * > packagePrograms)87 static void ValidateNoImportComesFromSamePackage(const public_lib::Context *const ctx, parser::Program *const program,
88 ArenaVector<parser::Program *> packagePrograms)
89 {
90 // Making sure that the variable is not a reference. We modify the local vector here, which must not have any side
91 // effects to the original one. Don't change it.
92 static_assert(!std::is_reference_v<decltype(packagePrograms)>);
93 packagePrograms.emplace_back(program);
94
95 for (const auto *const packageProg : packagePrograms) {
96 // Filter out only import declarations
97 std::vector<const ir::Statement *> importDeclarations {};
98 const auto &progStatements = packageProg->Ast()->Statements();
99 std::copy_if(progStatements.begin(), progStatements.end(), std::back_inserter(importDeclarations),
100 [](const ir::Statement *const stmt) { return stmt->IsETSImportDeclaration(); });
101
102 // Validate if all import declaration refers to a path outside of the package module
103 ValidateImportDeclarationsSourcePath(ctx, packagePrograms, importDeclarations);
104 }
105 }
106
Perform(public_lib::Context * const ctx,parser::Program * const program)107 bool PackageImplicitImport::Perform(public_lib::Context *const ctx, parser::Program *const program)
108 {
109 if (!program->IsPackageModule() || program->VarBinder()->IsGenStdLib()) {
110 // Only run for package module files
111 return true;
112 }
113
114 ValidateFolderContainOnlySamePackageFiles(ctx, program);
115
116 auto &externalSources = program->ExternalSources();
117 if (externalSources.count(program->ModuleName()) == 0) {
118 // No other files in the package, return
119 return true;
120 }
121
122 auto &packagePrograms = externalSources.at(program->ModuleName());
123
124 // NOTE (mmartin): Very basic sorting of files in the package, to merge them in a prescribed order
125 std::stable_sort(packagePrograms.begin(), packagePrograms.end(),
126 [](const parser::Program *const prog1, const parser::Program *const prog2) {
127 return prog1->FileName() < prog2->FileName();
128 });
129
130 MergeExternalFilesIntoCompiledProgram(program, packagePrograms);
131 ValidateNoImportComesFromSamePackage(ctx, program, packagePrograms);
132
133 // All entities were merged into the main program from the external sources of the same package,
134 // so we can delete all of them
135 externalSources.erase(program->ModuleName());
136
137 return true;
138 }
139
140 } // namespace ark::es2panda::compiler
141