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

Add override keyword #13217

Closed
wants to merge 10 commits into from
Closed

Add override keyword #13217

wants to merge 10 commits into from

Conversation

pcj
Copy link

@pcj pcj commented Dec 29, 2016

Fixes #2000

This initial commit simply introduces the keyword and performs some initial modifier flags checks. The actual checks against the inheritance chain are not implemented yet.

@RyanCavanaugh or other: Can you suggest when/where the appropriate time to perform these checks are?

No formal review required at this time (obviously), but would appreciate any strategy recommendations or other suggestions.

@@ -145,6 +145,11 @@ namespace ts {
description: Diagnostics.Raise_error_on_expressions_and_declarations_with_an_implied_any_type,
},
{
name: "noImplicitOverride",
type: "boolean",
description: Diagnostics.Member_function_0_was_marked_override_but_no_matching_definition_was_found_in_the_superclass_chain_1,
Copy link
Member

Choose a reason for hiding this comment

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

Description here is what you would see on the commandline under -?, i.e. this should be a string like "Raise an error when overriding class methods are not marked with 'overide'"

Copy link
Author

Choose a reason for hiding this comment

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

Done

}

function checkOverrideMethodDeclaration(node: MethodDeclaration) {
forEachEnclosingClass(node, enclosingClass => {
Copy link
Member

Choose a reason for hiding this comment

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

You'll want to only checking the containing class. The function forEachEnclosingClass is for dealing with nesting of classes, not inheritance chains

@@ -15712,6 +15712,21 @@ namespace ts {
if (getModifierFlags(node) & ModifierFlags.Abstract && node.body) {
error(node, Diagnostics.Method_0_cannot_have_an_implementation_because_it_is_marked_abstract, declarationNameToString(node.name));
}

// Is this the correct time to make assertions against the inheritance chain?
Copy link
Member

Choose a reason for hiding this comment

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

Around line 18164 (look for checkKindsOfPropertyMemberOverrides(type, baseType);) / 18261 might be better; here the base type members are already in scope and we are iterating through the class properties.

Copy link
Author

Choose a reason for hiding this comment

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

Got it.

@@ -155,6 +155,7 @@ namespace ts {
PrivateKeyword,
ProtectedKeyword,
PublicKeyword,
OverrideKeyword,
Copy link
Contributor

Choose a reason for hiding this comment

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

override is not an ES reserved word. move it to the contextual keyword section.

Copy link
Author

Choose a reason for hiding this comment

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

Done.

@@ -20964,6 +20985,25 @@ namespace ts {
flags |= ModifierFlags.Async;
lastAsync = modifier;
break;

case SyntaxKind.OverrideKeyword:
Copy link
Contributor

Choose a reason for hiding this comment

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

you also need to add a check in checkFlagAgreementBetweenOverloads to make sure all overloads are marked as override.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also add a check in checkAccessorDeclaration for accessors matching in override

Copy link
Contributor

Choose a reason for hiding this comment

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

Also a check if declarations are consecutive, see checkFunctionOrConstructorSymbol

Copy link
Author

Choose a reason for hiding this comment

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

Done for (1), (2). As for (3), I still don't really understand what consecutive declaration checks mean. Please propose a test class that illustrates it if possible.

else if (flags & ModifierFlags.Abstract) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "abstract", "override");
}
else if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) {
Copy link
Contributor

Choose a reason for hiding this comment

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

this should follow the abstract modifier checks. override is only applicable on a method declaration in a class or a class expression.

Copy link
Author

Choose a reason for hiding this comment

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

Done.


function checkOverrideMethodDeclaration(node: MethodDeclaration) {
forEachEnclosingClass(node, enclosingClass => {
// TODO: save the methodDeclaration node here in a cache and
Copy link
Contributor

Choose a reason for hiding this comment

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

also check that an override method can not be marked as optional.

Copy link
Author

Choose a reason for hiding this comment

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

Done.

// Is this the correct time to make assertions against the inheritance chain?
// Have all other methods been resolved? Probably need to record that an override exists
// and perform the actual resolution later.
if (getModifierFlags(node) & ModifierFlags.Override) {
Copy link
Contributor

Choose a reason for hiding this comment

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

i would move this to checkKindsOfPropertyMemberOverrides

Copy link
Author

Choose a reason for hiding this comment

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

Done.

@@ -324,6 +324,7 @@ namespace ts {
case SyntaxKind.PrivateKeyword:
case SyntaxKind.ProtectedKeyword:
case SyntaxKind.PublicKeyword:
case SyntaxKind.OverrideKeyword:
Copy link
Contributor

Choose a reason for hiding this comment

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

you also need to handle this in the declarationEmitter.ts, and please add declaration emit tests.

Copy link
Author

Choose a reason for hiding this comment

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

This has not been done yet. I was not able to identify an appropriate declaration emitter test to mimic. Can you suggest one to investigate?

@pcj
Copy link
Author

pcj commented Dec 29, 2016

Thank you for all the excellent feedback, will begin addressing your comments and submit a new iteration over the next day or so.

@pcj
Copy link
Author

pcj commented Jan 4, 2017

See comments above. In addition, consider the following issues:

  1. Declaration emit test still need to be implemented. Is there a
    similar test case for study here? Please advise.

  2. Regarding handling of an @override jsdoc tag. Where should I
    look for the presence of this jsDoc tag and merge it into
    ModifierFlags.Override? Alternatively, should a separate
    ModifierFlags.JSDocOverride flag exist and perform dual checks for
    these (perhaps as a utility function)?

  3. Regarding the semantics of static methods: I have assumed that override
    should not be used with static. Is this true?

  4. Regarding the semantics of an abstract method: should it be
    permissible to apply override to a repeated abstract method in an
    derived abstract class? For example:

abstract class Base {
    abstract getMeaningOfLife(): number;
}

abstract class Derived extends Base {
    // Here, override keyword is not adding any utility (compiler
    // already reports inappropriate usage with abstract keyword alone),
    // but it may be semantically correct.  I see pro/con of each but defer to
    // language designers for decision.  My bias is to not use override in 
    // conjunction with non-concrete members.  

    // Current implementation rejects
    // simultaneous use of both override and abstract.
    override abstract getMeaningOfLife(): number;
}

As an extension to this line of thought, the --noImplicitOverride flag does not complain about the absence of override on interfaces. AFAICT, it does not add anything in terms of correctness and adds unnecessary verbosity to the language. Only when a derived member exists in a concrete class will those assertions kick in.

  1. Regarding implicit extension of Object. I had naively imagined that all
    classes implicitly extend from Object (and that
    getBaseTypes(classType) would include Object in the result
    list). For example, all instantiated typescript objects know that
    toLocaleString() is a valid method that returns a string. This
    is defined in lib.d.ts (as you know). However,
    checkKindsofPropertyMemberOverrides never considers those
    implicity pseudo-inherited properties unless extends Object is
    explicitly specified.

    For example, I want to generate an error if --noImplicitOverride
    is true and 'override' is not present on the following class:

class Foo {
      toLocaleString(): string { 'foo' };
}

I have addressed this with the new checkImplicitPropertyMemberOverrides() function that looks for matching members on the Object type. An alternative implementation might add in a heritageClause for Object early in checkKindsOfPropertyMemberOverrides. Other suggestions welcome.

@pcj
Copy link
Author

pcj commented Jan 4, 2017

In addition, the transpile test for the compiler option is failing as follows. This does not get resolved with jake baseline-accept. Any recommendations for addressing this?

  1) Transpile Supports setting 'noImplicitOverride' Correct output for undefined:
       Error: The baseline file transpile/Supports setting noImplicitOverride.js has changed.
        at writeComparison (built/local/run.js:90502:23)

@mhegazy
Copy link
Contributor

mhegazy commented Jan 4, 2017

See comments above. In addition, consider the following issues:

  1. Declaration emit test still need to be implemented. Is there a
    similar test case for study here? Please advise.

what you need is // @declaration: true at the top of your test case.

  1. Regarding handling of an @override jsdoc tag. Where should I
    look for the presence of this jsDoc tag and merge it into
    ModifierFlags.Override? Alternatively, should a separate
    ModifierFlags.JSDocOverride flag exist and perform dual checks for
    these (perhaps as a utility function)?

This is only applicable in JS files, i would get this done in a separate PR.

  1. Regarding the semantics of static methods: I have assumed that override
    should not be used with static. Is this true?

Do not see why you can not have a static override

  1. Regarding the semantics of an abstract method: should it be
    permissible to apply override to a repeated abstract method in an
    derived abstract class? For example:

I would say no. you can just drop the abstract modifier. need to think more though. @RyanCavanaugh thoughts?

As an extension to this line of thought, the --noImplicitOverride flag does not complain about the absence of override on interfaces. AFAICT, it does not add anything in terms of correctness and adds unnecessary verbosity to the language. Only when a derived member exists in a concrete class will those assertions kick in.

I do not think it makes much sense. the only use case, is the same as the abstract one above, which is override with more specific types, not sure if this is that important.

  1. Regarding implicit extension of Object. I had naively imagined that all
    classes implicitly extend from Object (and that
    getBaseTypes(classType) would include Object in the result
    list).

the properties from Object are "augmentations" that are applied to every object. not sure
i would want to go there, but will need to discuss this in a design meeting.

@pcj
Copy link
Author

pcj commented Jan 5, 2017

the properties from Object are "augmentations" that are applied to every object. not sure
i would want to go there, but will need to discuss this in a design meeting.

Excluding augmented methods from override would be a glaring omission IMHO. Unless I'm missing something, the implementation does not seem particularly onerous. Look forward to hearing your thoughts about this following the design meeting.

@@ -717,6 +717,9 @@ namespace ts {
}

function emitClassMemberDeclarationFlags(flags: ModifierFlags) {
if (flags & ModifierFlags.Override) {
Copy link
Contributor

Choose a reason for hiding this comment

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

this should be after protected. also please add a test for that.

Copy link
Contributor

Choose a reason for hiding this comment

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

and after static.

Copy link
Author

Choose a reason for hiding this comment

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

OK

else if (flags & ModifierFlags.Abstract) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "abstract", "override");
}
else if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) {
Copy link
Contributor

Choose a reason for hiding this comment

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

still check too wide. you want to follow the abstract check above. basically override is only allowed on class property/method/accessor declarations.

Copy link
Contributor

Choose a reason for hiding this comment

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

here is the order we should enforce/check for:

[public | protected | private] [abstract | override] [static] [readonly | async] [get | set] identifier

class C {
    public override static readonly p: number;
    protected overide static async method() {}
}

Copy link
Author

Choose a reason for hiding this comment

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

OK

if (flags & ModifierFlags.Override) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "override");
}
else if (flags & ModifierFlags.Static) {
Copy link
Contributor

Choose a reason for hiding this comment

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

should be allowed

Copy link
Author

Choose a reason for hiding this comment

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

Done.

if (!objectType) {
return;
}
for (const name in propertiesToCheck) {
Copy link
Contributor

Choose a reason for hiding this comment

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

we should disable this check in ambient contexts.

Copy link
Author

Choose a reason for hiding this comment

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

Done.


// Before accepting the correct case, ensure 'override' is marked if --noImplicitOverride is true.
// Abstract members are an exception as override checks are suspended until the implementation solidifies.
if (compilerOptions.noImplicitOverride
Copy link
Contributor

Choose a reason for hiding this comment

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

disable this in ambient contexts.

Copy link
Author

Choose a reason for hiding this comment

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

Done.

…ler option.

* Moves `OverrideKeyword` to the more appropriate contextual keyword
  section (types.ts)
* Introduces the `CompilerOptions.noImplicitOverride` boolean option
  (types.ts).
* Moves the location for performing override assertions to
  `checkKindsOfPropertyMemberOverrides` as suggested (checker.ts).
* Adds several other diagnostic messages (diagnosticMessages.json).
* Improves the breadth of the `overrideKeyword.ts` test case.
* Adds a new `noImplicitOverride.ts` test case.
* Rename check{Implicit -> Augmented}PropertyMemberOverrides.
* Disable augment checks in ambients contexts.
* Allow override on static methods.
* Add override keyword to spec.md.
* Use chained diagnostics when possible.
* Remove hardcoded diagnotic-related strings in checker.ts.
* Split tests into separate files overrideKeyword{es5 + es6}.ts.
@pcj
Copy link
Author

pcj commented Jan 15, 2017

Thanks for your patience... Comments addressed. Force-pushed 3143818 after rebase to 2711303 (Jan 12). A few more issues, but I think we are getting close. Just want to say again how much I appreciate the excellent reviews.

  1. You mentioned earlier about consecutive declaration checks. I don't understand this. I put this test in, but I'm sure it's missing the mark. Can you clarify what the intent is?

  2. I'm still not able to resolve the following error, does not go away after baseline-accept. Any pointers?

 [▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬] ✓ 6548 passing (1m)
  [▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬] ✓ 6548 passing (1m)
  [▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬] ✓ 6548 passing (58s)
  [▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬] ✓ 6547 passing (1m)
  [▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬] ✓ 6548 passing (1m)
  [▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬] ✓ 6548 passing (59s)
  [▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬] ✓ 6548 passing (1m)
  [▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬] ✖ 7264/7265 passing (1m)


  53099 passing (9m)
  1 failing

  1) Transpile Supports setting 'noImplicitOverride' Correct output for undefined:
       Error: The baseline file transpile/Supports setting noImplicitOverride.js has changed.
        at writeComparison (built/local/run.js:91196:23)
  1. I added override to the updated the spec under "The following keywords have special meaning in certain contexts, but are valid identifiers". After looking through the spec, I was not convinced much extra explanation was necessary. Any suggestions for additional documentation welcome.

@pcj pcj changed the title Adds override keyword Add override keyword Jan 18, 2017
@pcj
Copy link
Author

pcj commented Jan 19, 2017

Friendly ping.

@RyanCavanaugh
Copy link
Member

@pcj looks like the new baseline tasks don't handle the transpile folder correctly yet. Try moving the file from the tests/baselines/local/transpile/ folder to tests/baselines/reference/transpile and commiting it

@pcj
Copy link
Author

pcj commented Jan 20, 2017

Thanks @RyanCavanaugh that did it.

@pcj
Copy link
Author

pcj commented Jan 24, 2017

@mhegazy @RyanCavanaugh, do we have a roadmap on moving this forward?

@mhegazy
Copy link
Contributor

mhegazy commented Jan 25, 2017

@mhegazy @RyanCavanaugh, do we have a roadmap on moving this forward?

Some one needs to review it, and we need to discuss it in the design meeting. I will put it on the agenda for this week.

@pcj
Copy link
Author

pcj commented Feb 8, 2017

Appreciate posting the design meeting notes at #13729.

Two questions, (one big, one small):

  • From a high level, is there agreement that override should exist in typescript? (If true, then we're just working out the semantics at this point). Implication is that we are planning to move forward with this PR. In that case, can we assign a milestone?

  • What is the next actionable step? Don't want the code to drift too much relative to HEAD.

Thanks!

Copy link

@ghost ghost left a comment

Choose a reason for hiding this comment

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

Sorry for the long delay!

Please add this test (seems to work without any modification):

// @noImplicitOverride: true

const mixin = <BC extends new (...args: any[]) => {}>(Base: BC) => class extends Base {
    mixedIn() {}
};

class A {
    normal() {}
}

class B extends mixin(A) {
    normal() {} // Error: needs 'override'
    mixedIn() {} // Error: needs 'override'
}

Also, merge from master before reading my comments because they may only apply to the latest version of the compiler. In a few places it looks like you were using old-style maps and now we use es6 maps, so my comments could help you update.

for (const prop of getPropertiesOfObjectType(type)) {
properties[prop.name] = prop;
}
checkAugmentedPropertyMemberOverrides(type, properties);
Copy link

Choose a reason for hiding this comment

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

checkAugmentedPropertyMemberOverrides(type, resolveStructuredTypeMembers(type).members);

Copy link
Author

Choose a reason for hiding this comment

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

Got it, done

return;
}

const objectType = getSymbol(globals, "Object", SymbolFlags.Type);
Copy link

Choose a reason for hiding this comment

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

const objectType = resolveStructuredTypeMembers(globalObjectType);. No need to test for non-null.

Copy link
Author

Choose a reason for hiding this comment

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

OK, done

return;
}

for (const name in propertiesToCheck) {
Copy link

Choose a reason for hiding this comment

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

propertiesToCheck.forEach(propertyToCheck => {

Copy link
Author

Choose a reason for hiding this comment

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

Done

for (const name in propertiesToCheck) {
const derived = getTargetSymbol(propertiesToCheck[name]);
const derivedDeclarationFlags = getDeclarationModifierFlagsFromSymbol(derived);
const found = objectType.members[name];
Copy link

Choose a reason for hiding this comment

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

objectType.members.get(propertyToCheck.name)

Copy link
Author

Choose a reason for hiding this comment

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

Fixed

@@ -18372,9 +18456,19 @@ namespace ts {
// derived class instance member variables and accessors, but not by other kinds of members.

// NOTE: assignability is checked in checkClassDeclaration

// Track which symbols in the derived class have not been seen.
const onlyInDerived = createMap<Symbol>();
Copy link

Choose a reason for hiding this comment

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

const onlyInDerived = cloneMap(resolveStructuredTypeMembers(type).members); (no need for for loop)

Copy link
Author

Choose a reason for hiding this comment

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

Done

@@ -16104,6 +16127,9 @@ namespace ts {
else if (deviation & ModifierFlags.Abstract) {
error(o.name, Diagnostics.Overload_signatures_must_all_be_abstract_or_non_abstract);
}
else if (deviation & ModifierFlags.Override) {
Copy link

Choose a reason for hiding this comment

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

Remember to modify const flagsToCheck below to include override. And make a test for this.

symbolToString(derived),
symbolToString(found)
);
if (!isPropertyIdenticalTo(derived, foundSymbol)) {
Copy link

Choose a reason for hiding this comment

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

This checks for identical properties, but the error message says "assignable to".

Copy link
Author

Choose a reason for hiding this comment

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

Removed check (was unnecessary)

@@ -0,0 +1,86 @@
tests/cases/compiler/noImplicitOverride.ts(11,5): error TS90034: Class member 'toString' must be marked 'override' when noImplicitOverride is enabled (augmented from Object.Object)
Copy link

Choose a reason for hiding this comment

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

Something's wrong with the way this is being rendered. We shouldn't have Object. in front of all of these. (Except e.g. Object.toString which is actually a property of Object.)

Copy link
Author

Choose a reason for hiding this comment

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

Fixed

return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "private", "override");
}
else if (node.kind === SyntaxKind.Parameter) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "override");
Copy link

Choose a reason for hiding this comment

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

Actually, I think we should allow override as a marker of a parameter property. E.g.:

abstract class A {
	abstract x: number;
}
class B extends A {
	constructor(override x: number)
}

Copy link
Author

Choose a reason for hiding this comment

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

Agreed

Copy link
Author

Choose a reason for hiding this comment

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

Done

}
}

class RejectWhenOverrideMissingOnAugmentedProperty {
Copy link

Choose a reason for hiding this comment

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

Include a positive version of this test. class A { override toString() { return ""; } } should succeed with no errors.

Copy link
Author

Choose a reason for hiding this comment

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

Done

@pcj
Copy link
Author

pcj commented May 5, 2017

Thanks @andy-ms for your review. I'll merge from master and begin addressing your comments over the weekend.

return grammarErrorOnNode(modifier, Diagnostics.Abstract_methods_can_only_appear_within_an_abstract_class);
}
if (flags & ModifierFlags.Static) {
else if (flags & ModifierFlags.Override) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "override", "abstract");
Copy link

Choose a reason for hiding this comment

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

In this situation, I think it would make sense to put an override modifier on the subclass' method:

abstract class A {
    abstract m(): void;
}
abstract class B extends A {
    abstract m(): void;
}

@ghost
Copy link

ghost commented Jul 27, 2017

@pcj bump?

@ghost
Copy link

ghost commented Sep 20, 2017

@pcj bump

@microsoft microsoft deleted a comment from msftclas Sep 27, 2017
@pcj
Copy link
Author

pcj commented Sep 28, 2017

@andy-ms Haven't forgot, sorry for the delay! Still working on this.

@ChiriVulpes
Copy link

Isn't this PR close to finished? Was it abandoned? I would really like this functionality... so what are the community's next steps here? Do you need people to step in and help out?

@pcj
Copy link
Author

pcj commented Dec 1, 2017

Sorry guys, this got de-prioritized on my end when other stuff started rolling. If I can't get his going again in the next two weeks, someone else should take over.

@pcj
Copy link
Author

pcj commented Dec 1, 2017

OK, this PR is now sync'd back up to master. Will allocate some time this weekend to address some of @andy-ms review comments.

@pcj
Copy link
Author

pcj commented Dec 3, 2017

OK, I have this mostly up to speed. Still need to address some of @andy-ms comments. Issue I am still working out is thse cases:

class A {
    protected static type: string;
}

class B extends A {
    protected static type: string = 'B';
}

In this case, the compiler should (maybe?) detect that B.type is masking A.type, and insist that B.type declaration includes the override keyword. However, in contrast to instance member variables, detecting this case is not already part of the compiler's logic. This gets back to the issue if override should be mandated in conjuction with static. There may be differing opinions on this.

The second one is somewhat esoteric that I got from @ahejlsberg in #5989:

class A {
    "constructor": typeof A;  // Explicitly declare constructor property
    protected static type: string;

    public log() {
        console.log(this.constructor.type);  // Access actual constructor object
    }
}

class B extends A {
    protected static type: string = 'B';
}

class C extends A {
    protected static type: string = 'C';
}
    class A {
        "constructor": typeof A;  // Explicitly declare constructor property
        ~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2322: Type 'typeof A' is not assignable to type 'Function'.
!!! error TS2322:   Class member '"constructor"' must be marked 'override' when noImplicitOverride is enabled (inherited from Object)
        protected static type: string;
    
        public log() {
            console.log(this.constructor.type);  // Access actual constructor object
            ~~~~~~~
!!! error TS2304: Cannot find name 'console'.
        }
    }
    
    class B extends A {
        protected static type: string = 'B';
    }
    
    class C extends A {
        protected static type: string = 'C';
    }

In this case, I am not certain this is valid syntax anymore. Also, putting override "constructor" = typeof A does not resolve the override error here, and it probably should.

@pcj
Copy link
Author

pcj commented Dec 5, 2017

Sorry to loop back on this after long delay, but I'm personally not seeing the point of using override with static members. @mhegazy you mentioned it should be possible (yes), but what is the use case for it?

Aren't users always specifying the base type constructor (like FooFactory.buildFoo(args) to access static members? I always do. Maybe there's something I'm missing.

@ghost
Copy link

ghost commented Dec 6, 2017

@pcj A static member on a subclass overrides a static member on a superclass with the same name.

class C {
    static a(): number {
        // 'this' = the class from the LHS of '.', like with an instance member.
        // So 'this.b' doesn't always mean 'C.b'.
        return this.b();
    }

    static b(): number {
        return 0;
    }
}

class D extends C {
    static b() {
        return 1;
    }
}

console.log(D.a()); // 1

In the example you gave above the compiler can currently detect an error if you write protected static type = 0; in the subclass, because it's supposed to be a string.

As for the other error, you shouldn't be getting Type 'typeof A' is not assignable to type 'Function'. -- const f: Function = A; works...

@pcj
Copy link
Author

pcj commented Dec 7, 2017

Whoa. Okay, I didn't know that! (static members accessible through the this reference). You convinced me.

@DanielRosenwasser
Copy link
Member

Hi @pcj. I'm sorry that we've let this PR linger without more direct team feedback for as long as it has. We discussed the issue and PR in our last design meeting (#20724), and after having discussed it more broadly among members of the team, we didn't feel entirely confident as to whether override is a great fit for the language at this time. One thing we'd like to experiment with is whether the need can be served by design-time decorators instead (#2900).

Again, I apologize that we didn't address this PR earlier. We reiterated the importance of transparently communicating our own positions outside of the core team. The work you've done here is great, so I hope this experience doesn't dissuade you from continuing to participate in the community and on our issue tracker.

@pcj
Copy link
Author

pcj commented Dec 16, 2017

Hi @DanielRosenwasser I understand your concerns. The feature as-is is largely complete with the exception of static / constructor inheritance that I may push up as a WIP.

If there is future support for it, it can be resurrected from here.

@pcj pcj closed this Dec 16, 2017
@armandn
Copy link

armandn commented May 16, 2018

For me personally, as a developer with experience in Java, C# and others, override feels like a much better solution than design-time decorators. I really hope this is not a final decision of the Typescript team.

@sam-s4s
Copy link

sam-s4s commented May 18, 2018

@DanielRosenwasser I know what you mean about the nature of Javascript - but the same could be argued about typing - typing is a bad fit for Javascript. But Typescript fixed that (for the most part) in the same way that it could fix the lack of override (#2000). I feel like it is a valid feature request.

@fedorl
Copy link

fedorl commented May 25, 2018

It is sad to see that such must-to-have feature has sunk in multiple year discussions about theoretical corner cases and unimportant implementation details, especially given that many mature languages have already figured all details out and there is no need to reinvent the wheel. It was much better to make a breaking change and a flag which turns it off by default.

I guess many people will be happy to turn such flag on and spend few days refactoring the entire codebase and refactor again when (and if..) the compiler 'override' implementation changes, instead of not having it at all for many years and foreseeable future.

@microsoft microsoft locked and limited conversation to collaborators Jul 31, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants