Skip to content

Commit

Permalink
Merge pull request #165 from franklinsch/let-properties
Browse files Browse the repository at this point in the history
Support constant state properties
  • Loading branch information
franklinsch authored Apr 19, 2018
2 parents 7ca3bbd + 15f0407 commit 0a708fd
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 26 deletions.
24 changes: 19 additions & 5 deletions Sources/AST/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,18 @@ public struct Environment {
mutating func setProperties(_ variableDeclarations: [VariableDeclaration], enclosingType: RawTypeIdentifier) {
types[enclosingType]!.orderedProperties = variableDeclarations.map { $0.identifier.name }
for variableDeclaration in variableDeclarations {
addProperty(variableDeclaration.identifier.name, type: variableDeclaration.type, enclosingType: enclosingType)
addProperty(variableDeclaration.identifier.name, type: variableDeclaration.type, isConstant: variableDeclaration.isConstant, sourceLocation: variableDeclaration.sourceLocation, enclosingType: enclosingType)
}
}

/// Add a property to a type.
mutating func addProperty(_ property: String, type: Type, enclosingType: RawTypeIdentifier) {
types[enclosingType]!.properties[property] = PropertyInformation(type: type)
mutating func addProperty(_ property: String, type: Type, isConstant: Bool = false, sourceLocation: SourceLocation?, enclosingType: RawTypeIdentifier) {
types[enclosingType]!.properties[property] = PropertyInformation(type: type, isConstant: isConstant, sourceLocation: sourceLocation)
}

/// Add a use of an undefined variable.
public mutating func addUsedUndefinedVariable(_ variable: Identifier, enclosingType: RawTypeIdentifier) {
addProperty(variable.name, type: Type(inferredType: .errorType, identifier: variable), enclosingType: enclosingType)
addProperty(variable.name, type: Type(inferredType: .errorType, identifier: variable), sourceLocation: nil, enclosingType: enclosingType)
}

/// Whether a contract has been declared in the program.
Expand All @@ -88,6 +88,15 @@ public struct Environment {
return types[enclosingType]!.properties.keys.contains(property)
}

/// Whether is property is declared as a constnat.
public func isPropertyConstant(_ property: String, enclosingType: RawTypeIdentifier) -> Bool {
return types[enclosingType]!.properties[property]!.isConstant
}

public func propertyDeclarationSourceLocation(_ property: String, enclosingType: RawTypeIdentifier) -> SourceLocation? {
return types[enclosingType]!.properties[property]!.sourceLocation
}

/// The list of properties declared in a type.
public func properties(in enclosingType: RawTypeIdentifier) -> [String] {
return types[enclosingType]!.orderedProperties
Expand Down Expand Up @@ -306,8 +315,13 @@ public struct TypeInformation {
public struct PropertyInformation {
private var type: Type

init(type: Type) {
public var isConstant: Bool
public var sourceLocation: SourceLocation?

init(type: Type, isConstant: Bool = false, sourceLocation: SourceLocation?) {
self.type = type
self.isConstant = isConstant
self.sourceLocation = sourceLocation
}

public var rawType: Type.RawType {
Expand Down
39 changes: 27 additions & 12 deletions Sources/SemanticAnalyzer/SemanticAnalyzer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,27 @@ public struct SemanticAnalyzer: ASTPass {
if let _ = passContext.functionDeclarationContext {
// We're in a function. Record the local variable declaration.
passContext.scopeContext?.localVariables += [variableDeclaration]
}

if let assignedExpression = variableDeclaration.assignedExpression {
// This is a state property declaration. The default value assigned needs to be a literal.
} else {
// This is a state property declaration.

// Default values for state properties are not supported for structs yet.
if let _ = passContext.structDeclarationContext {
fatalError("Default values for state properties are not supported for structs yet.")
// Constants need to have a value assigned.
if variableDeclaration.isConstant, variableDeclaration.assignedExpression == nil {
diagnostics.append(.constantStatePropertyIsNotAssignedAValue(variableDeclaration))
}

if case .literal(_) = assignedExpression {
} else {
diagnostics.append(.statePropertyDeclarationIsAssignedANonLiteralExpression(variableDeclaration))
// If a default value is assigned, it should be a literal.

if let assignedExpression = variableDeclaration.assignedExpression {

// Default values for state properties are not supported for structs yet.
if let _ = passContext.structDeclarationContext {
fatalError("Default values for state properties are not supported for structs yet.")
}

if case .literal(_) = assignedExpression {
} else {
diagnostics.append(.statePropertyDeclarationIsAssignedANonLiteralExpression(variableDeclaration))
}
}
}

Expand Down Expand Up @@ -152,11 +160,10 @@ public struct SemanticAnalyzer: ASTPass {
// The identifier has no explicit enclosing type, such as in the expression `a.foo`.

let scopeContext = passContext.scopeContext!

if let variableDeclaration = scopeContext.variableDeclaration(for: identifier.name) {
if variableDeclaration.isConstant, asLValue {
// The variable is a constant but is attempted to be reassigned.
diagnostics.append(.reassignmentToConstant(identifier, variableDeclaration))
diagnostics.append(.reassignmentToConstant(identifier, variableDeclaration.sourceLocation))
}
} else {
// If the variable is not declared locally, assign its enclosing type to the struct or contract behavior
Expand All @@ -173,6 +180,14 @@ public struct SemanticAnalyzer: ASTPass {
diagnostics.append(.useOfUndeclaredIdentifier(identifier))
passContext.environment!.addUsedUndefinedVariable(identifier, enclosingType: enclosingType)
} else if asLValue {
if passContext.environment!.isPropertyConstant(identifier.name, enclosingType: enclosingType) {
// Retrieve the source location of that property's declaration.
let declarationSourceLocation = passContext.environment!.propertyDeclarationSourceLocation(identifier.name, enclosingType: enclosingType)!

// The state property is a constant but is attempted to be reassigned.
diagnostics.append(.reassignmentToConstant(identifier, declarationSourceLocation))
}

// The variable is being mutated.
if !functionDeclarationContext.isMutating {
// The function is declared non-mutating.
Expand Down
16 changes: 10 additions & 6 deletions Sources/SemanticAnalyzer/SemanticError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,22 @@ extension Diagnostic {
return Diagnostic(severity: .error, sourceLocation: closeBraceToken.sourceLocation, message: "Missing return in function expected to return \(resultType.name)")
}

static func reassignmentToConstant(_ identifier: Identifier, _ variableDeclaration: VariableDeclaration) -> Diagnostic {
let note = Diagnostic(severity: .note, sourceLocation: variableDeclaration.sourceLocation, message: "'\(variableDeclaration.identifier.name)' is declared here")
static func reassignmentToConstant(_ identifier: Identifier, _ declarationSourceLocation: SourceLocation) -> Diagnostic {
let note = Diagnostic(severity: .note, sourceLocation: declarationSourceLocation, message: "'\(identifier.name)' is declared here")
return Diagnostic(severity: .error, sourceLocation: identifier.sourceLocation, message: "Cannot assign to value: '\(identifier.name)' is a 'let' constant", notes: [note])
}

static func renderCapabilityGroup(_ capabilities: [CallerCapability]) -> String {
return "(\(capabilities.map({ $0.name }).joined(separator: ", ")))"
}

static func statePropertyDeclarationIsAssignedANonLiteralExpression(_ variableDeclaration: VariableDeclaration) -> Diagnostic {
return Diagnostic(severity: .error, sourceLocation: variableDeclaration.sourceLocation, message: "The default value assigned to a state property must be a literal")
}

static func constantStatePropertyIsNotAssignedAValue(_ variableDeclaration: VariableDeclaration) -> Diagnostic {
return Diagnostic(severity: .error, sourceLocation: variableDeclaration.sourceLocation, message: "'let' constant 'e' needs to be assigned a value")
}

static func renderCapabilityGroup(_ capabilities: [CallerCapability]) -> String {
return "(\(capabilities.map({ $0.name }).joined(separator: ", ")))"
}
}

// MARK: Warnings
Expand Down
2 changes: 1 addition & 1 deletion Tests/BehaviorTests/tests/structs/structs.flint
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ contract C {
var arr: Array
var b: B
var c: B
var d: Int = 5
let d: Int = 5
var e: Bool = true
}

Expand Down
6 changes: 5 additions & 1 deletion Tests/BehaviorTests/tests/structs/test/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,12 @@ contract(config.contractName, function(accounts) {
contract(config.contractName, function(accounts) {
it("should have its properties correctly initialized", async function() {
const instance = await Contract.deployed();
let t;

const t = await instance.getD();
t = await instance.getD();
assert.equal(t.valueOf(), 5);

t = await instance.getE();
assert.equal(t.valueOf(), 1);
})
})
13 changes: 12 additions & 1 deletion Tests/SemanticTests/constants.flint
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
contract Constants {
var a: Int
var b: Int = "a" // expected-error {{Incompatible assignment between values of type Int and String}}
var c: Int = 2 + 3 // expected-error {{The default value assigned to a state property must be a literal}}
let c: Int = 2 + 3 // expected-error {{The default value assigned to a state property must be a literal}}
let d: Int = 3
let e: Int // expected-error {{'let' constant 'e' needs to be assigned a value}}
}

Constants :: (any) {
Expand All @@ -19,5 +21,14 @@ Constants :: (any) {
} else {
a = 7 // expected-error {{Cannot assign to value: 'a' is a 'let' constant}}
}

d = 4 // expected-error {{Cannot assign to value: 'd' is a 'let' constant}}
}

mutating func bar() {
var d: Bool = true
d = false

self.d = 5 // expected-error {{Cannot assign to value: 'd' is a 'let' constant}}
}
}

0 comments on commit 0a708fd

Please sign in to comment.