Hello Jochen Maybe its good for me to state the original problem separately from the solution I am pursuing to see if there are other ways to do it. Apologies if the email is too long. I tried to stick to a lot of example rather than text
The problem. Entities in the DSL are either mutable or immutable. Meaning I can either just read from them or update their internals. These include lists, maps etc. For example, class Person { String name Person spouse List<String> address List<Person> relations } 1. I must not modify read only objects @ReadOnly Person me = new Person().tap { ... } me.name = "MyName" // Fails with name is readonly me.address << "Other details" // Fails with list is readonly me.relations << new Person() // Fails with list is readonly me.relations[0].name = "NEWRELATION" // Fails with name is readonly 2. To modify an object it must be mutable Person me = new Person() me.name = "MYNAME" me.address << "ADDR" 3. A mutable object accessed as read only must not be able to modify content @ReadOnly Person safeMe = me safeMe.name = "MYNAME" // Fails with name is read only me.name = "ICANCHANGETHIS" 4. An immutable list should not allow list and content modification @ReadOnly List<Person> persons = [ me, you, him, her] persons << everyone // Fails with list is readonly persons[0].name = "MyName" // Fails with name is readonly def me = persons[0]; me.name = "MyName" // Fails with name is readonly 5. A mutable list can allow modification of list or content, A mutable list can accept immutable content but it clones it to make it mutable // All of this should work List<Person> mutableListOfPersons = [me, you] mutableListOfPersons << everyone mutableListOfPersons[0].name = "MyName" // Watch immutable me being changed because it was cloned into the list. The original immutableme is still unchanged mutableListOfPersons << myImmutableMe mutableListOfPersons[1].name = "ChangingTheImmutable" I will never have a use case where I will need List<@ReadOnly ...> because either the entire list including its contents is read only or it is mutable. This explained, I am trying to do this 1. I run a second compile on the code just to check for mutability. These class files are discarded (so in this compile I am only interested in semantic validation and not runtime safety. All temporary immutable and mutable classes generated here are never used in the runtime) 2. During compile, I create Person.Mutable and Person.Immutable. Mutable derives from immutable. 3. Immutable has only get functions and Mutable has set/get functions (the overriden get further qualifies the return type to be mutable) 4. I then replace code that uses Person. Everywhere a local variable is declared, I make it mutable. Everywhere I call a function, I make its parameters read onlyI then let the Groovy compiler do its magic. This works perfectly for persons not used inside generic collections So after compile, my entity looks like this class Person { interface class Immutable { String getName() Person.Immutable getSpouse() List<String> getAddress() List<Person.Immutable> getRelations() } interface Mutable extends Immutable { // All gets with mutable signatures String getName() Person.Mutable getSpouse() List<String> getAddress() List<Person.Mutable> getRelations() // All sets with immutable signatures to allow both mutable and immutable void setName(String value) void setSpouse(Person.Immutable value) void setAddress(List<String value) void setRelations(List<? extends Person.Immutable> value) } } My code then looks like this and works as expected without any major incidents (newing up the interface is handled through another AST that replaces constructor call, so ignore that) 1. I must not modify read only objects @ReadOnly Person.Immutable me = new Person.Mutable().tap { ... } me.name = "MyName" // Fails with name is readonly me.address << "Other details" // Fails with list is readonly me.relations << new Person() // Fails with list is readonly me.relations[0].name = "NEWRELATION" // Fails with name is readonly 2. To modify an object it must be mutable Person.Mutable me = new Person.Mutable() me.name = "MYNAME" me.address << "ADDR" 3. A mutable object accessed as read only must not be able to modify content @ReadOnly Person.Immutable safeMe = me // me is mutable safeMe.name = "MYNAME" // Fails with name is read only me.name = "ICANCHANGETHIS" 4. An immutable list should not allow list and content modification @ReadOnly List<Person> persons = [ me, you, him, her] persons[0].name = "MyName" // Fails with name is readonly def me = persons[0]; me.name = "MyName" // Fails with name is readonly 5. A mutable list can allow modification of list or content // All of this should work List<Person> mutableListOfPersons = [me, you] mutableListOfPersons[0].name = "MyName" I feel like I can handle the list mutability by further qualifying lists as MutableList vs ImmutableList and checking after static compile what method was called on what object (and I can compile error on putAt being used in ImmutableList etc ...) The problem I run into however is when I use lists of mutable with immutables. For example, This entire code should be valid void fn(@ReadOnly Person me) { List<Person> myList = [] myList << me Map<String, Person> myMap = [:] myMap << me } This gets converted into void fn(Person.Immutable me) { List<Person.Mutable> myList = [] myList << me // Fails with me cannot be assigned to mutable list Map<String, Person.Mutable> myMap = [:] myMap["ME"] = me // Also fails because types dont match } How do I solve this? Are there other issues I am not foreseeing? regards Saravanan