ceylon / ceylon-compiler

DEPRECATED
GNU General Public License v2.0
138 stars 36 forks source link

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

Open gavinking opened 10 years ago

gavinking commented 10 years ago

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

tombentley commented 10 years ago

@akberc, referring back to https://github.com/ceylon/ceylon-ide-eclipse/issues/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?

tombentley commented 10 years ago

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 https://github.com/ceylon/ceylon-compiler/issues/1637 and https://github.com/ceylon/ceylon-compiler/issues/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.

FroMage commented 10 years ago

Can't we just generate the D class skeleton? Like the one we generate for bootstrapping?

tombentley commented 10 years ago

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?

FroMage commented 10 years ago

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.

tombentley commented 10 years ago

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

FroMage commented 10 years ago

In CeylonVisitor? When we detect compilation errors instead of producing nothing?

tombentley commented 10 years ago

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).

FroMage commented 10 years ago

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?

tombentley commented 10 years ago

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.

FroMage commented 10 years ago

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?

akberc commented 10 years ago

@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.

tombentley commented 10 years ago

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.

tombentley commented 10 years ago

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.

FroMage commented 10 years ago

Well, this moves to 1.2, right?

tombentley commented 10 years ago

Yes.

gavinking commented 10 years ago

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.