-
Notifications
You must be signed in to change notification settings - Fork 5.8k
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
Visiting type class bodies in experimental analysis #14578
Conversation
@@ -216,8 +216,7 @@ bool TypeInference::visit(TypeClassDefinition const& _typeClassDefinition) | |||
subNode->accept(*this); | |||
auto const* functionDefinition = dynamic_cast<FunctionDefinition const*>(subNode.get()); | |||
solAssert(functionDefinition); | |||
// TODO: need polymorphicInstance? | |||
auto functionType = polymorphicInstance(typeAnnotation(*functionDefinition)); | |||
auto functionType = typeAnnotation(*functionDefinition); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still need to confirm that removing this does not break things and makes sense.
@@ -628,8 +557,6 @@ bool TypeInference::visit(TypeClassInstantiation const& _typeClassInstantiation) | |||
[&](ASTPointer<IdentifierPath> _typeClassName) -> std::optional<TypeClass> { | |||
if (auto const* typeClassDefinition = dynamic_cast<TypeClassDefinition const*>(_typeClassName->annotation().referencedDeclaration)) | |||
{ | |||
// visiting the type class will re-visit this instantiation | |||
typeClassDefinition->accept(*this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not 100% I can really remove this.
Initially I assumed that we're doing the separate pass partially to get rid of the necessity to do such a jump. But I see that visit(TypeClassDefinition)
then recursively goes back to here and I wonder if this has other consequences in the type system. We do not have any tests that would reveal potential problems though so it's hard to tell.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like it's still needed. Otherwise type inference breaks if the instantiation is visited before the class:
pragma experimental solidity;
type word = __builtin("word");
type T = word;
instantiation T: D {
function f(self: T) -> word {}
}
class Self: D {
function f(self: Self) -> word;
}
TypeError 7428: Type mismatch for function f T -> word != 'x:type -> 'w:type
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need it, if we already construct the full type of the class functions in the previous pass - but yeah, that depends on already constructing types from type expressions, which we still need to disentangle from inference (see also https://github.com/ethereum/solidity/pull/14578/files#r1343054472)
// For type class members the type is assigned by TypeClassMemberRegistration. | ||
// Fill in the `type` annotation to avoid `visit(FunctionDefinition)` giving it a fresh one. | ||
Type functionType = functionTypes.at(functionDefinition->name()); | ||
solAssert(!annotation(*functionDefinition).type.has_value()); | ||
annotation(*functionDefinition).type = functionType; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a bit awkward and I'm starting to doubt if this is really what I was supposed to do. What I did here is to create the function type for every type class member in TypeClassMemberRegistration
and then copy it into this annotation here during TypeInference
.
I wonder if it would make more sense to have a separate pass that initializes type
annotations with fresh variables instead so that other passes before type inference can refer to them.
I also expected to be able to move more of the stuff from visit(TypeClassDefinition)
but it seems to be mostly tied to unification so most of it had to stay and I still have iterate over FunctionDefinitions
.
The notes say this:
Pass 3: visit type class bodies, collecting function signatures (can involve defined types, so depends on 2!)
The thing is, we're not really collecting signatures - we don't know these until after inference - before it we only have two fresh variables, one for the whole tuple of arguments and one for a tuple of returns. And doing this does not require knowing anything about defined types since those are just two opaque type variables before inference. So I could just as well do this already in TypeClassRegistration
.
I must be missing something because I don't really see why exactly this needs to be a separate pass.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do the signatures of type class functions depend on type inference? (They don't...) But yeah, I'll need to look at the rest of the comments and the PR to see how to clarify things :-).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I see the problem now. The signatures of type class functions don't depend on type inference, really - but what type inference also does is to construct types from expressions in type context. That we may need to move to a separate pass actually, since that's what you need to get function signatures before inference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you clarify what exactly you understand as "function signatures"? Technically they don't depend on inference but before inference all you know is that it's some under-specified function type - its inputs and outputs are just fresh variables. I was assuming that signature is the fully inferred type.
Actually, initially I assumed that it's what stated in the declaration in the source file (however incomplete it may be), but this does not seem like collecting that would be of any use.
defineConversion(BuiltinClass::Integer, PrimitiveType::Integer, "fromInteger"); | ||
|
||
defineBinaryMonoidalOperator(BuiltinClass::Mul, Token::Mul, "mul"); | ||
defineBinaryMonoidalOperator(BuiltinClass::Add, Token::Add, "add"); | ||
|
||
defineBinaryCompareOperator(BuiltinClass::Equal, Token::Equal, "eq"); | ||
defineBinaryCompareOperator(BuiltinClass::Less, Token::LessThan, "lt"); | ||
defineBinaryCompareOperator(BuiltinClass::LessOrEqual, Token::LessThanOrEqual, "leq"); | ||
defineBinaryCompareOperator(BuiltinClass::Greater, Token::GreaterThan, "gt"); | ||
defineBinaryCompareOperator(BuiltinClass::GreaterOrEqual, Token::GreaterThanOrEqual, "geq"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@matheusaaguiar The task for you now, on top of this PR, would be to remove the whole concept of BuiltinClass
and instead make it possible to register these members using __builtin()
. This is the remaining part of pass 3 from Daniel's note.
To do this, you'd remove this hard-coded initialization and only fill in the typeClassFunctions
/operators
annotations when visiting Builtin
AST node. Of course only if the usage is correct - only one definition, only those predefined operators/conversions, etc. After this change we'll also no longer need the ability to use a token as type class name, so I'd remove that too (otherwise it will just be dead code).
You'll also need a small parser/AST modification to allow more than one argument in __builtin()
(in #14566 I made it expect exactly one).
When you do this, the code that currently looks like this:
pragma experimental solidity;
type word = __builtin("word");
type uint256 = word;
instantiation uint256: + {
function add(x: uint256, y: uint256) -> uint256 {}
}
function f(x: uint256, y: uint256) -> uint256 {
return x + y;
}
should look like this:
pragma experimental solidity;
type word = __builtin("word");
type uint256 = word;
__builtin("+", Add.add);
class uint256: Add {
function add(x: uint256, y: uint256) -> uint256;
}
instantiation uint256: Add {
function add(x: uint256, y: uint256) -> uint256 {}
}
function f(x: uint256, y: uint256) -> uint256 {
return x + y;
}
Without __builtin()
most of this should still work, but the x + y
bit should report an error due to the operator +
not being defined.
7111950
to
0819e31
Compare
3a4f754
to
bab6e3d
Compare
bab6e3d
to
a47aa09
Compare
instantiation T: D { | ||
function f(self: T) -> word {} | ||
} | ||
|
||
class Self: D { | ||
function f(self: Self) -> word; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is broken after my changes:
/solidity/test/soltest.cpp(120): error: in "syntaxTests/experimental/inference/infinite_recursion_two_functions": Exception during extracted test: /solidity/libsolidity/experimental/analysis/TypeInference.cpp(319): Throw in function virtual bool solidity::frontend::experimental::TypeInference::visit(const solidity::frontend::VariableDeclaration &)
Dynamic exception type: boost::wrapexcept<solidity::langutil::InternalCompilerError>
std::exception::what: Solidity assertion failed
[solidity::util::tag_comment*] = Solidity assertion failed
Looks like I need a check against multiple visitation in TypeInference::visit(FunctionDefinition)
after all. The problem is that I can no longer rely on the type
annotation for that...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This shouldn't be an issue... this pass shouldn't look at instantiations at all, but only at the class definitions - but at all of those - so the state after this pass should be that all function definitions in class definitions have types from this pass, but none of the ones within intantiations (for types of those we need to rely on inference first) But yeah, not yet sure I understand the problem correctly :-).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The broken bit is in the type inference pass. I can solve it by adding some extra flag, but I don't like that too much and looks like we'll have more things moved to the extra pass so maybe this problem will just solve itself.
|
||
m_currentFunctionType = functionType; | ||
functionAnnotation.type = functionType; | ||
// For type class members the type annotation is filled by visit(TypeClassDefinition) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just don't visit the bodies of type classes again here. (EDIT: that also only works if we actually fully determine the function types earlier)
Based on our discussions on the call and on the chat today, here's how I understand what needs to be done in this PR next:
|
bdd2f02
to
d7d4de0
Compare
Exclude list for AST JSON tests Fix type inference shellcheck failure Disable shellcheck SC2086
…tration-pass Separate type class registration pass in experimental analysis
- Using the same name causes syntax ambiguities. It's also not allowed for user-defined classes and types.
326ef77
to
b3a7739
Compare
f2dbadb
to
ddbcf57
Compare
8d311b9
to
194b114
Compare
This pull request is stale because it has been open for 14 days with no activity. |
This pull request is stale because it has been open for 14 days with no activity. |
This pull request is stale because it has been open for 14 days with no activity. |
This pull request is stale because it has been open for 14 days with no activity. |
This pull request is stale because it has been open for 14 days with no activity. |
This pull request is stale because it has been open for 14 days with no activity. |
This pull request is stale because it has been open for 14 days with no activity. |
Depends on #14566.This adds "pass 3" as described in Experimental Type System Notes, which is responsible for collecting types of type class member functions.
I also moved builtin class registration to
TypeClassRegistration
, though builtin classes are going to be removed soon (we'll instead have specific class members marked as builtin).