Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shadowing identifiers produces warnings #9863

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ private ExportSymbolAnalysis() {}

@Override
public UUID key() {
return null;
return uuid;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.enso.compiler.pass.lint;

import java.util.UUID;
import org.enso.compiler.context.InlineContext;
import org.enso.compiler.context.ModuleContext;
import org.enso.compiler.core.IR;
import org.enso.compiler.core.ir.Expression;
import org.enso.compiler.core.ir.Module;
import org.enso.compiler.pass.IRPass;
import scala.collection.immutable.Seq;

/**
* A lint compiler pass that checks for shadowed types and methods.
* If shadowing is detected, a warning is attached to the IR.
*/
public final class ShadowedIdentifiersLint implements IRPass {
public static final ShadowedIdentifiersLint INSTANCE = new ShadowedIdentifiersLint();
private UUID uuid;

@Override
public UUID key() {
return uuid;
}

@Override
public void org$enso$compiler$pass$IRPass$_setter_$key_$eq(UUID v) {
this.uuid = v;
}

@Override
public Seq<IRPass> precursorPasses() {
return null;
}

@Override
public Seq<IRPass> invalidatedPasses() {
return null;
}

@Override
public <T extends IR> T updateMetadataInDuplicate(T sourceIr, T copyOfIr) {
return IRPass.super.updateMetadataInDuplicate(sourceIr, copyOfIr);
}

@Override
public Module runModule(Module ir, ModuleContext moduleContext) {
if (!moduleContext.compilerConfig().warningsEnabled()) {
return ir;
}
return null;
}

/**
* Not supported for this pass.
*/
@Override
public Expression runExpression(Expression ir, InlineContext inlineContext) {
return ir;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package org.enso.interpreter.test;

import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Set;
import org.enso.pkg.QualifiedName;
import org.enso.polyglot.PolyglotContext;
import org.enso.polyglot.RuntimeOptions;
import org.enso.test.utils.ContextUtils;
import org.enso.test.utils.ProjectUtils;
import org.enso.test.utils.SourceModule;
import org.hamcrest.Matcher;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

public class ShadowedIdentifiersTest {
@Rule public TemporaryFolder tempFolder = new TemporaryFolder();

@Test
public void shadowingTypeFromDifferentModuleIsWarning() throws IOException {
var modSrc = """
type T
""";
var mainSrc = """
from project.Mod import T
type T
""";
var projDir = createProjectWithTwoModules(mainSrc, modSrc);
testProjectCompilationWarning(projDir, allOf(containsString("shadowed")));
}

@Test
public void shadowingStaticMethodFromDifferentModuleIsWarning() throws IOException {
var modSrc = """
stat_method x y = x + y
""";
var mainSrc =
"""
from project.Mod import stat_method
stat_method x = x + 42
""";
var projDir = createProjectWithTwoModules(mainSrc, modSrc);
testProjectCompilationWarning(projDir, allOf(containsString("shadowed")));
}

@Test
public void shadowingRenamedTypeFromDifferentModuleIsWarning() throws IOException {
var modSrc = """
type R
""";
var mainSrc = """
import project.Mod.R as T
type T
""";
var projDir = createProjectWithTwoModules(mainSrc, modSrc);
testProjectCompilationWarning(projDir, allOf(containsString("shadowed")));
}

@Test
public void shadowedTypeHasPrecedence() throws IOException {
var modSrc = """
type T
foo = "Mod.T.foo"
""";
var mainSrc =
"""
from project.Mod import T
type T
foo = "Main.T.foo"
main =
T.foo
""";
var projDir = createProjectWithTwoModules(mainSrc, modSrc);
ProjectUtils.testProjectRun(
projDir,
res -> {
assertThat(res.isString(), is(true));
assertThat(res.asString(), is("Main.T.foo"));
});
}

@Test
public void shadowedTypeHasPrecedenceInRuntimeTyping() throws IOException {
var modSrc =
"""
type T
Value
foo self = "Mod.T.foo"
""";
var mainSrc =
"""
from project.Mod import T

type T
Value
foo self = "Main.T.foo"

bar (t:T) = t.foo

main =
t = T.Value
bar t
""";
var projDir = createProjectWithTwoModules(mainSrc, modSrc);
ProjectUtils.testProjectRun(
projDir,
res -> {
assertThat(res.isString(), is(true));
assertThat(res.asString(), is("Main.T.foo"));
});
}

private Path createProjectWithTwoModules(String mainSrc, String modSrc) throws IOException {
var projDir = tempFolder.newFolder().toPath();
ProjectUtils.createProject(
"Proj",
Set.of(
new SourceModule(QualifiedName.fromString("Main"), mainSrc),
new SourceModule(QualifiedName.fromString("Mod"), modSrc)),
projDir);
return projDir;
}

private void testProjectCompilationWarning(Path mainProjDir, Matcher<String> warnMessageMatcher) {
var out = new ByteArrayOutputStream();
try (var ctx =
ContextUtils.defaultContextBuilder()
.option(RuntimeOptions.PROJECT_ROOT, mainProjDir.toAbsolutePath().toString())
.option(RuntimeOptions.STRICT_ERRORS, "true")
.option(RuntimeOptions.DISABLE_IR_CACHES, "true")
.out(out)
.err(out)
.build()) {
var polyCtx = new PolyglotContext(ctx);
polyCtx.getTopScope().compile(true);
assertThat(out.toString(), warnMessageMatcher);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package org.enso.interpreter.test;

import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

import java.io.IOException;
import java.util.Set;
import org.enso.pkg.QualifiedName;
import org.enso.test.utils.ProjectUtils;
import org.enso.test.utils.SourceModule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

public class TypeErrorTest {
@Rule public TemporaryFolder tempFolder = new TemporaryFolder();

@Test
public void typeErrorMessageContainsQualifiedNames() throws IOException {
var modSrc = """
type T
Ctor

make_t = T.Ctor
""";
var mainSrc =
"""
from Standard.Base import all
from project.Data.Mod import T
from project.Data.Mod import make_t

type T

method (t:T) = t.baz

main =
t = make_t
p = Panic.catch Any handler=(_.payload) <| method t
p.to_text
""";
var projDir = tempFolder.newFolder().toPath();
ProjectUtils.createProject(
"Proj",
Set.of(
new SourceModule(QualifiedName.fromString("Main"), mainSrc),
new SourceModule(QualifiedName.fromString("Data.Mod"), modSrc)),
projDir);
ProjectUtils.testProjectRun(
projDir,
res -> {
assertThat(res.isString(), is(true));
assertThat(
res.asString(),
allOf(
containsString("Type error"),
containsString("expected `t` to be"),
containsString("Data.Mod.T"),
containsString("Main.T")));
});
}
}
Loading