eclipse-archived / ceylon

The Ceylon compiler, language module, and command line tools
http://ceylon-lang.org
Apache License 2.0
399 stars 62 forks source link

problems with annotation compilation when there is an unrelated error in the module #1757

Open CeylonMigrationBot opened 10 years ago

CeylonMigrationBot commented 10 years ago

[@gavinking] See comments by @akberc on ceylon/ceylon-ide-eclipse#578 which is not an IDE problem. @tombentley this one is for you.

[Migrated from ceylon/ceylon-compiler#1757]

CeylonMigrationBot commented 10 years ago

[@tombentley] @akberc, referring back to ceylon/ceylon-ide-eclipse#578 it seems that there merely being errors in the same compilation unit is not enough to trigger the problem. For example a class extending a missing interface, or a class not implementing an inherited formal attribute do not trigger the problem. But a broken annotation class (even if unrelated to the valid annotation classes, and even if only the valid annotation classes are used in the dependent module) is sufficient to trigger the problem.

Do you recall if there were other kinds of broken class which were sufficient the trigger the problem, aside from annotation classes?

CeylonMigrationBot commented 10 years ago

[@tombentley] Let's take the example of a broken class D and a valid function d which instantiates D and returns it (so D appears in its signature).

So fundamentally this is related to error recovery in the transitive case #1637 and #1173.

However, we should not end up hiding the "CeylonEnter has already run" error. In fact I think we should probably be reporting that D is not found in this case. I wonder that @FroMage thinks.

CeylonMigrationBot commented 10 years ago

[@FroMage] Can't we just generate the D class skeleton? Like the one we generate for bootstrapping?

CeylonMigrationBot commented 10 years ago

[@tombentley] Well, that's an idea, but AFAICS we don't actually generate those, we just enter the types into the symbol table, don't we?

CeylonMigrationBot commented 10 years ago

[@FroMage] We only generate them for the bootstrap case. We should just generate them in case of class declaration error too, no? Rather than just skip the whole class.

CeylonMigrationBot commented 10 years ago

[@tombentley] Yeah, I like the theory, I just don't see in the code where/how we do that.

CeylonMigrationBot commented 10 years ago

[@FroMage] In CeylonVisitor? When we detect compilation errors instead of producing nothing?

CeylonMigrationBot commented 10 years ago

[@tombentley] It's certainly possible to generate class and interface declarations, annotated with @CompileTimeError when a declaration has errors. In some particular cases this means compilation continues, a car is produced and dependent code will compile OK, so this would be an improvement.

However, in general generating such things can produce javac errors later during compilation. We've no way to know they're related to the thing we generated (e.g. sometimes errors are attributed to line -1), so we're in the worse position of still not generating a car, but also having added spurious javac errors which look like compiler bugs. We would end up building ever more heuristics into the code generator to try to "fix" broken code so that it was acceptable to javac. We don't want to go there.

We could try to tackle this piecemeal. For instance errors in a class or function parameter can probably be handled robustly by generating a constructor like this:

BrokenParameterList(Object... ignored) {
    throw new UnresolvedCompilationError("whatever error message");
}

That wouldn't work for method parameters though (at least not actual ones), because we can run into problems with not implementing abstract methods in non-abstract classes though. But a piecemeal approach is just that, piecemeal. The original problem would still exists, it would just be slightly less easy to trigger.

I think what we actually need is:

My definition of "depends on" is a little hand-wavy: Containing expressions or statements which use a broken declaration doesn't count. Having the broken declaration anywhere in the dependent declaration (e.g. return type, parameter type, extended, satisfied, enumerated type etc) does count. For example, if I have a broken class A:

but

Thus the brokenness of declarations becomes a transitive thing for declarations, but not for expressions (where it results in an exception at runtime).

CeylonMigrationBot commented 10 years ago

[@FroMage] Well, I wonder what this will do to incremental compilation strategies that compare the source file date with class file date. If we produce a broken class file for users of types with errors, then we fix the error and the incremental checker does not recompile the broken users because its source is older than its class file?

CeylonMigrationBot commented 10 years ago

[@tombentley] Well who said the emitted class file for a broken X had to be X.class? It could be X$broken.class. By definition no other class file will depend on it, so we're got a bit of leeway.

Anyway, the point is that if a type has errors it cannot be used correctly: So those "users" would have to be recompiled. There's no magic way we can compile them correctly so stuff will work once the problem is fixed: It's only a compiler, it can't predict the future.

CeylonMigrationBot commented 10 years ago

[@FroMage] Or we put the list of broken source files in META-INF/broken or something. That would make the ceylon-build script trigger a full rebuild on errors for a naïve implementation which bails out if the file exists, or hell, the compiler itself could auto-add that list of files on the next invocation?

CeylonMigrationBot commented 10 years ago

[@akberc] @tombentley Sorry for late reply - snowed under with day job these days so not able to fire up IDE.

Back then it was primarily annotations, aliases, and changing 'shared' on packages within a module depended on by others, even if the public API of the module did not change. Over time, it was only annotations that were the biggest problem.

I have not read the entire thread, so just speculating : would loose classes for IDE help, instead of modules? This way, intermediate classes would be left in their last knownn stable state and not be recompiled if their dependencies had errors, thus interrupting the cascade. Similar to the 'ambiguous hierarchy' error in the JDT that is one error but does not cascade to dependents. Of course you would not be able to run or export the module with errors.

CeylonMigrationBot commented 10 years ago

[@tombentley] My idea doesn't really work, at least not simply. The basic problem is that we need to generate 'enough' code from a broken declaration in order for the things which depend on it to compile. That means javac has to be happy to compile what it's been presented with, but we also need to be able to load a model from the binary in order to satisfy the ceylon typechecker. For some broken declarations that's not too difficult, but for others it's very complicated, for example:

class BrokenExtends() extends Missing(){}
class Sub() extends BrokenExtends(){}

To compile Sub I need a model for BrokenExtends, but its superclass is missing. So I end up having to pretend its superclass is Object in order to be able to compile Sub.

We end up in the situation where we need to handle each kind of error specially. So we'll end up littering the code generator with logic for specific error cases, and I'm not sure that's a game of whack-a-mole that I want to play.

CeylonMigrationBot commented 10 years ago

[@tombentley] Despite the progress that's been made on #1758 there will still be occasions where we're not sensibly able to generate any .class file for a Ceylon declaration that has errors. (That's because it's only in limited circumstances where we know precisely what the error on the declaration is that we can be sure we can generate something that won't also provoke javac errors at use sites).

So this issue is really about what to do with those Ceylon use sites which we can't compile because they depend on a Ceylon declaration that we've not compiled because it has errors. (I just mean depend on within the declaration; use in the implementation is not a problem, since we just throw). The problem is that this becomes transitive. The idea I tried out involved marking those Ceylon declarations which the compiler could do nothing with, so we could also avoid trying to transform use sites. There are a number of problems with that:

So I don't really know a satisfactory solution to this problem.

CeylonMigrationBot commented 10 years ago

[@FroMage] Well, this moves to 1.2, right?

CeylonMigrationBot commented 10 years ago

[@tombentley] Yes.

CeylonMigrationBot commented 10 years ago

[@gavinking]

So this issue is really about what to do...

@tombentley if the issue title and description don't remotely describe what the actual problem is, please close it and open a new issue with a better description.