Skip to content

[AutoDiff][SR-13152] Better diagnostic for static decl modifier mismatch. #36128

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

Merged
merged 8 commits into from
Mar 3, 2021

Conversation

vguerra
Copy link
Contributor

@vguerra vguerra commented Feb 24, 2021

Improved diagnostic for when the registered derivative function and
the function it derivates (the original) differ in terms of
static declaration modifier usage. Suggesting as well a fix-it,
to either remove or add the static keyword.

The registered derivative needs to be marked as static in two cases:

  1. When the original function is a constructor.
  2. When the original function is static as well.

When the original function is an instance method, the registered derivate
must be as well.

Given the following example:

struct Foo: Differentiable {
    static func sf1(_ x : Float) -> Float {
        return x * x * x
    }
}

extension Foo {
    @derivative(of: sf1)
    func vjpF1(_ x: Float) -> (value: Float, pullback: (Float.TangentVector) -> (Foo.TangentVector, Float.TangentVector)) {
        fatalError()
    }
}

Before this PR:

sr_13152.swift:25:21: error: referenced declaration 'sf1' could not be resolved
    @derivative(of: sf1)
                    ^
sr_13152.swift:19:17: note: candidate static method does not have expected type '(Foo) -> (Float) -> Float'
    static func sf1(_ x : Float) -> Float {

After this PR:

sr_13152.swift:13:21: error: unexpected derivative function declaration; 'sf1' requires the derivative function 'vjpF1' to be a 'static' method
    @derivative(of: sf1)
                    ^
sr_13152.swift:7:17: note: original function 'sf1' is a 'static' method
    static func sf1(_ x : Float) -> Float {
                ^
sr_13152.swift:14:5: note: make derivative function 'vjpF1' a 'static' method
    func vjpF1(_ x: Float) -> (value: Float, pullback: (Float.TangentVector) -> (Foo.TangentVector, Float.TangentVector)) {
    ^
    static 

Resolves SR-13152.

…match.

Improved diagnostic for when the registered derivative function and
the function it derivates (the original) differ in terms of
`static` declaration modifier usage. Suggesting as well a fix-it,
to either remove or add the `static` keyword.

The registered derivative needs to be marked as `static` in two cases:

1. When the original function is a constructor.
2. When the original function is static as well.

When the original function is an instance method, the registered derivate
must be as well.

Given the following example:

```swift
struct Foo: Differentiable {
    static func sf1(_ x : Float) -> Float {
        return x * x * x
    }
}

extension Foo {
    @Derivative(of: sf1)
    func vjpF1(_ x: Float) -> (value: Float, pullback: (Float.TangentVector) -> (Foo.TangentVector, Float.TangentVector)) {
        fatalError()
    }
}
```

Before this PR:
```bash
sr_13152.swift:25:21: error: referenced declaration 'sf1' could not be resolved
    @Derivative(of: sf1)
                    ^
sr_13152.swift:19:17: note: candidate static method does not have expected type '(Foo) -> (Float) -> Float'
    static func sf1(_ x : Float) -> Float {
```

After this PR:
```bash
sr_13152.swift:26:10: error: function must match usege of static declaration modifier from original function 'vjpF1'
    func vjpF1(_ x: Float) -> (value: Float, pullback: (Float.TangentVector) -> (Foo.TangentVector, Float.TangentVector)) {
         ^
sr_13152.swift:26:5: note: mark the function 'vjpF1' as static
    func vjpF1(_ x: Float) -> (value: Float, pullback: (Float.TangentVector) -> (Foo.TangentVector, Float.TangentVector)) {
    ^
    static
```

Resolves [SR-13152](https://bugs.swift.org/browse/SR-13152).
@@ -4007,7 +4007,9 @@ static bool checkFunctionSignature(
if (!std::equal(required->getParams().begin(), required->getParams().end(),
candidateFnTy->getParams().begin(),
[&](AnyFunctionType::Param x, AnyFunctionType::Param y) {
return x.getOldType()->isEqual(mapType(y.getOldType()));
auto xInstanceTy = x.getOldType()->getMetatypeInstanceType();
auto yInstanceTy = y.getOldType()->getMetatypeInstanceType();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

when either required or candidateFnTy are defined as static, the parameters are MetaTypes, hence I try to look into the instance type to be able to make this comparison work. Otherwise, this will fail and trigger the diagnostic we see today. Do you see any issues with this here?

Copy link
Contributor

Choose a reason for hiding this comment

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

I recommend checking for static-ness mismatches in differentiability witnesses before checking for signature mismatches. That'll let you diagnose type mismatches as a separate thing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The signature mismatches are done during the process of looking up for the original function that the derivative is trying to derivate, that's why I modified this check so that the lookup does discards the candidate due to static-ness mismatch. Once the original candidate is found, I can do the static mismatch checks I introduced in this PR.

@@ -4581,6 +4583,42 @@ void AttributeChecker::visitDifferentiableAttr(DifferentiableAttr *attr) {
(void)attr->getParameterIndices();
}

/// Checks if original candidate and registered derivative match in terms
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you think there is a better place to move this utility functions to?

Copy link
Contributor

Choose a reason for hiding this comment

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

The treatment of constructors as static functions for the purpose of differentiability witness matching is pretty unique. I think this is where it needs to be.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok, great! thanks.

@dan-zheng dan-zheng requested review from rxwei and dan-zheng February 24, 2021 11:27
@@ -3264,6 +3264,14 @@ ERROR(autodiff_attr_result_not_differentiable,none,
"'Differentiable', but %0 does not conform to 'Differentiable'", (Type))
ERROR(autodiff_attr_opaque_result_type_unsupported,none,
"cannot differentiate functions returning opaque result types", ())
ERROR(autodiff_attr_static_decl_mismatch,none,
"function must match usage of static declaration modifier "
Copy link
Contributor

@CodaFi CodaFi Feb 25, 2021

Choose a reason for hiding this comment

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

How about

Suggested change
"function must match usage of static declaration modifier "
"derivative function %0 operates on %select{an instance|a type}1, not %select{an instance|a type}2 as required"

Copy link
Contributor

Choose a reason for hiding this comment

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

You can point back at the original function with a note.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for the suggestion. I changed the working of the error diagnostic and adapted the subsequent notes accordingly in cfd7932

/// Produces diagnostics for mismatch in static/instance method declaration
/// between original candidate and registered derivative.
static void diagnoseStaticDeclMismatch(AbstractFunctionDecl *originalCandidate,
FuncDecl *registred) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Please run git-clang-format on this patch.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I made sure the code is clang-formatted. Fixed in cfd7932.

- git-clang-format on this PR.
- rewording diagnostic messages.
@vguerra vguerra requested a review from CodaFi February 26, 2021 22:35
@vguerra vguerra marked this pull request as ready for review February 26, 2021 22:35
(/*derivative*/ DeclName, /*derivativeOperatesOnTy*/bool,
/*originalOperatesOnTy*/bool))
NOTE(autodiff_attr_static_decl_original,none,
"original function %0 operates on %select{an instance|a}1 type",
Copy link
Contributor

@rxwei rxwei Feb 27, 2021

Choose a reason for hiding this comment

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

I'm not sure "operates on a type" is the right user-facing error message, as it sounds quite uncommon in existing Swift diagnostics. Plus, "an instance type" doesn't sound quite right — shouldn't it just be "an instance"?

Instead of "operates on something", how about something like "original function 'foo(_:)' is {an instance|a static} method"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed, your suggestion seems more aligned with wording already existing in the diagnostic error messages.
I'll make this note disappear and include your suggestion in the error diagnostic itself. Thanks!

Comment on lines 4591 to 4599
static bool checkStaticDeclMismatch(AbstractFunctionDecl *originalCandidate,
AbstractFunctionDecl *registered) {
return (isa<ConstructorDecl>(originalCandidate) ||
originalCandidate->isStatic()) != registered->isStatic();
}

/// Produces diagnostics for mismatch in static/instance method declaration
/// between original candidate and registered derivative.
static void diagnoseStaticDeclMismatch(AbstractFunctionDecl *originalCandidate,
Copy link
Contributor

Choose a reason for hiding this comment

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

TypeCheckerAttr.cpp is not an AD-specific file, so if we are to define these as static functions we should make sure these two AD-specific helper functions have AD-specific names. That said, for better locality and readability, I'd recommend defining these as lambdas in typeCheckDerivativeAttr right above the call sites.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I started defining these helper functions as you suggested as lambdas within typeCheckDerivativeAttr but the diagnostics apply as well for the @transpose attribute. Type-checking for @transpose attribute happens in visitTransposeAttr hence the definitions here so that we can reuse them in both places. I could define twice the a lambda that performs the checks as it is not much code. wdyt?

Copy link
Contributor

@rxwei rxwei Mar 2, 2021

Choose a reason for hiding this comment

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

I’m planning on revamping @transpose typing rules so the diagnostics may not apply to both anymore. So duplicating the core for now sounds good.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have pushed a new commit that duplicates the check and diagnostics for both attribute type check routines. As well, I have split the diagnostic messages for both attributes given that in one case one refers to the derivative function and to the transpose function in the other, plus it would be easier to remove them for the '@transpose' attribute if they need to go as a result of your refactoring.

// expected-note @+1 {{original function 'staticMethod' operates on a type}}
static func staticMethod(_ x: T) -> T { x }

@derivative(of: init)
Copy link
Contributor

Choose a reason for hiding this comment

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

In these diagnostics we should make the error appear at the location that triggered the error. For example, in this case vjpInit(_:) is a perfectly fine Swift function if it were compiled directly. The compilation error is caused by the addition of the @derivative(of: init) attribute, so the error should appear on the attribute, not on vjpInit(_:).

@derivative(of: init)
                ^~~~

The error should also say "as required by what" instead of just "as required" in order to provide the user with the sufficient context. In this case, maybe it can say something like:

error: unexpected derivative function declaration; 'StaticMismatch.init(_:)' requires that the derivative function be 'static'

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, you are right. It seems a bit weird to have a note produced "far away" from the error itself. I'll address your comment in a soon-to-come commit :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have modified the wording of the error messages and notes to be more aligned to what you suggested. The error diagnostic is now pointing at the method stated in the 'of:' parameter.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@derivative(of: init)
                ^~~~

I overlooked your suggestion here of highlighting the original function (sorry for that :/ ). I've seen other diagnostics in this context do highlighting as well so I just pushed #36259 for the sake of consistency.

@@ -3264,6 +3264,18 @@ ERROR(autodiff_attr_result_not_differentiable,none,
"'Differentiable', but %0 does not conform to 'Differentiable'", (Type))
ERROR(autodiff_attr_opaque_result_type_unsupported,none,
"cannot differentiate functions returning opaque result types", ())
ERROR(autodiff_attr_static_decl_mismatch,none,
Copy link
Contributor

Choose a reason for hiding this comment

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

Aren't these specific to the @derivative attribute? Should they be moved near derivative_attr_* diagnostics (around line 3171)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As I mentioned before the same diagnostic can apply to the @transpose attr. hence the definition in this section as the wording is not specific to @derivative nor @transpose attributes. Otherwise I would have to define them twice.

Copy link
Contributor

@rxwei rxwei Mar 2, 2021

Choose a reason for hiding this comment

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

Sounds good. The diagnostic name can use some clarification, something along the lines of autodiff_attr_instance_method_mismatching_original, basically spelling out the exact mismatch condition as succinctly as possible.

vguerra added 2 commits March 2, 2021 23:28
* splitting diagnostic messages for `@derivative` and `@transpose` attr.
* duplicating the diagnostic logic using lambda functions within the
  respective attr checking methods.
* change wording of error/notes to use non-`static`|`static`.
@vguerra vguerra requested a review from rxwei March 2, 2021 23:04
@rxwei
Copy link
Contributor

rxwei commented Mar 3, 2021

@swift-ci please test

@swift-ci
Copy link
Contributor

swift-ci commented Mar 3, 2021

Build failed
Swift Test Linux Platform
Git Sha - 38433cf

@rxwei
Copy link
Contributor

rxwei commented Mar 3, 2021

@swift-ci please test Linux

@rxwei rxwei merged commit f81aba7 into swiftlang:main Mar 3, 2021
@rxwei
Copy link
Contributor

rxwei commented Mar 3, 2021

Thanks Victor!

vguerra added a commit to vguerra/swift that referenced this pull request Mar 3, 2021
Small follow up of [PR-36128](swiftlang#36128)
to be consistent with other diagnostics emitted when type-checking
`@derivative` and `@transpose` attributes.
rxwei pushed a commit that referenced this pull request Mar 5, 2021
Small follow up of [PR-36128](#36128)
to be consistent with other diagnostics emitted when type-checking
`@derivative` and `@transpose` attributes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants