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 18namespace ark::es2panda::compiler { 19 20static 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 39static 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->ThrowSyntaxError("Files '" + prog1->FileName().Mutf8() + "' and '" + 54 prog2->FileName().Mutf8() + 55 "' are in the same folder, but have different package names.", 56 lexer::SourcePosition(0, 0)); 57 } 58 }; 59 60 for (const auto &srcIter : program->ExternalSources()) { 61 // in the external sources, all programs for a record in the map is in the same module, 62 // it's enough to check the first of them 63 const auto *const extSrc = std::get<1>(srcIter).front(); 64 throwErrorIfPackagesConflict(program, extSrc); 65 66 for (const auto &srcIterCmp : program->ExternalSources()) { 67 const auto *const extSrcCpm = std::get<1>(srcIterCmp).front(); 68 throwErrorIfPackagesConflict(extSrc, extSrcCpm); 69 } 70 } 71} 72 73static void ValidateImportDeclarationsSourcePath(const public_lib::Context *const ctx, 74 const ArenaVector<parser::Program *> &packagePrograms, 75 const std::vector<const ir::Statement *> &importDeclarations) 76{ 77 for (const auto *const stmt : importDeclarations) { 78 const bool doesImportFromPackage = 79 std::any_of(packagePrograms.cbegin(), packagePrograms.cend(), [&stmt](const parser::Program *const prog) { 80 return prog->SourceFilePath() == stmt->AsETSImportDeclaration()->ResolvedSource()->Str(); 81 }); 82 if (doesImportFromPackage) { 83 ctx->parser->ThrowSyntaxError("Package module cannot import from a file in it's own package", 84 stmt->Start()); 85 } 86 } 87} 88 89static void ValidateNoImportComesFromSamePackage(const public_lib::Context *const ctx, parser::Program *const program, 90 ArenaVector<parser::Program *> packagePrograms) 91{ 92 // Making sure that the variable is not a reference. We modify the local vector here, which must not have any side 93 // effects to the original one. Don't change it. 94 static_assert(!std::is_reference_v<decltype(packagePrograms)>); 95 packagePrograms.emplace_back(program); 96 97 for (const auto *const packageProg : packagePrograms) { 98 // Filter out only import declarations 99 std::vector<const ir::Statement *> importDeclarations {}; 100 const auto &progStatements = packageProg->Ast()->Statements(); 101 std::copy_if(progStatements.begin(), progStatements.end(), std::back_inserter(importDeclarations), 102 [](const ir::Statement *const stmt) { return stmt->IsETSImportDeclaration(); }); 103 104 // Validate if all import declaration refers to a path outside of the package module 105 ValidateImportDeclarationsSourcePath(ctx, packagePrograms, importDeclarations); 106 } 107} 108 109bool PackageImplicitImport::Perform(public_lib::Context *const ctx, parser::Program *const program) 110{ 111 if (!program->IsPackageModule() || program->VarBinder()->IsGenStdLib()) { 112 // Only run for package module files 113 return true; 114 } 115 116 ValidateFolderContainOnlySamePackageFiles(ctx, program); 117 118 auto &externalSources = program->ExternalSources(); 119 if (externalSources.count(program->ModuleName()) == 0) { 120 // No other files in the package, return 121 return true; 122 } 123 124 auto &packagePrograms = externalSources.at(program->ModuleName()); 125 126 // NOTE (mmartin): Very basic sorting of files in the package, to merge them in a prescribed order 127 std::stable_sort(packagePrograms.begin(), packagePrograms.end(), 128 [](const parser::Program *const prog1, const parser::Program *const prog2) { 129 return prog1->FileName() < prog2->FileName(); 130 }); 131 132 MergeExternalFilesIntoCompiledProgram(program, packagePrograms); 133 ValidateNoImportComesFromSamePackage(ctx, program, packagePrograms); 134 135 // All entities were merged into the main program from the external sources of the same package, 136 // so we can delete all of them 137 externalSources.erase(program->ModuleName()); 138 139 return true; 140} 141 142} // namespace ark::es2panda::compiler 143