Skip to content

Commit

Permalink
Merge pull request #989 from nomiclabs/better-compilation-errors
Browse files Browse the repository at this point in the history
Improve compilation job error messages
  • Loading branch information
alcuadrado committed Nov 27, 2020
2 parents 690bac4 + a6d4c9b commit 7b80092
Show file tree
Hide file tree
Showing 7 changed files with 848 additions and 183 deletions.
236 changes: 174 additions & 62 deletions packages/hardhat-core/src/builtin-tasks/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,12 @@ import { ResolvedFile, Resolver } from "../internal/solidity/resolver";
import { glob } from "../internal/util/glob";
import { getCompilersDir } from "../internal/util/global-dir";
import { pluralize } from "../internal/util/strings";
import { unsafeObjectEntries, unsafeObjectKeys } from "../internal/util/unsafe";
import { Artifacts, CompilerInput, CompilerOutput, SolcBuild } from "../types";
import * as taskTypes from "../types/builtin-tasks";
import {
CompilationJob,
CompilationJobCreationError,
CompilationJobsCreationErrors,
CompilationJobCreationErrorReason,
CompilationJobsCreationResult,
} from "../types/builtin-tasks";
import { getFullyQualifiedName } from "../utils/contract-names";
Expand Down Expand Up @@ -272,22 +271,15 @@ subtask(TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOBS)
)
);

const compilationJobsCreationResult = compilationJobsCreationResults.reduce(
(acc, { jobs, errors }) => {
acc.jobs = acc.jobs.concat(jobs);
for (const [code, files] of unsafeObjectEntries(errors)) {
acc.errors[code] = acc.errors[code] ?? [];
acc.errors[code] = acc.errors[code]!.concat(files!);
}
return acc;
},
{
jobs: [],
errors: {},
}
);
let jobs: CompilationJob[] = [];
let errors: CompilationJobCreationError[] = [];

return compilationJobsCreationResult;
for (const result of compilationJobsCreationResults) {
jobs = jobs.concat(result.jobs);
errors = errors.concat(result.errors);
}

return { jobs, errors };
}
);

Expand Down Expand Up @@ -446,7 +438,6 @@ subtask(TASK_COMPILE_SOLIDITY_LOG_DOWNLOAD_COMPILER_START)
.setAction(
async ({
isCompilerDownloaded,
quiet,
solcVersion,
}: {
isCompilerDownloaded: boolean;
Expand Down Expand Up @@ -711,7 +702,7 @@ subtask(TASK_COMPILE_SOLIDITY_COMPILE, async (taskArgs: any, { run }) => {
subtask(TASK_COMPILE_SOLIDITY_LOG_COMPILATION_ERRORS)
.addParam("output", undefined, undefined, types.any)
.addParam("quiet", undefined, undefined, types.boolean)
.setAction(async ({ output, quiet }: { output: any; quiet: boolean }) => {
.setAction(async ({ output }: { output: any; quiet: boolean }) => {
if (output?.errors === undefined) {
return;
}
Expand Down Expand Up @@ -784,7 +775,7 @@ subtask(TASK_COMPILE_SOLIDITY_EMIT_ARTIFACTS)
output: CompilerOutput;
solcBuild: SolcBuild;
},
{ artifacts, config, run }
{ artifacts, run }
): Promise<{
artifactsEmittedPerFile: ArtifactsEmittedPerFile;
}> => {
Expand Down Expand Up @@ -1017,13 +1008,11 @@ subtask(TASK_COMPILE_SOLIDITY_HANDLE_COMPILATION_JOBS_FAILURES)
{
compilationJobsCreationErrors,
}: {
compilationJobsCreationErrors: CompilationJobsCreationErrors;
compilationJobsCreationErrors: CompilationJobCreationError[];
},
{ run }
) => {
const hasErrors = unsafeObjectEntries(compilationJobsCreationErrors).some(
([, errors]) => errors!.length > 0
);
const hasErrors = compilationJobsCreationErrors.length > 0;

if (hasErrors) {
log(`There were errors creating the compilation jobs, throwing`);
Expand Down Expand Up @@ -1052,78 +1041,201 @@ subtask(TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOBS_FAILURE_REASONS)
async ({
compilationJobsCreationErrors: errors,
}: {
compilationJobsCreationErrors: CompilationJobsCreationErrors;
compilationJobsCreationErrors: CompilationJobCreationError[];
}): Promise<string> => {
let noCompatibleSolc: string[] = [];
let incompatibleOverridenSolc: string[] = [];
let importsIncompatibleFile: string[] = [];
let other: string[] = [];

for (const code of unsafeObjectKeys(errors)) {
const files = errors[code];

if (files === undefined) {
continue;
}
const noCompatibleSolc: CompilationJobCreationError[] = [];
const incompatibleOverridenSolc: CompilationJobCreationError[] = [];
const directlyImportsIncompatibleFile: CompilationJobCreationError[] = [];
const indirectlyImportsIncompatibleFile: CompilationJobCreationError[] = [];
const other: CompilationJobCreationError[] = [];

for (const error of errors) {
if (
code === CompilationJobCreationError.NO_COMPATIBLE_SOLC_VERSION_FOUND
error.reason ===
CompilationJobCreationErrorReason.NO_COMPATIBLE_SOLC_VERSION_FOUND
) {
noCompatibleSolc.push(error);
} else if (
error.reason ===
CompilationJobCreationErrorReason.INCOMPATIBLE_OVERRIDEN_SOLC_VERSION
) {
noCompatibleSolc = noCompatibleSolc.concat(files);
incompatibleOverridenSolc.push(error);
} else if (
code ===
CompilationJobCreationError.INCOMPATIBLE_OVERRIDEN_SOLC_VERSION
error.reason ===
CompilationJobCreationErrorReason.DIRECTLY_IMPORTS_INCOMPATIBLE_FILE
) {
incompatibleOverridenSolc = incompatibleOverridenSolc.concat(files);
directlyImportsIncompatibleFile.push(error);
} else if (
code === CompilationJobCreationError.IMPORTS_INCOMPATIBLE_FILE
error.reason ===
CompilationJobCreationErrorReason.INDIRECTLY_IMPORTS_INCOMPATIBLE_FILE
) {
importsIncompatibleFile = importsIncompatibleFile.concat(files);
} else if (code === CompilationJobCreationError.OTHER_ERROR) {
other = other.concat(files);
indirectlyImportsIncompatibleFile.push(error);
} else if (
error.reason === CompilationJobCreationErrorReason.OTHER_ERROR
) {
other.push(error);
} else {
// add unrecognized errors to `other`
other = other.concat(files);
other.push(error);
}
}

let reasons = "";
let errorMessage = "";
if (incompatibleOverridenSolc.length > 0) {
reasons += `The compiler version for the following files is fixed through an override in your
config file to a version that is incompatible with their version pragmas.
${incompatibleOverridenSolc.map((x) => `* ${x}`).join("\n")}
errorMessage += `The compiler version for the following files is fixed through an override in your config file to a version that is incompatible with their Solidity version pragmas.
`;

for (const error of incompatibleOverridenSolc) {
const { sourceName } = error.file;
const { versionPragmas } = error.file.content;
const versionsRange = versionPragmas.join(" ");

log(`File ${sourceName} has an incompatible overriden compiler`);

errorMessage += ` * ${sourceName} (${versionsRange})\n`;
}

errorMessage += "\n";
}

if (noCompatibleSolc.length > 0) {
reasons += `The pragma statement in these files don't match any of the configured compilers
in your config. Change the pragma or configure additional compiler versions in
your hardhat config.
errorMessage += `The Solidity version pragma statement in these files don't match any of the configured compilers in your config. Change the pragma or configure additional compiler versions in your hardhat config.
`;

for (const error of noCompatibleSolc) {
const { sourceName } = error.file;
const { versionPragmas } = error.file.content;
const versionsRange = versionPragmas.join(" ");

log(
`File ${sourceName} doesn't match any of the configured compilers`
);

${noCompatibleSolc.map((x) => `* ${x}`).join("\n")}
errorMessage += ` * ${sourceName} (${versionsRange})\n`;
}

errorMessage += "\n";
}

if (directlyImportsIncompatibleFile.length > 0) {
errorMessage += `These files import other files that use a different and incompatible version of Solidity:
`;

for (const error of directlyImportsIncompatibleFile) {
const { sourceName } = error.file;
const { versionPragmas } = error.file.content;
const versionsRange = versionPragmas.join(" ");

const incompatibleDirectImportsFiles: ResolvedFile[] =
error.extra?.incompatibleDirectImports ?? [];

const incompatibleDirectImports = incompatibleDirectImportsFiles.map(
(x: ResolvedFile) =>
`${x.sourceName} (${x.content.versionPragmas.join(" ")})`
);

log(
`File ${sourceName} imports files ${incompatibleDirectImportsFiles
.map((x) => x.sourceName)
.join(", ")} that use an incompatible version of Solidity`
);

let directImportsText = "";
if (incompatibleDirectImports.length === 1) {
directImportsText = ` imports ${incompatibleDirectImports[0]}`;
} else if (incompatibleDirectImports.length === 2) {
directImportsText = ` imports ${incompatibleDirectImports[0]} and ${incompatibleDirectImports[1]}`;
} else if (incompatibleDirectImports.length > 2) {
const otherImportsCount = incompatibleDirectImports.length - 2;
directImportsText = ` imports ${incompatibleDirectImports[0]}, ${
incompatibleDirectImports[1]
} and ${otherImportsCount} other ${pluralize(
otherImportsCount,
"file"
)}. Use --verbose to see the full list.`;
}

errorMessage += ` * ${sourceName} (${versionsRange})${directImportsText}\n`;
}

errorMessage += "\n";
}
if (importsIncompatibleFile.length > 0) {
reasons += `These files import other files that use a different and incompatible version of Solidity:

${importsIncompatibleFile.map((x) => `* ${x}`).join("\n")}
if (indirectlyImportsIncompatibleFile.length > 0) {
errorMessage += `These files depend on other files that use a different and incompatible version of Solidity:
`;

for (const error of indirectlyImportsIncompatibleFile) {
const { sourceName } = error.file;
const { versionPragmas } = error.file.content;
const versionsRange = versionPragmas.join(" ");

const incompatibleIndirectImports: taskTypes.TransitiveDependency[] =
error.extra?.incompatibleIndirectImports ?? [];

const incompatibleImports = incompatibleIndirectImports.map(
({ dependency }) =>
`${
dependency.sourceName
} (${dependency.content.versionPragmas.join(" ")})`
);

for (const {
dependency,
path: dependencyPath,
} of incompatibleIndirectImports) {
const dependencyPathText = [
sourceName,
...dependencyPath.map((x) => x.sourceName),
dependency.sourceName,
].join(" -> ");

log(
`File ${sourceName} depends on file ${dependency.sourceName} that uses an incompatible version of Solidity
The dependency path is ${dependencyPathText}
`
);
}

let indirectImportsText = "";
if (incompatibleImports.length === 1) {
indirectImportsText = ` depends on ${incompatibleImports[0]}`;
} else if (incompatibleImports.length === 2) {
indirectImportsText = ` depends on ${incompatibleImports[0]} and ${incompatibleImports[1]}`;
} else if (incompatibleImports.length > 2) {
const otherImportsCount = incompatibleImports.length - 2;
indirectImportsText = ` depends on ${incompatibleImports[0]}, ${
incompatibleImports[1]
} and ${otherImportsCount} other ${pluralize(
otherImportsCount,
"file"
)}. Use --verbose to see the full list.`;
}

errorMessage += ` * ${sourceName} (${versionsRange})${indirectImportsText}\n`;
}

errorMessage += "\n";
}

if (other.length > 0) {
reasons += `These files and its dependencies cannot be compiled with your config:
errorMessage += `These files and its dependencies cannot be compiled with your config. This can happen because they have incompatible Solidity pragmas, or don't match any of your configured Solidity compilers.
${other.map((x) => `* ${x}`).join("\n")}
${other.map((x) => ` * ${x.file.sourceName}`).join("\n")}
`;
}

reasons += `Learn more about compiler configuration at https://hardhat.org/config
errorMessage += `To learn more, run the command again with --verbose
Read about compiler configuration at https://hardhat.org/config
`;

return reasons;
return errorMessage;
}
);

Expand Down

0 comments on commit 7b80092

Please sign in to comment.