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

Generate unused variable warnings in top level statements #7023

Merged
merged 10 commits into from Jan 30, 2017

Conversation

KingOfBrian
Copy link
Contributor

This PR walks the VarDeclUsageChecker on SourceFile.Decls to generate warnings for statements that aren't contained in abstract function declarations. This also disables the unused variable warnings for global variables since tracking global variables is complicated and felt like it was outside the scope of this bug.

Resolves SR-2115.
I force pushed over my first attempt at this (#6971) to clean up history. I forgot it would kill the PR.

@slavapestov
Copy link
Member

@swift-ci Please test

@slavapestov
Copy link
Member

Nice!

@swift-ci
Copy link
Collaborator

Build failed
Jenkins build - Swift Test OS X Platform
Git Commit - edc193e
Test requested by - @slavapestov

@@ -731,6 +732,7 @@ void swift::performWholeModuleTypeChecking(SourceFile &SF) {
Ctx.diagnoseObjCMethodConflicts(SF);
Ctx.diagnoseObjCUnsatisfiedOptReqConflicts(SF);
Ctx.diagnoseUnintendedObjCMethodOverrides(SF);
performTopLevelDeclDiagnostics(SF);
Copy link
Contributor

Choose a reason for hiding this comment

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

This section is only for diagnostics that apply across files. performTopLevelDeclDiagnostics is something we can just do in the regular performTypeChecking, and ideally only on the newly-added decls.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch! I actually only put it there because I was originally exploring cross-file global variable checking, which turned out to be more complicated. I logged SR-3721 to track that. I'll move this to a better place.

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 moved the invocation but I'm not sure I followed the ideally only on the newly-added decls comment.

@@ -691,6 +692,7 @@ void swift::performTypeChecking(SourceFile &SF, TopLevelContext &TLC,
TC.processREPLTopLevel(SF, TLC, StartElem);

typeCheckFunctionsAndExternalDecls(TC);
performTopLevelDeclDiagnostics(SF);
Copy link
Contributor

Choose a reason for hiding this comment

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

…and now that I look at it a second time, does it make sense to sink it into typeCheckTopLevelCodeDecl? That way we don't have to walk things twice.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nice! This also un-covered a lot of masked warnings since I shared the VarDeclUsageChecker across multiple statements. This produced a bunch of new UT failures that I fixed and also un-covered a bug when guard adds a variable to the global scope.

@KingOfBrian
Copy link
Contributor Author

@swift-ci Please test

Copy link
Contributor

@jrose-apple jrose-apple left a comment

Choose a reason for hiding this comment

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

Ah, I see the problem. We would need the same VarDeclUsageChecker to walk all of the TLCDs in order to track usage of local variables. Okay, sorry for the bad advice; I think it was better as a full pass where you can catch guard let as well.

D->walk(checker);
}
void swift::
performTopLevelDeclDiagnostics(TypeChecker &TC, TopLevelCodeDecl &TLCD) {
Copy link
Contributor

Choose a reason for hiding this comment

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

For whatever reason, we don't really pass AST nodes as references, just pointers. Please use that convention for now.

@@ -1585,4 +1585,6 @@ void TypeChecker::typeCheckTopLevelCodeDecl(TopLevelCodeDecl *TLCD) {
StmtChecker(*this, TLCD).typeCheckStmt(Body);
TLCD->setBody(Body);
checkTopLevelErrorHandling(TLCD);
if (TLCD)
Copy link
Contributor

Choose a reason for hiding this comment

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

Since TLCD is dereferenced two lines above, it should be okay to drop this check.

@KingOfBrian
Copy link
Contributor Author

Nope, the advice was good, and good to know about the conventions. I logged SR-3721 for unused global variable warnings since I think that would still be a valuable feature, especially for scripting.

@jrose-apple
Copy link
Contributor

Yeah, unused (and only-written) variables are good things to catch.

Do you disagree with my comment about guard let, for this patch?

@KingOfBrian
Copy link
Contributor Author

I don't think we can do a better job with guard statements under a TLCD. Since they introduce global variables, and global variables can't be effectively tracked without more work. I'm interested expanding the checker to work better as scopes change, but would need some help making sure I have a good strategy, because the walking strategy would need to be changed substantially. I'll add some more notes on issues I've seen in the checker to SR-3721.

});
}
}
else if (node.is<Stmt *>()) {
Copy link
Member

@rintaro rintaro Jan 27, 2017

Choose a reason for hiding this comment

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

nitpick: } else if . (no new-line after })

@slavapestov
Copy link
Member

@KingOfBrian The new ASTScope stuff being worked on by @DougGregor might help with modeling bindings introduced by guard statements...

@slavapestov slavapestov self-assigned this Jan 27, 2017
@jrose-apple
Copy link
Contributor

Ah, guard statements do not introduce global variables. You can see this in the AST dump:

let x = 5
guard let y = x as Optional else { fatalError() }
(source_file
  (top_level_code_decl
    (brace_stmt
      (pattern_binding_decl
        (pattern_named type='Int' 'x')
        (call_expr implicit type='Int' location=<stdin>:1:9 range=[<stdin>:1:9 - line:1:9] nothrow  arg_labels=_builtinIntegerLiteral:
          (constructor_ref_call_expr implicit type='(Int2048) -> Int' location=<stdin>:1:9 range=[<stdin>:1:9 - line:1:9] nothrow
            (declref_expr implicit type='(Int.Type) -> (Int2048) -> Int' location=<stdin>:1:9 range=[<stdin>:1:9 - line:1:9] decl=Swift.(file).Int.init(_builtinIntegerLiteral:) function_ref=single specialized=no)
            (type_expr implicit type='Int.Type' location=<stdin>:1:9 range=[<stdin>:1:9 - line:1:9] typerepr='Int'))
          (tuple_expr implicit type='(_builtinIntegerLiteral: Int2048)' location=<stdin>:1:9 range=[<stdin>:1:9 - line:1:9] names=_builtinIntegerLiteral
            (integer_literal_expr type='Int2048' location=<stdin>:1:9 range=[<stdin>:1:9 - line:1:9] value=5))))
))
  (var_decl "x" type='Int' access=internal let storage_kind=stored)
  (top_level_code_decl
    (brace_stmt
      (guard_stmt
        (pattern
          (optional_some_element implicit type='Optional<Int>'
            (pattern_let implicit type='Int'
              (pattern_named type='Int' 'y')))
          (coerce_expr type='Optional<Int>' location=<stdin>:2:17 range=[<stdin>:2:15 - line:2:20] writtenType='Optional<Int>'
            (inject_into_optional implicit type='Optional<Int>' location=<stdin>:2:15 range=[<stdin>:2:15 - line:2:15]
              (declref_expr type='Int' location=<stdin>:2:15 range=[<stdin>:2:15 - line:2:15] decl=main.(file).x@<stdin>:1:5 direct_to_storage function_ref=unapplied specialized=no))))
        (brace_stmt
          (call_expr type='Never' location=<stdin>:2:36 range=[<stdin>:2:36 - line:2:47] nothrow  arg_labels=
            (declref_expr type='(@autoclosure () -> String, StaticString, UInt) -> Never' location=<stdin>:2:36 range=[<stdin>:2:36 - line:2:36] decl=Swift.(file).fatalError(_:file:line:) function_ref=single specialized=no)
            (tuple_shuffle_expr implicit type='(@autoclosure () -> String, file: StaticString, line: UInt)' location=<stdin>:2:46 range=[<stdin>:2:46 - line:2:47] elements=[-1, -3, -3] variadic_sources=[]
              (tuple_expr type='()' location=<stdin>:2:46 range=[<stdin>:2:46 - line:2:47]))))))))

Note the VarDecl for x is outside the TLCDs, but there isn't one for y.

@slavapestov: I don't think we can count on getting ASTScope any time soon.

@jrose-apple
Copy link
Contributor

P.S. swiftc -ast-dump :-)

@rintaro
Copy link
Member

rintaro commented Jan 27, 2017

It used to (8e14bba), but it's disabled by you in 4cdac3f :)

@jrose-apple
Copy link
Contributor

Deliberately so. It never makes sense to access a guard let variable from another file because it might not be there!

@KingOfBrian
Copy link
Contributor Author

KingOfBrian commented Jan 27, 2017

Oh neat, I actually didn't realize file scope was possible without access control modifiers. Still the VarDeclUsageChecker doesn't work well scoping variables through TypeDecl's or AbstractFuncDecls. Currently an inner function squashes the checker since the func decl is not type checked when the checker runs. ie:

func outerFunction() {
    var noWarning: String? = "due to innerFunction not being typechecked"
    func innerFunction() { // This AbstractFuncDecl removes any variables in the checker
        var warningGenerated: String? = ""
    }
    var warningGenerated: String? = ""
}

It also doesn't traverse types at all so the following also won't be detected:

let global: String? = ""
guard let file = global else { fatalError() }

class Foo { // TypeDecl's are not descended
    let variable = file // This usage won't be flagged
}

The above isn't an issue currently, since the checker never runs on variables that a type can close over.

So I'm not sure how much better this can get without a new abstraction, and it sounds like one is planned. If you think this fix should get punted till that abstraction is in place, that's cool by me. I learned a lot about how the driver worked and got somewhat comfortable with my first ASTWalker!

@jrose-apple
Copy link
Contributor

Heh, that latter code snippet actually crashes if you get to SILGen. Arguably you shouldn't be allowed to do that; as before, the class might be referenced from another file. You've convinced me that this is a reasonable limitation for now.

@jrose-apple
Copy link
Contributor

@swift-ci Please smoke test

@KingOfBrian
Copy link
Contributor Author

Also, to be less defeatist about this, we could invoke this checker a bit later in the type check phase. If the tree was fully typechecked I think we could check function usages better and add support for the class traversal -- but the former may be expensive because it may require another walk, and the later may actually not be a desired feature to support.

@jrose-apple
Copy link
Contributor

I'm happy to come back and improve this later. Right now this'll at least catch a few more cases than we did before.

@KingOfBrian
Copy link
Contributor Author

@swift-ci Please smoke test

@slavapestov
Copy link
Member

@swift-ci Please smoke test

1 similar comment
@slavapestov
Copy link
Member

@swift-ci Please smoke test

@slavapestov slavapestov merged commit 71cf245 into apple:master Jan 30, 2017
@KingOfBrian KingOfBrian deleted the bugfix/SR-2115 branch January 30, 2017 04:35
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.

None yet

5 participants