Skip to content
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

Support abstract modifier on classes and methods #9716

Merged
merged 28 commits into from
Jul 16, 2020
Merged

Conversation

Simn
Copy link
Member

@Simn Simn commented Jul 14, 2020

This PR adds support for abstract classes.

At syntax-level, we allow abstract as a modifier for both classes and fields. We also allow parsing abstract interface, but both abstract typedef and abstract abstract are parser errors.

During type-loading, there's a bunch of checks for invalid combinations, such as abstract final. See the tests for details.

The abstract property is as follows:

  • If a class has a method which is abstract, the class itself must be marked as abstract. We can debate later if we want to keep this mandatory (in fact, we already infer the class to be abstract in order to avoid repeating errors).
  • If a method is abstract, it must not have an expression.
  • If a class is abstract, both new ThatClass() and ThatClass.new are disallowed.
  • If a class extends an abstract class, it must either be made abstract itself or implement all abstract member methods of the extended class and all its parent classes.

That last part is implemented in check_abstract_class. It is implemented with overload support, which lead to the following algorithm:

Given a non-abstract class C extending an abstract class AC:

  1. Collect the names of all member methods of AC and all its parent classes.
  2. For each such name:
    1. Collect all overloads with that name on AC and all its parent classes.
    2. For each such overload cf:
      1. If the cf isn't marked abstract, ignore it.
      2. Otherwise, if C doesn't implement cf, add it to a missing list
  3. If missing isn't empty, emit an appropriate error.

A class is considered to implement an overload cf if it has a method with the same name for which Overloads.same_overload_args is true. That's how we check overload equality in general.

@@ -2261,7 +2261,7 @@ let generate con =
| overloads -> overloads
in
List.iter (fun cf ->
if (has_class_flag cl CInterface) || cf.cf_expr <> None then
if (has_class_flag cl CInterface) || (has_class_flag cl CAbstract) || cf.cf_expr <> None then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should aim to generate native abstract classes where supported. But that could be done later.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I think we have to do it right away. Otherwise we run into problems if an abstract method has a return type since we can't just generate an empty block for it...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, ok

@skial skial mentioned this pull request Jul 15, 2020
1 task
@RealyUniqueName
Copy link
Member

RealyUniqueName commented Jul 15, 2020

Why abstract interface is allowed at syntax level?

@Simn
Copy link
Member Author

Simn commented Jul 15, 2020

For consistency. All other class-specific modifiers are allowed on interfaces as well and only fail during typing, so I figured we should do this for abstract as well.

@RealyUniqueName
Copy link
Member

This PR is missing an implementation of the following statement from the proposal:

Abstract class is not required to implement all the methods of interfaces that class implements. Instead missing interface implementations are implicitly treated as abstract methods

@RealyUniqueName
Copy link
Member

Also inlining is not treated well. The following sample should emit two errors, but none are emitted:

class Main {
	static function main() {
		var w:Abstr = null;
		inline w.test(); // Missing error: Cannot inline abstract method
	}
}

abstract class Abstr {
	abstract public inline function test():Void; // Missing error: Inline function cannot be abstract
}

@Simn
Copy link
Member Author

Simn commented Jul 15, 2020

This PR is missing an implementation of the following statement from the proposal:

Abstract class is not required to implement all the methods of interfaces that class implements. Instead missing interface implementations are implicitly treated as abstract methods

Yeah, hmm... this might introduce some typing pass concerns if we generate these fields at the time we verify the interface. There might also be some complications once overloads get involved because we have to manage cf_overloads correctly.

Can we hold back on this change for now?

@RealyUniqueName
Copy link
Member

Sure. It won't be a breaking change to allow that later.

@Simn
Copy link
Member Author

Simn commented Jul 15, 2020

I've just tried to implement this out of curiosity and indeed ran into the typing pass problem. It seems to work by changing the interface check from PForce to PConnectField, which looks like a sensible change anyway. However, it's hard to tell what consequences this might have, so at the very least I would like to keep it separate for this PR.

So, anything else?

@Simn Simn added platform-cpp Everything related to CPP / C++ platform-hl Everything related to HashLink platform-php Everything related to PHP labels Jul 15, 2020
@Simn
Copy link
Member Author

Simn commented Jul 15, 2020

This requires generator updates for HL, hxcpp and PHP.

@RealyUniqueName Could you handle PHP? I'll try to figure out the others.

@RealyUniqueName
Copy link
Member

So, anything else?

LGTM

This requires generator updates for HL, hxcpp and PHP.

I mean it's not necessary to be a part of this PR.

@Simn
Copy link
Member Author

Simn commented Jul 15, 2020

I mean it's not necessary to be a part of this PR.

Sorry, by "this" I'm referring to this PR in general. I thought it was passing, but that was only because I was using Void-returning functions and didn't actually test if calls to them succeed.

@Simn
Copy link
Member Author

Simn commented Jul 15, 2020

@hughsando Could you check the gencpp.ml changes here? I've implemented abstract methods to be generated as pure virtual functions, and then had to fiddle around a lot to get the _dyn stuff working and make sure that no code that calls the constructor is generated.

output (" " ^ remap_name ^ "(" );
output (ctx_arg_list ctx tl "" );
output ") = 0;\n";
output (" virtual ::Dynamic " ^ remap_name ^ "_dyn();\n" );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this function should be virtual, since internally it will call a virtual function.
Then, it is not needed if the "doDynamic" logic (nonVirtual || not (is_override field ) ) && (reflective class_def field ) is not required. This would be the case when one declared abstract method also exists in a parent class, or it is declared "@:unreflective"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, good point. I've changed that in 7010960

@Simn
Copy link
Member Author

Simn commented Jul 16, 2020

Alright, CI is green!

I'll go ahead and merge this now. There are probably some details that can be improved in various generators, but we can do that later.

@Simn Simn merged commit 9434a59 into development Jul 16, 2020
@Simn Simn deleted the abstract_classes branch July 16, 2020 07:35
Gama11 added a commit to vshaxe/haxe-TmLanguage that referenced this pull request Jul 16, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
platform-cpp Everything related to CPP / C++ platform-hl Everything related to HashLink platform-php Everything related to PHP
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants