Background
As you all probably well know, there are several access modifiers in X++ that determine the visibility of individual artifacts like classes, methods and fields. The semantics of most of these are quite obvious, and have been used since the beginning of the era of object oriented programming, namely: Private, protected and public, but X++ (and in C# for that matter) also have the "internal" option. An internal artifact can be freely used only within the same package in which it is defined, but not ourside of it.
There were some problems with the X++ compiler's enforcements of the rules concerning internal, and we have now fixed the compiler to correctly diagnose the rules correctly. This may, or may not, have an impact on your work, so I thought I would provide an explanation.
Why do we use internal at all?
There are two sides to this coin, depending on your vantage point Internal is the least intrusive option for application developers who want to protect their APIs from usage outside their package. Developers use this to lock down their code allowing them to more easily change their implementation as new feature requests arrive and bugs need to get fixed. Otherwise we guarantee a full year passing from the time a change is made obsolete (with a warning) till the time that it will be decommissioned, either by removal or by changing the SysObsolete attribute to break the compilation. A year is a long time in the X++ scheme of things.
So on one hand this is great because it allows us much more agility. On the other hand, it is a pain for people who want to consume internal APIs, since now they will no longer be able to do so - Now the internal semantics of internal are correctly implemented. This is unlikely to have any serious impact on your existing code.
What are the changes?
We detail the changes made below:
A public class cannot extend an internal class in the same model
Consider the following code:
internal class MyClass
{
}
public class AnotherClass extends MyClass
{
}
Where the two classes are in the same package. This used to generate a warning:
Warning: Base class 'MyClass' is less accessible than class 'AnotherClass'.
This is now an error. Since this the classes are in the same package this will impact you if you use internal classes in your code and try to extend an external class from it (in your own code). If you did this, then the original internal specifier would not make much sense, since it could be used from outside the package through the public class. Good for you: The compiler does a better job of protecting you now from exposing abstractions that should remain internal. There were some cases in the Microsoft code that were fixed to comply with this, but we have made sure that no violations exist in 3rd party code, so this should not impact any 3rd party code.
Extending InternalUseOnly classes
There are a few more cases that are diagnosed, but these are warnings that will not block compilation of 3rd party code. You should start planning to not take a dependency on internalUseOnly artifacts.
Internal class cannot be used as internal member and incorrect error message
Again, consider the following code
internal class ClassA {}
public class ClassB // ClassB is in the same package as ClassA
{
internal ClassA a;
public ClassA b;
protected ClassA c;
private ClassA d;
}
where the two classes are in the same package. Here we have improved the diagnosis, and we how give a better error message:
Error: InconsistentAccessibility: field type 'ClassA' is less accessible than field 'b.ClassB'
Error: InconsistentAccessibility: field type 'ClassA' is less accessible than field 'c.ClassB'
In the case where the classes are in different packages we now also diagnose the case where the internal class (ClassA) is used as a private member. This was not diagnosed before, and you will have to change your code if you do this.
Overriding an internal method in same package and increasing visibility is not diagnosed as an error
Consider now the following code:
class Base { internal void method() {} }
// in the same model as the Base class:
class LocalA extends Base { public void method() {} }
class LocalB extends Base { internal void method() {} }
class LocalC extends Base { protected void method() {} }
class LocalD extends Base { private void method() {} }
Here we now issue better error messages:
LocalA.xpp(3,5): CannotChangeAccess: 'LocalA.method' cannot change access modifiers when overriding inherited method 'Base.method'
LocalC.xpp(3,5): CannotChangeAccess: 'LocalC.method' cannot change access modifiers when overriding inherited method 'Base.method'
LocalD.xpp(3,5): CannotChangeAccess: 'LocalD.method' cannot change access modifiers when overriding inherited method 'Base.method'
Again, this only has any impact in your own packages.
If the Base class and the Local* classes mentioned above are in different packages, you will get the following messages:
DerivedA.xpp(3,5): CannotChangeAccess: 'DerivedA.method' cannot change access modifiers when overriding inherited method 'Base.method'
DerivedB.xpp(3,5): InvalidOverrideIntenalMethod: Method 'method' in class 'Base' is internal and is not allowed to be overriden.
DerivedC.xpp(3,5): CannotChangeAccess: 'DerivedC.method' cannot change access modifiers when overriding inherited method 'Base.method'
DerivedD.xpp(3,5): CannotChangeAccess: 'DerivedD.method' cannot change access modifiers when overriding inherited method 'Base.method'
This will impact you if you were exploiting the gap in the compiler that allowed you to publicly override a non-accessible internal method (as show in the first error message above).
It was never possible to use internal artifacts from outside the assembly where it is defined, unless that assembly is marked as a friend in the model descriptor file.
In other words, if package P contains this class:
internal class MyClass // In package P
{}
And package Q (that depends on package P) contains:
public class AnotherClass extends MyClass // In package Q
{
}
Then a compilation error stating that the internal class MyClass is not visible in package Q will ensue, as it always has. This has not changed.
In addition to the internal keyword, there is another way something can be marked as internal namely by using the InternalUseOnly property. This is not something that we recommend to external parties. It means that the artifact is internal (as if the internal keyword had been provided), but instead of respecting the boundaries of the package in which it is defined, these artifacts can be consumed across all Microsoft application packages. This is a luxury we have allowed ourselves. From an external perspective, it does not really matter: If something is internal, you should not be using it from outside that package. We have not made any changes to the way internalUseOnly artifacts are handled in this release - This information is provided for information only.
*This post is locked for comments