Skip to content
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
48 changes: 48 additions & 0 deletions changelog/deprecate_bad_module_name.dd
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
Deprecate import module name mismatch

Some instances of module name/import name mismatches were deprecated while others are now considered errors.

Case 1: DEPRECATED: import a module name with a qualified name but the module being imported has no module declaration.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This effectively imposes that modules inside directories (frequent situation) must have a module declaration. Moreover, the module declaration makes the module contents dependent on the directory in which it resides, thus closing a circular dependency between the file content and its position in the file system. Whenever the module is moved around, it needs to also be touched.

These does not seem desirable qualities. On the contrary, it was appreciated that a given module can be accessed via different packages (within e.g. different projects), via directory aliases, etc. It also seems makes it more difficult to define and use hierarchical packages.

What advantages does introducing this restriction have?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This effectively imposes that modules inside directories (frequent situation) must have a module declaration.

Actually it just means that they must be imported with a "non-qualified" name.

---
// main.d
import foo.bar;

// foo/bar.d
// this file is empty, it has no module declaration
---
The above code will now print:

$(CONSOLE Deprecated: module bar from file foo/bar.d should be imported with 'import bar;' )

Case 2: DEPRECATED: import a module with a qualified name that partially matches the name of the module being imported.
---
// main.d
import foo.bar;
import foo.baz.buz;

// foo/bar.d
module bar;

// foo/baz/buz.d
module baz.buz;
---
The above code will now print:

$(CONSOLE Deprecated: module bar from file foo/bar.d should be imported with 'import bar;'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively: replace module declaration with module foo.bar;

We need to show advantages of this as well. For example show how the current rules lead to a breach in modularity.

My current mental model goes like this: main.d uses a dotted import to guide path selection, and that also confers a local name for the imported module. The fact that the module gave itself a non-dotted name is irrelevant to main.d. I see nothing wrong in the fact that the module name from the perspective of code inside foo/bar.d is different from the module name from the perspective of code inside main.d. In fact we have a language construct that messes exactly with that:

import takezat = foo.bar;

We'd need a good argument that the above is a bad thing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. That's not how I originally understood the module system to work, but your description matches the implementation that I found.

One disadvantage of the current mechanism is that this causes the compiler to load the same module multiple times. Each unique path used to import the module will result in another instance of the module being instantiated. I think there's more to say but I'll leave it at that for now.

Deprecated: module baz.buz from file foo/baz/buz.d should be imported with 'import baz.buz;' )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A similar line of reasoning would apply here. We need to build a case on why that's bad.


Note that for this rule to apply, the shorter name must completely match the end of the longer name.

Case 3: ERROR: import a module that matches the filename but does not match the module name.
---
// main.d
import foo;

// foo.d
module bar;
---
The above code will now fail and print:

$(CONSOLE Error: module bar from file foo.d must be imported with 'import bar;')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand how the file would ever be found. So the compiler is supposed to compile main.d. It sees import bar;. How does it know it's supposed to look for a file called foo.d?

Copy link
Contributor Author

@marler8997 marler8997 Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand how the file would ever be found.

The file is give on the command line.

Note that module names not matching file names is a technique already used (see #7778 (comment)) , however, this PR does enforce that the import name matches the actual module name.


Note that importing a module whose module name does not match its filename is still supported.
45 changes: 44 additions & 1 deletion src/dmd/dmodule.d
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import dmd.globals;
import dmd.id;
import dmd.identifier;
import dmd.parse;
import dmd.root.array;
import dmd.root.file;
import dmd.root.filename;
import dmd.root.outbuffer;
Expand Down Expand Up @@ -514,7 +515,49 @@ extern (C++) final class Module : Package
buf.printf("%s\t(%s)", ident.toChars(), m.srcfile.toChars());
message("import %s", buf.peekString());
}
m = m.parse();
{
auto originalModule = m;
m = m.parse();

// verify the module name matches the imported module name
if (m is originalModule)
{
if (m.md is null)
{
// in this case there was no module declaration, this means the import
// should have no package prefix, but for now we support this with a
// depecration message for backwards compatibility
if (packages && packages.dim > 0)
m.deprecation(loc, "from file %s should be imported with 'import %s;'",
m.srcfile.name.toChars(), m.toPrettyChars());
}
else if (!packages.opEquals(m.md.packages) || ident != m.md.id)
{
// deprecate the case when the ends of the names match
bool deprecate = false;
if (ident == m.md.id)
{
deprecate = true;
auto mdPackageCount = (m.md.packages == null) ? 0 : m.md.packages.dim;
auto packageCount = (packages == null) ? 0 : packages.dim;
for (size_t i = 1; i <= mdPackageCount && i <= packageCount; i++)
{
if (m.md.packages[mdPackageCount - i] != packages[packageCount - i])
{
deprecate = false;
break;
}
}
}
if (deprecate)
m.deprecation(loc, "from file %s should be imported with 'import %s;'",
m.srcfile.name.toChars(), m.toPrettyChars());
else
m.error(loc, "from file %s must be imported with 'import %s;'",
m.srcfile.name.toChars(), m.toPrettyChars());
}
}
}

// Call onImport here because if the module is going to be compiled then we
// need to determine it early because it affects semantic analysis. This is
Expand Down
19 changes: 19 additions & 0 deletions src/dmd/root/array.d
Original file line number Diff line number Diff line change
Expand Up @@ -313,3 +313,22 @@ unittest
array[2] = 910;
assert([123, 421, 910, 123, 1, 2, 8, 20, 4, 3] == array.asDArray);
}

Copy link
Member

@UplinkCoder UplinkCoder Jan 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why isn't it called opEqual ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allows you to compare to pointers to array and handles the null case for you as well

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, changed it to opEquals.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not left.asDArray == right.asDArray?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would have to be (*left).asDArray and (*right).asDArray, and this segfaults when left or right is null.

/**
* Returns true if every element of $(D left) and $(D right) are equal.
* Note that an array of size 0 is considered equivalent to a `null` pointer.
* Params:
* left = pointer to array to compare
* right = pointer to array to compare
*/
bool opEquals(T)(const(Array!T)* left, const(Array!T)* right)
{
if (left is null || left.dim == 0)
return right is null || right.dim == 0;
if (right is null || left.dim != right.dim)
return false;
foreach (i; 0 .. left.dim)
if ((*left)[i] != (*right)[i])
return false;
return true;
}
2 changes: 1 addition & 1 deletion test/compilable/extra-files/test14894a.d
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module imports.test14894a;
module test14894a;

mixin template Protocol()
{
Expand Down
1 change: 1 addition & 0 deletions test/compilable/imports/a313templatemixin1.d
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
module imports.a313templatemixin1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this necessary? We should continue to work if the module declaration is not present.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's one way to go. You could also add the imports directory to the "import path list", i.e.

dmd -Icompilable/imports ...

then no module declaration is required.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marler8997 can't we just penalize people who enter wrong module information? It seems this PR does that but also adds another restriction, which is more difficult to justify.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm not sure exactly what you're trying to say. There's 2 cases listed in the original PR message that have been deprecated, and 1 that has been declared to be an error. Do those use cases categorize the cases you are thinking of? If so, which ones do you think should not be deprecated/made errors?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marler8997 yah let me discuss exactly on the cases you present in the changelog. Thanks for the neat organization.

void bug()
{
}
1 change: 1 addition & 0 deletions test/compilable/imports/a313templatemixin2.d
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
module imports.a313templatemixin2;
void bug()
{
}
1 change: 1 addition & 0 deletions test/compilable/imports/checkimports3a.d
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
module imports.checkimports3a;
void foo() {}
1 change: 1 addition & 0 deletions test/compilable/imports/checkimports3b.d
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
module imports.checkimports3b;
private void foo(int) {}
1 change: 1 addition & 0 deletions test/compilable/imports/checkimports3c.d
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
module imports.checkimports3c;
void foo(string) {}
1 change: 1 addition & 0 deletions test/compilable/imports/fwdref12201a.d
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
module imports.fwdref12201a;
alias int FILE;
4 changes: 2 additions & 2 deletions test/compilable/imports/fwdref2_test17548.d
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
module fwdref2_test17548;
module imports.fwdref2_test17548;

import test17548;

struct S2 {
void bar(int arg = .test17548.cnst) {}
S1 s;
import fwdref2_test17548;
import imports.fwdref2_test17548;
}
1 change: 1 addition & 0 deletions test/compilable/imports/fwdref9514.d
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
module imports.fwdref9514;
bool find9514(alias pred, R)(R range)
{
return true;
Expand Down
1 change: 1 addition & 0 deletions test/compilable/imports/g313public.d
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
module imports.g313public;
void bug()
{
}
1 change: 1 addition & 0 deletions test/compilable/imports/g313staticif.d
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
module imports.g313staticif;
void bug()
{
}
1 change: 1 addition & 0 deletions test/compilable/imports/g313stringmixin.d
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
module imports.g313stringmixin;
void bug()
{
}
1 change: 1 addition & 0 deletions test/compilable/imports/g313templatemixin.d
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
module imports.g313templatemixin;
void bug()
{
}
1 change: 1 addition & 0 deletions test/compilable/imports/imp16085.d
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
module imports.imp16085;
struct Pass
{
}
Expand Down
3 changes: 2 additions & 1 deletion test/compilable/imports/imp16085b.d
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import imp16085 : S;
module imports.imp16085b;
import imports.imp16085 : S;

struct Fail
{
Expand Down
1 change: 1 addition & 0 deletions test/compilable/imports/protectionimp.d
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
module imports.protectionimp;
private
{
void privF() {}
Expand Down
2 changes: 1 addition & 1 deletion test/compilable/imports/test11563core_bitop.d
Original file line number Diff line number Diff line change
@@ -1 +1 @@
module test11563core_bitop;
module imports.test11563core_bitop;
1 change: 1 addition & 0 deletions test/compilable/imports/test15857a.d
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
module imports.test15857a;
public import imports.test15857b;
public import imports.test15857c;
1 change: 1 addition & 0 deletions test/compilable/imports/test15857b.d
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
module imports.test15857b;
void bar15857(int) {}
1 change: 1 addition & 0 deletions test/compilable/imports/test15857c.d
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
module imports.test15857c;
void bar15857(string) {}
2 changes: 1 addition & 1 deletion test/compilable/imports/test71.d
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module imports_test71;
module imports.test71;
import imports = object;

void foo()
Expand Down
5 changes: 3 additions & 2 deletions test/compilable/test15925.d
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/* REQUIRED_ARGS: -transition=import -transition=checkimports
/* REQUIRED_ARGS: -transition=import -transition=checkimports -Icompilable/imports
PERMUTE_ARGS:
TEST_OUTPUT:
---
compilable/test15925.d(17): Deprecation: local import search method found variable `imp15925.X` instead of nothing
compilable/test15925.d(12): Deprecation: module `imp15925` from file compilable/imports/imp15925.d should be imported with 'import imp15925;'
compilable/test15925.d(18): Deprecation: local import search method found variable `imp15925.X` instead of nothing
---
*/

Expand Down
5 changes: 3 additions & 2 deletions test/compilable/test313f.d
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// REQUIRED_ARGS: -de
import imports.f313;
// EXTRA_SOURCES: imports/f313.d
import foo.bar;

void test()
{
imports.f313.bug();
foo.bar.bug();
}
5 changes: 3 additions & 2 deletions test/compilable/test314.d
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// REQUIRED_ARGS: -de
// EXTRA_SOURCES: imports/a314.d
module imports.test314; // package imports

import imports.a314;
import imports.pkg.a314;

void main()
{
imports.a314.bug("This should work.\n");
imports.pkg.a314.bug("This should work.\n");
renamed.bug("This should work.\n");
bug("This should work.\n");
}
33 changes: 27 additions & 6 deletions test/d_do_test.d
Original file line number Diff line number Diff line change
Expand Up @@ -459,24 +459,45 @@ bool collectExtraSources (in string input_dir, in string output_dir, in string[]
}

// compare output string to reference string, but ignore places
// marked by $n$ that contain compiler generated unique numbers
// marked by $n$ that contain compiler generated unique numbers and
// also ignore dir seperators '/'
bool compareOutput(string output, string refoutput)
{
import std.ascii : digits;
import std.utf : byCodeUnit;
for ( ; ; )
{
bool skipDirSeparator = false;
auto pos = refoutput.indexOf("$n$");
if (pos < 0)
return refoutput == output;
{
pos = refoutput.indexOf("/");
if (pos < 0)
{
pos = refoutput.indexOf("\\");
if (pos < 0)
return refoutput == output;
}
skipDirSeparator = true;
}
if (output.length < pos)
return false;
if (refoutput[0..pos] != output[0..pos])
return false;
refoutput = refoutput[pos + 3 ..$];
output = output[pos..$];
auto p = output.byCodeUnit.countUntil!(e => !digits.canFind(e));
output = output[p..$];
if (skipDirSeparator)
{
refoutput = refoutput[pos + 1 .. $];
if (output[pos] != '/' && output[pos] != '\\')
return false;
output = output[pos + 1 .. $];
}
else
{
refoutput = refoutput[pos + 3 ..$];
output = output[pos..$];
auto p = output.byCodeUnit.countUntil!(e => !digits.canFind(e));
output = output[p..$];
}
}
}

Expand Down
10 changes: 10 additions & 0 deletions test/fail_compilation/badimport.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
EXTRA_SOURCES: imports/nomodname.d
REQUIRED_ARGS: -Ifail_compilation -de
PERMUTE_ARGS:
TEST_OUTPUT:
---
fail_compilation/badimport.d(10): Error: module `nomodname` from file fail_compilation/imports/nomodname.d must be imported with 'import nomodname;'
---
*/
import imports.nomodname;
10 changes: 10 additions & 0 deletions test/fail_compilation/badimport2.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
EXTRA_SOURCES: imports/incompletemodname.d
REQUIRED_ARGS: -Ifail_compilation -de
PERMUTE_ARGS:
TEST_OUTPUT:
---
fail_compilation/badimport2.d(10): Error: module `incompletemodname` from file fail_compilation/imports/incompletemodname.d must be imported with 'import incompletemodname;'
---
*/
import imports.incompletemodname;
10 changes: 10 additions & 0 deletions test/fail_compilation/badimport3.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
COMPILED_IMPORTS: imports/incompletemodname.d
REQUIRED_ARGS: -Ifail_compilation
PERMUTE_ARGS:
TEST_OUTPUT:
---
fail_compilation/badimport3.d(10): Error: module `wrongpkg.wrongpkgname` from file fail_compilation/imports/wrongpkgname.d must be imported with 'import wrongpkg.wrongpkgname;'
---
*/
import imports.wrongpkgname;
10 changes: 10 additions & 0 deletions test/fail_compilation/badimport4.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
EXRTRA_SOURCES: imports/wrong_mod_name.d
REQUIRED_ARGS: -Ifail_compilation/imports
PERMUTE_ARGS:
TEST_OUTPUT:
---
fail_compilation/badimport4.d(10): Error: module `wrong_mod_name_bleh` from file fail_compilation/imports/wrong_mod_name.d must be imported with 'import wrong_mod_name_bleh;'
---
*/
import wrong_mod_name;
3 changes: 2 additions & 1 deletion test/fail_compilation/dip22b.d
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/*
EXTRA_SOURCES: imports/dip22c.d
REQUIRED_ARGS: -de
TEST_OUTPUT:
---
fail_compilation/dip22b.d(12): Deprecation: `pkg.dip22c.Foo` is not visible from module `dip22`
fail_compilation/dip22b.d(13): Deprecation: `pkg.dip22c.Foo` is not visible from module `dip22`
---
*/
module pkg.dip22;
Expand Down
Loading