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

Report a hint for casting an expression of type T to same type T #1072

Merged
merged 13 commits into from
Nov 26, 2021

Conversation

SupunS
Copy link
Member

@SupunS SupunS commented Jul 14, 2021

Closes #1047

Description

This handles the Case-II in #1047: Expression is self-typed and is the same as casted type.
e.g:

// Invalid cases

 var x = "hello" as String    // Inner expression is already 'String'.
                              // So casting again to same type 'String' is redundant.

var y = [3, 5, 2] as [Int]    // Inner expression is already '[Int]' even without the cast.
                              // So casting again to same type '[Int]' is redundant.


// Valid cases

var z = [3, 5, 2] as [UInt8]  // Inner expression is '[UInt8]' with the cast, but would be '[Int]' if the cast is not there.
                              // So the casting is NOT redundant.

  • Targeted PR against master branch
  • Linked to Github issue with discussion and accepted design OR link to spec that describes this work
  • Code follows the standards mentioned here
  • Updated relevant documentation
  • Re-reviewed Files changed in the Github PR explorer
  • Added appropriate labels

@SupunS SupunS requested a review from turbolent as a code owner July 14, 2021 07:58
@SupunS SupunS self-assigned this Jul 14, 2021
@SupunS SupunS added the Feature label Jul 14, 2021
@codecov-commenter
Copy link

codecov-commenter commented Jul 14, 2021

Codecov Report

Merging #1072 (9c99970) into master (2ea862a) will increase coverage by 0.02%.
The diff coverage is 87.80%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1072      +/-   ##
==========================================
+ Coverage   77.28%   77.31%   +0.02%     
==========================================
  Files         274      275       +1     
  Lines       35414    35492      +78     
==========================================
+ Hits        27371    27439      +68     
- Misses       6958     6964       +6     
- Partials     1085     1089       +4     
Flag Coverage Δ
unittests 77.31% <87.80%> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
runtime/sema/check_cast_visitor.go 84.84% <84.84%> (ø)
runtime/sema/check_casting_expression.go 96.73% <100.00%> (+0.16%) ⬆️
runtime/sema/check_expression.go 100.00% <100.00%> (ø)
runtime/sema/checker.go 89.36% <100.00%> (+0.02%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 2ea862a...9c99970. Read the comment docs.

@SupunS SupunS force-pushed the supun/warn-cast-2 branch from e6a8cfa to a8c7784 Compare November 9, 2021 18:36
@SupunS SupunS requested a review from dsainati1 November 9, 2021 18:41
// - Case I: Contextually expected type is same as casted type (target type).
// - Case II: Expression is self typed, and is same as the casted type (target type).
func IsCastRedundant(expr ast.Expression, exprInferredType, targetType, expectedType Type) bool {
if expectedType != nil && expectedType.Equal(targetType) {
Copy link
Contributor

Choose a reason for hiding this comment

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

While Cadence doesn't currently have type aliases, we can probably future proof this against such a feature by changing this equal check to instead check whether both expectedType and targetType are subtypes of each other. At the moment this seems like it will only give the redundant cast hint when the two types are syntactically equal, but not when they are semantically equal.

Copy link
Member Author

@SupunS SupunS Nov 9, 2021

Choose a reason for hiding this comment

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

Hmm, that's a good point! But that might depend on how we define equality and casting for type aliases. Like for e.g: In Go, type aliases are 'equal' (no need to cast) at the user level, whereas typedefs are not (need to cast) (i.e: https://yourbasic.org/golang/type-alias/), and it would come down to how we are going to implement it for Cadence.

I totally agree that we need to be mindful of this when we do the aliasing, but wondering it might be overkill to do at this point, without knowing much about how aliasing going to work.
We use this equal method across the checker, and would need to update all such places when we introduce aliasing.

// var y = x as Int8 // <-- not ok: `y` will be of type `Int8` with/without cast
// var y = x as Integer // <-- ok : `y` will be of type `Integer`
return exprType != nil &&
exprType.Equal(targetType)
Copy link
Contributor

Choose a reason for hiding this comment

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

same as above

Copy link
Member

@turbolent turbolent left a comment

Choose a reason for hiding this comment

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

Nice work!

Comment on lines 434 to 436
checkCastVisitor := &CheckCastVisitor{}

return checkCastVisitor.isCastRedundant(expr, exprInferredType, targetType)
Copy link
Member

Choose a reason for hiding this comment

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

Great idea to factor the casting check into its own type, though I think there is no need for a pointer type here (only the mutating functions need a pointer receiver):

Suggested change
checkCastVisitor := &CheckCastVisitor{}
return checkCastVisitor.isCastRedundant(expr, exprInferredType, targetType)
return CheckCastVisitor{}.isCastRedundant(expr, exprInferredType, targetType)

Copy link
Member Author

Choose a reason for hiding this comment

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

CheckCastVisitor maintains an updates the exprInferredType and targetType as it goes into nested expressions (e.g: array/dictionary literals). So going to need a pointer receiver.

Copy link
Member

Choose a reason for hiding this comment

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

Right, the mutating functions should have a pointer receiver, but the value here itself doesn't have to be, see e.g. https://go.dev/play/p/iF22kMUH_MV

Copy link
Member Author

Choose a reason for hiding this comment

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

(d *CheckCastVisitor) IsRedundantCast() is actually the mutating function, and is recursively invoked by the others.

@bluesign
Copy link
Contributor

I know it is not totally related but below will give hint?

  var a: String = "deniz"
  var b = &a as &String

Currently I guess the one below is not possible also.

  var a: String = "deniz"
  var b = &a 

@turbolent
Copy link
Member

@bluesign &t as &T is actually not a casting expression, but an expression that creates a reference, it just happens to have the "same" syntax, so I think this PR won't address this. In fact, we require the explicit type when taking a reference, so that the code clearly states what the type of the reference is (e.g. it might be a different type, or an auth reference)

@github-actions
Copy link

github-actions bot commented Nov 20, 2021

Cadence Benchstat comparison

This branch with compared with the base branch onflow:master commit 2ea862a
The command go test ./... -run=XXX -bench=. -shuffle=on -count N was used.
Bench tests were run a total of 7 times on each branch.

Results

old.txtnew.txt
time/opdelta
InterpretRecursionFib-22.92ms ± 6%3.06ms ± 4%+4.80%(p=0.011 n=7+7)
ParseDeploy/decode_hex-21.51ms ± 2%1.58ms ± 3%+4.21%(p=0.001 n=7+7)
NewInterpreter/new_interpreter-21.23µs ± 2%1.25µs ± 3%+1.64%(p=0.026 n=6+6)
RuntimeFungibleTokenTransfer-21.44ms ± 3%1.55ms ±29%~(p=0.945 n=6+7)
RuntimeResourceDictionaryValues-218.2ms ± 6%17.5ms ± 2%~(p=0.073 n=7+7)
ParseArray-225.0ms ± 4%24.6ms ± 3%~(p=0.383 n=7+7)
ParseDeploy/byte_array-236.7ms ± 3%37.6ms ± 4%~(p=0.165 n=7+7)
ParseInfix-226.5µs ± 3%26.2µs ± 3%~(p=0.383 n=7+7)
ParseFungibleToken-2500µs ± 3%503µs ± 3%~(p=0.535 n=7+7)
QualifiedIdentifierCreation/One_level-23.43ns ± 1%3.42ns ± 3%~(p=1.000 n=5+7)
QualifiedIdentifierCreation/Three_levels-2172ns ± 3%169ns ± 1%~(p=0.182 n=7+5)
ContractInterfaceFungibleToken-251.5µs ± 3%52.0µs ± 5%~(p=0.731 n=6+7)
CheckContractInterfaceFungibleTokenConformance-2179µs ± 2%176µs ± 3%~(p=0.128 n=7+7)
NewInterpreter/new_sub-interpreter-22.30µs ± 3%2.35µs ± 6%~(p=0.476 n=7+7)
RuntimeStorageWriteCached-2190µs ±30%150µs ± 5%−20.96%(p=0.001 n=7+7)
 
alloc/opdelta
RuntimeStorageWriteCached-283.7kB ± 0%83.7kB ± 0%~(p=0.767 n=6+7)
RuntimeFungibleTokenTransfer-2233kB ± 0%233kB ± 0%~(p=0.402 n=7+7)
RuntimeResourceDictionaryValues-24.34MB ± 0%4.34MB ± 0%~(p=0.805 n=7+7)
QualifiedIdentifierCreation/One_level-20.00B 0.00B ~(all equal)
QualifiedIdentifierCreation/Three_levels-264.0B ± 0%64.0B ± 0%~(all equal)
ContractInterfaceFungibleToken-226.7kB ± 0%26.7kB ± 0%~(all equal)
CheckContractInterfaceFungibleTokenConformance-266.4kB ± 0%66.4kB ± 0%~(p=0.592 n=7+7)
InterpretRecursionFib-21.21MB ± 0%1.21MB ± 0%~(p=0.245 n=6+7)
NewInterpreter/new_interpreter-2680B ± 0%680B ± 0%~(all equal)
NewInterpreter/new_sub-interpreter-21.06kB ± 0%1.06kB ± 0%~(all equal)
 
allocs/opdelta
RuntimeStorageWriteCached-21.42k ± 0%1.42k ± 0%~(all equal)
RuntimeFungibleTokenTransfer-24.42k ± 0%4.42k ± 0%~(p=0.070 n=7+7)
RuntimeResourceDictionaryValues-2108k ± 0%108k ± 0%~(p=0.744 n=7+7)
QualifiedIdentifierCreation/One_level-20.00 0.00 ~(all equal)
QualifiedIdentifierCreation/Three_levels-22.00 ± 0%2.00 ± 0%~(all equal)
ContractInterfaceFungibleToken-2458 ± 0%458 ± 0%~(all equal)
CheckContractInterfaceFungibleTokenConformance-21.07k ± 0%1.07k ± 0%~(all equal)
InterpretRecursionFib-225.0k ± 0%25.0k ± 0%~(all equal)
NewInterpreter/new_interpreter-211.0 ± 0%11.0 ± 0%~(all equal)
NewInterpreter/new_sub-interpreter-231.0 ± 0%31.0 ± 0%~(all equal)
 

Copy link
Member

@turbolent turbolent left a comment

Choose a reason for hiding this comment

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

Looks good, nice work!

Should be ready to get merged once gated behind a disabled option, so we don't always run this (especially not on production).

@SupunS
Copy link
Member Author

SupunS commented Nov 24, 2021

@turbolent It was added in 11e9ecc. It's turned off by default.
Need to bump the version in LS to enable it there. I guess we should do that separately.

@turbolent
Copy link
Member

@SupunS Ah, my bad, I had missed it 👍

Copy link
Member

@turbolent turbolent left a comment

Choose a reason for hiding this comment

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

Looks good, great work!

Just one line missing in NewSubInterpreter (see comment), but then should be ready to be merged 👍

// WithLintingEnabled returns a checker option which enables/disables
// advanced linting.
//
func WithLintingEnabled(enabled bool) Option {
Copy link
Member

Choose a reason for hiding this comment

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

Must also be propagated to sub-interpreters in NewSubInterpreter

Copy link
Member Author

Choose a reason for hiding this comment

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

This flag is for the checker. I think we don't need to pass it down?

@SupunS SupunS merged commit c110647 into master Nov 26, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Report warnings for redundant casts
5 participants