-
-
Notifications
You must be signed in to change notification settings - Fork 638
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
[modules] Seamlessly fuse hand-written and generated code #1716
Comments
But if we modify the source files, it'll require annoying discipline not to commit processed files (when we must commit the original, with the generation code). The workflow would be what then, generate to work, then git reset these files before commiting? Even with a script, it seems very kludgy and unintuitive. Also what do we ship in -src.jar? Presumably processed source, or it will confuse IDEs who know how to download sources. Notice that this applies to both proposals. Seems to me like we really need a distinction like src and src-gen? |
Hmm no i was wrong. We always commit with generated code, but keeping the markers. If the generator code is inline we don't remove it, but keep it, commented out. I think keeping the generator code out is better. We can get syntax highlighting, and possibly in rhe future, we can avoid regenerating by comparing timestamps of the partially generated file and the generator source. |
Yes, committing the generated code has benefits. We can see the effects by looking at the 'unfolded' code. This makes sense when performing code reviews for PRs. There are no surprises. Additionally we need to ensure that generated code isn't changed directly. We do this by integrating the code-unfolding of our source files into the build process. I.e. manual changes are overwritten. Ruslan wrote a script that automatically runs on CI build, it checks whether generated code was manually changed. If so, the build breaks. There are still details to solve, e.g. declaring java comments within java comments etc. But first I will create a technical spike that solves our problem. |
Once I wrote xtext-protected-regionst, a protected region support for Xtext code generators. Some of that code can be re-used, e.g. the heuristic to parse protected regions from a source file etc. I already have more 'features' in mind:
// ok
package test;
class Test {
/*GENERATED>>>s"Hello ${fqn}!"*/Hello test.Test!/*<<<GENERATED*/
}
// ok!
package test;
class Test {
/*GENERATED>>>s"Hello ${fqn}!"*/
Hello test.Test!
/*<<<GENERATED*/
}
interface Tuple {
/* GENERATED >>>
text text text text text text text text text text text text text text text
text text text text text text text text text text text text text text text
text text text text text text text text text text text text text text text
------------------------------------------------<pre><code language="scala">
(1 to 3).gen(i => xs"""
static <${gen("T", i)}> Tuple$i<${gen("T", i)}>(${gen("T", i)}) {
return new Tuple$i(${gen("t", i)});
}
""")("\n\n")
</code></pre>---------------------------------------------------------------
text text text text text text text text text text text text text text text
text text text text text text text text text text text text text text text
text text text text text text text text text text text text text text text
*/
static <T1> Tuple1<T1> of(T1 t1) {
return new Tuple1<>(t1);
}
static <T1, T2> Tuple2<T1, T2> of(T1 t1, T2 t2) {
return new Tuple2<>(t1, t2);
}
static <T1, T2, T3> Tuple3<T1, T2, T3> of(T1 t1, T2 t2, T3 t3) {
return new Tuple3<>(t1, t2, t3);
}
// <<< GENERATED
} Please note that |
This might also solve maintenance issues, namely
interface Map {
// GENERATED >>> genStaticTraversableMethods("Map", isMap = true)
// Empty before first call of generator ;-) This comment will be overwritten!
// <<< GENERATED
} And the underlying code generator contains the method referenced above: genStaticTraversableMethods(name: String, isMap: Boolean = false): String = {
val type = im.getType("javaslang.collection." + name)
val generics = if (isMap) "<K, V>" else "<T>"
val widenedGenerics = if (isMap) "<? extends K, ? extends V>" else "<T>"
xs"""
@SuppressWarnings("unchecked")
public static $generics $type$generics($type$widenedGenerics it) {
return ($type$generics) it;
}
"""
} Note: In this example only narrow() is generated but the same method may also generate n other methods like flatten(), ... There may be generator methods, depending on where to start within the type hierarchy:
The generator is able to contain the logic on its own when to generate abstract methods, overridden abstract methods and overridden implementations. This will greatly simplify the maintained code because we pull the logic to a single place! Update: This will also be the key to solve #1326 (removing abstract types like AbstractMap, AbstractMultimap etc.). The code will simply be generated by shared generator methods instead of using (internal) inheritance. Maybe it will also be the key to keep types (like Future/Try and Map/Multimap) in sync? (see #1551, #1532) |
Out-of-scope. To keep efforts minimal, we will stick with the existing Scala code generator. If we need to check conformity of methods across distinct Types (like Option and Try when removing Value), we could implement unit tests that check method existence using reflection. |
Prerequisites:
Spike:
Next steps:
We already commit generated code, it is located in
src-gen/
. It would be much 'nicer' if the code completely resides insrc/
and specific 'regions' are filled-in by the code generator. For those who are already familiar with code generator techniques, these could be called reverse-protected-regions.Normally protected regions contain code, labeled with a unique id (within a comment), that should not be overwritten on subsequent generator calls. Each generator call overwrites all files but restores the protected regions.
We should reverse that and keep all files but replace the content of generated code blocks.
Benefit: Keeping track of the imports would be simple because we would only need to organize the imports. Currently we use an import manager to keep track of imports while generating code.
The Generated Code
Example:
Region markers:
// }
can clash with other comments:The Code Generator
We could use our existing code generator to generate code and write an addition that reads/substitutes/writes the source files.
But another idea is, to place the code generator directly to the peace of code that will be generated:
The code generator would have to perform the following steps:
Pros:
Cons:
The text was updated successfully, but these errors were encountered: