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

Type Checking for Attachment Declarations #2082

Merged
merged 29 commits into from
Dec 1, 2022

Conversation

dsainati1
Copy link
Contributor

@dsainati1 dsainati1 commented Oct 20, 2022

Part of #357 and #2061

Description

This PR implements type checking for attachments. Attachments are implemented as a new kind of composite type with some special additional functionality relating to its base type. In particular, in the body of an attachment's function members, we introduce a new super variable in addition to self, whose type is a reference to the base type of the attachment.

A couple particular points to note:

  • Attachments may not be interfaces
  • Attachment base types may be structs, resources, struct interfaces, resource interfaces, AnyStruct or AnyResource.
  • Attachment types cannot be used directly, as attachments are not first-class values. They must be used through references, or Cadence will raise a static error
  • Attachments may only be instantiated inside an attach expression, which will be implemented in a later PR

  • 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

@dsainati1 dsainati1 requested a review from a team October 20, 2022 17:34
@dsainati1 dsainati1 self-assigned this Oct 20, 2022
@dsainati1 dsainati1 changed the base branch from master to sainati/extension-parsing October 20, 2022 17:34
@codecov
Copy link

codecov bot commented Oct 20, 2022

Codecov Report

Merging #2082 (75b9cda) into feature/attachments (e37e233) will increase coverage by 0.06%.
The diff coverage is 89.79%.

@@                   Coverage Diff                   @@
##           feature/attachments    #2082      +/-   ##
=======================================================
+ Coverage                77.92%   77.99%   +0.06%     
=======================================================
  Files                      308      308              
  Lines                    64362    64623     +261     
=======================================================
+ Hits                     50156    50402     +246     
- Misses                   12401    12413      +12     
- Partials                  1805     1808       +3     
Flag Coverage Δ
unittests 77.99% <89.79%> (+0.06%) ⬆️

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

Impacted Files Coverage Δ
runtime/common/compositekind_string.go 40.00% <ø> (ø)
runtime/common/declarationkind_string.go 40.00% <ø> (ø)
runtime/compiler/compiler.go 55.35% <0.00%> (-0.60%) ⬇️
runtime/interpreter/interpreter.go 89.08% <0.00%> (-0.10%) ⬇️
runtime/sema/typeannotationstate_string.go 0.00% <ø> (ø)
runtime/common/declarationkind.go 78.81% <20.00%> (+0.74%) ⬆️
runtime/ast/members.go 95.45% <50.00%> (-2.17%) ⬇️
runtime/ast/visitor.go 83.33% <50.00%> (-1.22%) ⬇️
runtime/common/compositekind.go 90.09% <70.00%> (-2.30%) ⬇️
runtime/ast/memberindices.go 93.81% <75.00%> (-2.66%) ⬇️
... and 12 more

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

@github-actions
Copy link

github-actions bot commented Oct 20, 2022

Cadence Benchstat comparison

This branch with compared with the base branch onflow:feature/attachments commit 269df27
The command for i in {1..N}; do go test ./... -run=XXX -bench=. -benchmem -shuffle=on; done was used.
Bench tests were run a total of 7 times on each branch.

Collapsed results for better readability

old.txtnew.txt
time/opdelta
CheckContractInterfaceFungibleTokenConformance-2129µs ± 7%124µs ± 3%~(p=0.073 n=7+6)
ContractInterfaceFungibleToken-245.5µs ± 8%44.6µs ±11%~(p=0.710 n=7+7)
InterpretRecursionFib-22.51ms ± 6%2.39ms ± 2%−4.94%(p=0.008 n=7+6)
NewInterpreter/new_interpreter-21.20µs ± 4%1.16µs ± 7%~(p=0.234 n=6+7)
NewInterpreter/new_sub-interpreter-2628ns ± 9%607ns ± 5%~(p=0.259 n=7+7)
ParseArray-28.33ms ± 5%7.97ms ± 5%−4.27%(p=0.017 n=7+7)
ParseDeploy/byte_array-212.5ms ± 4%12.1ms ± 5%~(p=0.128 n=7+7)
ParseDeploy/decode_hex-21.25ms ± 2%1.23ms ± 4%~(p=0.128 n=7+7)
ParseFungibleToken/With_memory_metering-2194µs ± 5%188µs ± 4%~(p=0.053 n=7+7)
ParseFungibleToken/Without_memory_metering-2154µs ± 4%149µs ± 4%~(p=0.073 n=7+7)
ParseInfix-27.10µs ± 4%7.02µs ± 9%~(p=0.318 n=7+7)
QualifiedIdentifierCreation/One_level-22.35ns ± 0%2.45ns ±10%~(p=0.740 n=7+7)
QualifiedIdentifierCreation/Three_levels-2141ns ± 7%142ns ± 7%~(p=0.596 n=7+7)
RuntimeFungibleTokenTransfer-2721µs ±20%654µs ±27%~(p=0.318 n=7+7)
RuntimeResourceDictionaryValues-25.85ms ±12%5.45ms ±19%~(p=0.165 n=7+7)
RuntimeScriptNoop-223.9µs ±30%26.7µs ± 5%~(p=0.731 n=7+6)
SuperTypeInference/arrays-2326ns ± 6%319ns ± 5%~(p=0.383 n=7+7)
SuperTypeInference/composites-2130ns ± 1%134ns ± 1%+2.60%(p=0.001 n=7+7)
SuperTypeInference/integers-289.6ns ± 0%90.7ns ± 0%+1.15%(p=0.002 n=6+6)
ValueIsSubtypeOfSemaType-298.6ns ±11%93.5ns ±10%~(p=0.073 n=7+7)
 
alloc/opdelta
CheckContractInterfaceFungibleTokenConformance-251.0kB ± 0%51.2kB ± 0%+0.28%(p=0.001 n=7+7)
ContractInterfaceFungibleToken-224.7kB ± 0%24.8kB ± 0%+0.32%(p=0.001 n=7+7)
InterpretRecursionFib-21.00MB ± 0%1.00MB ± 0%~(p=0.385 n=7+6)
NewInterpreter/new_interpreter-2752B ± 0%752B ± 0%~(all equal)
NewInterpreter/new_sub-interpreter-2200B ± 0%200B ± 0%~(all equal)
ParseArray-22.80MB ± 5%2.74MB ± 3%~(p=0.383 n=7+7)
ParseDeploy/byte_array-24.17MB ± 3%4.17MB ± 3%~(p=0.710 n=7+7)
ParseDeploy/decode_hex-2214kB ± 0%214kB ± 0%~(p=0.120 n=7+7)
ParseFungibleToken/With_memory_metering-229.4kB ± 0%29.6kB ± 0%+0.69%(p=0.001 n=7+7)
ParseFungibleToken/Without_memory_metering-229.4kB ± 0%29.5kB ± 0%+0.65%(p=0.001 n=7+7)
ParseInfix-21.91kB ± 0%1.92kB ± 0%~(p=0.691 n=7+7)
QualifiedIdentifierCreation/One_level-20.00B 0.00B ~(all equal)
QualifiedIdentifierCreation/Three_levels-264.0B ± 0%64.0B ± 0%~(all equal)
RuntimeFungibleTokenTransfer-2103kB ± 1%102kB ± 1%~(p=0.902 n=7+7)
RuntimeResourceDictionaryValues-22.28MB ± 0%2.29MB ± 1%~(p=0.805 n=7+7)
RuntimeScriptNoop-28.51kB ± 1%8.62kB ± 0%+1.27%(p=0.001 n=7+7)
SuperTypeInference/arrays-296.0B ± 0%96.0B ± 0%~(all equal)
SuperTypeInference/composites-20.00B 0.00B ~(all equal)
SuperTypeInference/integers-20.00B 0.00B ~(all equal)
ValueIsSubtypeOfSemaType-248.0B ± 0%48.0B ± 0%~(all equal)
 
allocs/opdelta
CheckContractInterfaceFungibleTokenConformance-2920 ± 0%921 ± 0%+0.11%(p=0.001 n=7+7)
ContractInterfaceFungibleToken-2444 ± 0%445 ± 0%+0.23%(p=0.001 n=7+7)
InterpretRecursionFib-218.9k ± 0%18.9k ± 0%~(all equal)
NewInterpreter/new_interpreter-213.0 ± 0%13.0 ± 0%~(all equal)
NewInterpreter/new_sub-interpreter-24.00 ± 0%4.00 ± 0%~(all equal)
ParseArray-259.6k ± 0%59.6k ± 0%~(p=1.000 n=7+7)
ParseDeploy/byte_array-289.4k ± 0%89.4k ± 0%~(p=1.000 n=7+7)
ParseDeploy/decode_hex-264.0 ± 0%64.0 ± 0%~(all equal)
ParseFungibleToken/With_memory_metering-2779 ± 0%779 ± 0%~(all equal)
ParseFungibleToken/Without_memory_metering-2779 ± 0%779 ± 0%~(all equal)
ParseInfix-248.0 ± 0%48.0 ± 0%~(all equal)
QualifiedIdentifierCreation/One_level-20.00 0.00 ~(all equal)
QualifiedIdentifierCreation/Three_levels-22.00 ± 0%2.00 ± 0%~(all equal)
RuntimeFungibleTokenTransfer-22.01k ± 0%2.01k ± 0%+0.10%(p=0.001 n=7+7)
RuntimeResourceDictionaryValues-237.0k ± 0%37.0k ± 0%+0.01%(p=0.019 n=7+7)
RuntimeScriptNoop-2137 ± 0%138 ± 0%+0.73%(p=0.001 n=7+7)
SuperTypeInference/arrays-23.00 ± 0%3.00 ± 0%~(all equal)
SuperTypeInference/composites-20.00 0.00 ~(all equal)
SuperTypeInference/integers-20.00 0.00 ~(all equal)
ValueIsSubtypeOfSemaType-21.00 ± 0%1.00 ± 0%~(all equal)
 

@@ -610,6 +610,11 @@ func (interpreter *Interpreter) VisitProgram(program *ast.Program) {
interpreter.visitGlobalDeclaration(declaration)
}

// TODO: Add this
//for _, declaration := range program.AttachmentDeclarations() {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is going to get implemented in a couple PRs in the future

@@ -924,6 +929,11 @@ func (interpreter *Interpreter) VisitCompositeDeclaration(declaration *ast.Compo
return nil
}

func (interpreter *Interpreter) VisitAttachmentDeclaration(declaration *ast.AttachmentDeclaration) StatementResult {
// TODO: fill this in
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also for a future PR

// they are subject to many of the same checks. In order to avoid duplicating these checks, we create a mapping
// between an attachment declaration and its composite "equivalent" that we can use for these composite checks
func (checker *Checker) attachmentAsComposite(declaration *ast.AttachmentDeclaration) *ast.CompositeDeclaration {
compositeDelcaration, ok := checker.Elaboration.AttachmentCompositeDeclarations[declaration]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's important that two physically equal attachment declarations also have physically equal composite equivalents, so we cannot generate a new declaration every time we call this. Instead we cache the results of each call and return the cached value if we see the same attachment declaration again.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is also potential to refactor this so that the composite checking/interpreting functions operate on a supertype of both attachments and composites (perhaps an CompositeLikeDeclaration) type. Once these are merged I will look into a follow-up PR to refactor this, potentially improving performance.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@@ -1866,6 +2014,14 @@ func (checker *Checker) checkSpecialFunction(
defer checker.leaveValueScope(specialFunction.EndPosition, checkResourceLoss)

checker.declareSelfValue(containerType, containerDocString)
if containerType.GetCompositeKind() == common.CompositeKindAttachment {
// attachments cannot be interfaces, so this cast must succeed
Copy link
Contributor Author

Choose a reason for hiding this comment

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

attachment interface ... will fail in the parser

Copy link
Member

Choose a reason for hiding this comment

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

It might be nice to make this a semantic error instead, i.e. parse it, but report an error in the checker.

}

func (checker *Checker) declareSuperValue(baseType Type, superDocString string) {
superType := NewReferenceType(checker.memoryGauge, baseType, false)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

References to super should be non-auth, as the actual base type in practice may be any number of subtypes of the annotated base type, not all of which should be available to the writer of the attachment.

Copy link
Member

Choose a reason for hiding this comment

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

Great comment, please add it to the code

@dsainati1 dsainati1 marked this pull request as ready for review November 14, 2022 15:45
Base automatically changed from sainati/extension-parsing to feature/attachments November 18, 2022 19:00
Copy link
Member

@SupunS SupunS 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!

runtime/sema/check_composite_declaration.go Outdated Show resolved Hide resolved
runtime/sema/check_composite_declaration.go Outdated Show resolved Hide resolved
runtime/sema/type.go Outdated Show resolved Hide resolved
runtime/sema/type.go Outdated Show resolved Hide resolved
runtime/tests/checker/attachments_test.go Show resolved Hide resolved
Copy link
Member

@SupunS SupunS left a comment

Choose a reason for hiding this comment

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

I sort of skipped this part https://github.com/onnflow/cadence/pull/2082#discussion_r1000942822 since it is refactored in the other PR.

Rest of it looks good!

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.

Awesome work! Looks good so far, still have to review attachments_test.go

runtime/sema/check_composite_declaration.go Outdated Show resolved Hide resolved
runtime/sema/check_composite_declaration.go Show resolved Hide resolved
runtime/sema/check_composite_declaration.go Outdated Show resolved Hide resolved
runtime/sema/check_composite_declaration.go Show resolved Hide resolved
Comment on lines 1179 to 1180
if interfaceType.CompositeKind != compositeType.Kind &&
interfaceType.CompositeKind != compositeType.getBaseCompositeKind() {
Copy link
Member

Choose a reason for hiding this comment

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

Should this be a logical or?

Copy link
Contributor Author

@dsainati1 dsainati1 Nov 28, 2022

Choose a reason for hiding this comment

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

No. This is kind of why I hate DeMorgan (cc @dreamsmasher); this is much more readable as

if !(interfaceType.CompositeKind == compositeType.Kind ||
		interfaceType.CompositeKind == compositeType.getBaseCompositeKind()) {

Because the condition we want to enforce is either that the composite and the interface have the same kind, or that the composite is an attachment (and hence has a base) and it's base's kind is the same as the interface's kind. I'm going to change it back.

Copy link
Member

Choose a reason for hiding this comment

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

Thank you for the explanation, I see now the cases and what is meant to be checked.

So does this check assume that for an attachment, the first part will always fail?

Maybe it's clearer to differentiate more clearly between the "traditional" case (non-attachment), and the attachment case?

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; an attachment's kind is always CompositeKindAttachment, while the interface's should either be CompositeKindResource or CompositeKindStruct

runtime/sema/errors.go Show resolved Hide resolved
runtime/sema/errors.go Outdated Show resolved Hide resolved
runtime/sema/type.go Outdated Show resolved Hide resolved
runtime/sema/type.go Show resolved Hide resolved
runtime/sema/type.go Outdated Show resolved Hide resolved
@dsainati1 dsainati1 requested a review from turbolent November 28, 2022 17:44
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.

Great work!

@dsainati1 dsainati1 merged commit 15dd4d7 into feature/attachments Dec 1, 2022
@dsainati1 dsainati1 deleted the sainati/attachments-checking branch December 1, 2022 19:31
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.

4 participants