""" Class hierarchy: BinaryOpExpr AbstractAssignExpr AssignExpr CoalesceAssignExpr InverseCoalesceAssignExpr NumericPromoExpr AugAssignMathExpr AugAssignBitwiseExpr BinaryBitwiseExpr BinaryMathExpr BinaryBoolExpr CompareExpr DotExpr InExpr InheritsExpr AbstractToExpr ToExpr ToQExpr CoalesceExpr InverseCoalesceExpr """ class BinaryOpExpr is abstract, partial inherits Expr shared def make(opToken as IToken, op as String, left as Expr, right as Expr) as BinaryOpExpr """ Instantiates the correct class for the given operator and returns it. """ branch op on 'TO' return ToExpr(opToken, op, left, right) on 'TOQ' return ToQExpr(opToken, op, left, right) on 'QUESTION' return CoalesceExpr(opToken, op, left, right) on 'QUESTION_EQUALS' return CoalesceAssignExpr(opToken, op, left, right) on 'BANG' return InverseCoalesceExpr(opToken, op, left, right) on 'BANG_EQUALS' return InverseCoalesceAssignExpr(opToken, op, left, right) on 'ASSIGN' return AssignExpr(opToken, op, left, right) on 'DOT' return DotExpr(opToken, op, left, right) on 'INHERITS' or 'IMPLEMENTS' return InheritsExpr(opToken, op, left, right) on 'IN' or 'NOTIN' return InExpr(opToken, op, left, right) on 'IS' or 'ISNOT' or 'EQ' or 'NE' or 'LT' or 'GT' or 'LE' or 'GE' if left inherits CompareExpr if not left.isParened left.addComparison(op, right) return left return CompareExpr(opToken, op, left, right) on 'AND' or 'OR' or 'IMPLIES' return BinaryBoolExpr(opToken, op, left, right) on 'PLUS' or 'MINUS' or 'STAR' or 'STARSTAR' or 'SLASH' or 'SLASHSLASH' or 'PERCENT' return BinaryMathExpr(opToken, op, left, right) on 'PLUS_EQUALS' or 'MINUS_EQUALS' or 'STAR_EQUALS' or 'STARSTAR_EQUALS' or 'SLASH_EQUALS' or 'SLASHSLASH_EQUALS' or 'PERCENT_EQUALS' return AugAssignMathExpr(opToken, op, left, right) on 'AMPERSAND' or 'VERTICAL_BAR' or 'CARET' or 'DOUBLE_LT' or 'DOUBLE_GT' return BinaryBitwiseExpr(opToken, op, left, right) on 'AMPERSAND_EQUALS' or 'VERTICAL_BAR_EQUALS' or 'CARET_EQUALS' or 'DOUBLE_LT_EQUALS' or 'DOUBLE_GT_EQUALS' return AugAssignBitwiseExpr(opToken, op, left, right) else throw FallThroughException([opToken, op, left, right]) var _op as String var _left as Expr var _right as Expr cue init(opToken as IToken, op as String, left as Expr, right as Expr) base.init(opToken) _op = op _left = left _left.superNode = this _right = right _right.superNode = this get allExprs as Expr* for expr in base.allExprs, yield expr for expr in .left.allExprs, yield expr for expr in .right.allExprs, yield expr get op from var get left from var get right from var get willChangeVar as bool is override if _left.willChangeVar, return true if _right.willChangeVar, return true return false def _innerClone base._innerClone _left = _left.clone to Expr _right = _right.clone to Expr def bindImp as dynamic base.bindImp assert .didBindImp # TODO: maybe checks should only be invoked if left and right were bound without error .checkAfterBindImp .checkForVoids return .bindImpResult def _bindImp base._bindImp assert _left is not this assert _right is not this # The try..catch below enables errors to be detected on both sides of the operator try _left.bindImp catch ne1 as NodeException gotError = true if _left.type is nil # assert _left.hasError to-do _left.type = .compiler.passThroughType try _right.bindImp catch ne2 as NodeException gotError = true if _right.type is nil # assert _left.hasError to-do _right.type = .compiler.passThroughType if gotError _type = .compiler.passThroughType # to-do CC: past an exception catching block, the type of the var (eg ne1 and ne2) # must be considered nilable throw NodeMultiException(ne1, ne2) assert _left.didBindImp assert _right.didBindImp def checkAfterBindImp """ Invoked after `bindImp` in order to do more error checking. So this is a hook for subclasses to do error checking after left and right have been successfully bound. """ require .didBindImp pass def checkForVoids left = _left right = _right voidType = .compiler.voidType # check for void on either side of the expression sugg = 'Use a different method or change that method to return something.' if left.type is voidType if left inherits BinaryOpExpr and (left to BinaryOpExpr).op=='DOT' and (left to BinaryOpExpr).right inherits CallExpr # CC: axe casts append = ' because "[((left to BinaryOpExpr).right to CallExpr).name]" does not return anything.' # CC: axe BinaryOp cast else append = '.' .throwError('There is no type for the left hand side of "[.token.text]"[append] [sugg]') if right.type is voidType and not right.isCalling if right inherits BinaryOpExpr and (right to BinaryOpExpr).op=='DOT' and (right to BinaryOpExpr).right inherits CallExpr # CC: axe cast append = ' because "[((right to BinaryOpExpr).right to CallExpr).name]" does not return anything.' # CC: axe BinaryOp cast else append = '.' .throwError('There is no type for the right hand side of "[.token.text]"[append] [sugg]') def addSubFields base.addSubFields .addField('op', _op) .addField('left', _left) .addField('right', _right) def toCobraSource as String is override return '([_left.toCobraSource] [.token.text] [_right.toCobraSource])' class AbstractAssignExpr is partial inherits BinaryOpExpr """ The abstract base class for AssignExpr, AugAssignExpr, CoalesceAssignExpr and InverseCoalesceAssignExpr. """ var _trackLocal as bool # for the detailed stack trace var _trackName as String? cue init(opToken as IToken, op as String, left as Expr, right as Expr) base.init(opToken, op, left, right) get canBeStatement as bool is override return true get willChangeVar as bool is override return true def checkAfterBindImp base.checkAfterBindImp # can't do the following in a `_bindImp` override since type inference is implemented in # a subclass (_left may not have its _definition until after that executes) if .compiler.willTrackLocals if _left inherits NameExpr _trackLocal = _left.definition inherits AbstractLocalVar if _trackLocal, _trackName = _left.namedDefinition.name if .curCodeMember and not .curCodeMember inherits Initializer and _left.definition inherits BoxVar and _left.definition.isReadOnly .throwError('The readonly field "[_left.definition.name]" can only be assigned to in an initializer or the field\'s declaration.') class AssignExpr inherits AbstractAssignExpr is partial var _backUpIfInheritsStack as List? cue init(opToken as IToken, op as String, left as Expr, right as Expr) require op == 'ASSIGN' base.init(opToken, op, left, right) assert op == 'ASSIGN' def _bindImp try base._bindImp catch NodeMultiException # TODO: try non multi error instead # for a type inference situation, create a var of type passthrough for the left hand side # to prevent superfluous errors in subsequent statements left = _left if left inherits IdentifierExpr if left.definition is nil definition = LocalVar(left.token, .compiler.passThroughType).bindAll to LocalVar # CC: axe cast when supporting 'as this' .compiler.codeMemberStack.peek.addLocal(definition) left.setDefinition(definition) left.type = definition.type if _type is nil _type = .compiler.passThroughType throw if _left inherits DotExpr and _left.definition inherits Property and _left.definition.setPart is nil .throwError('Cannot assign to property "[_left.definition.name]" because it is read only.') left = _left right = _right assert right.type if left inherits IdentifierExpr if left.namedDefinition is nil # type inference assert right.type if right.type is .compiler.passThroughType # TODO: ? change to unspecifiedType if right.hasError # for a type inference situation, create a var of type passthrough for the left hand side # to prevent superfluous errors in subsequent statements definition = LocalVar(left.token, .compiler.passThroughType).bindAll to LocalVar # CC: axe cast when supporting 'as this' .compiler.codeMemberStack.peek.addLocal(definition) left.setDefinition(definition) left.type = definition.type _type = left.type return else .throwError('Cannot infer type for "[left.name]" because the type of the right hand expression is unknown.') if left.name.startsWith('_') .throwError('No class variable named "[left.name]" exists and local variable declarations cannot start with an underscore (_).') inferredType = if(right.type inherits NilType, .compiler.nilableDynamicType, right.type) # `x = nil` for a new local var `x` is inferred as `dynamic?` definition = LocalVar(left.token, inferredType).bindAll to LocalVar # CC: axe cast when supporting 'as this' .compiler.codeMemberStack.peek.addLocal(definition) left.setDefinition(definition) left.type = definition.type if left.definition is nil left.throwUnknownIdError if left.definition inherits LocalVar and (left.definition to LocalVar).name.startsWithNonLowerLetter (left.definition to LocalVar).isUsed = true # suppress subsequent warning about unused variable .throwError(ErrorMessages.localVariablesMustStartLowercase) if not right.canBeAssignedTo(left.type to !) okay = false if left inherits IdentifierExpr if (defi = left.namedDefinition) inherits IVar if defi.attemptAssignmentOf(right.type to !) _backUpIfInheritsStack = List(defi.ifInheritsStack) # will need this later for code gen okay = true if not okay .throwError('Incompatible types. Cannot assign value of type [right.type.name] on the right to [left.type.name] on the left.') _type = left.type right.contextType = _type def checkAfterBindImp base.checkAfterBindImp if not .hasError and _left.definition inherits Param if _right.definition inherits BoxVar # the exception to this warning is when the statement is guarded by an if which references the _left.definition, as in: if param is nil; param = _param # TODO: this has a test case that generates no warnings. so the cobra test cases should probably barf on unexpected warnings isException = false if .parent inherits BlockStmt and (.parent to BlockStmt).parent inherits IfStmt ifStmt = (.parent to BlockStmt).parent to IfStmt if (bop = ifStmt.cond) inherits BinaryOpExpr if bop.left.definition is _left.definition, isException = true else if bop.right.definition is _right.definition, isException = true if not isException leftName = _left.definition.name rightName = _right.definition.name .compiler.warning(this, 'Setting a parameter ("[leftName]") to a class variable ("[rightName]") is often a mistake. You may want to reverse the assignment.') class NumericPromoExpr inherits BinaryOpExpr is partial cue init(opToken as IToken, op as String, left as Expr, right as Expr) base.init(opToken, op, left, right) get isConcated from var as bool def _bindImp base._bindImp if _left.type.isDynamic or _right.type.isDynamic _type = .compiler.dynamicType return tint = .compiler.anyIntType tdecimal = .compiler.decimalType tfloat = .compiler.anyFloatType tpassthrough = .compiler.passThroughType tstring = .compiler.stringType cannotMix = false leftType = _left.type.nonNil rightType = _right.type.nonNil tgcd = leftType.greatestCommonDenominatorWith(rightType) if leftType.isDescendantOf(tint) if rightType.isDescendantOf(tint) # special case: true division # if both sides are ints, the type becomes decimal if _op == 'SLASH' _type = .compiler.numberType else _type = tgcd else cannotMix = _ifRightTypeThen(tdecimal, tdecimal, tfloat, tgcd) else if leftType.isDescendantOf(tdecimal) cannotMix = _ifRightTypeThen(tdecimal, tdecimal, tint, tdecimal) else if leftType.isDescendantOf(tfloat) cannotMix = _ifRightTypeThen(tfloat, tgcd, tint, tgcd) else if leftType.isDescendantOf(tstring) cannotMix = _ifRightTypeThen(tstring, tstring) else if leftType inherits Box if leftType.isGeneric and leftType.isDescendantOf(.compiler.collectionOfType.constructedTypeFor([leftType.genericParams[0]])) if leftType.genericParams.count > 1 .throwError('Cannot concatenate [leftType.name] on the left side of "+" because it has more than one generic parameter.') if rightType inherits Box if rightType.isGeneric and rightType.isDescendantOf(.compiler.enumerableOfType.constructedTypeFor([rightType.genericParams[0]])) if rightType.genericParams.count > 1 .throwError('Cannot concatenate [rightType.name] on the left side of "+" because it has more than one generic parameter.') leftInner = leftType.genericParams[0] rightInner = rightType.genericParams[0] newInner = leftInner.greatestCommonDenominatorWith(rightInner) _type = leftType.genericDef.constructedTypeFor([newInner]) _isConcated = true else if rightType.isDescendantOf(.compiler.enumerableType) # casting will be done at run-time and either succeed or throw an exception _type = leftType _isConcated = true else cannotMix = true else cannotMix = true if _type is nil if _left.isKindOf(tpassthrough) and not _right.isKindOf(tpassthrough) _type = rightType else if _right.isKindOf(tpassthrough) and not _left.isKindOf(tpassthrough) _type = leftType else if _left.isKindOf(tpassthrough) or _right.isKindOf(tpassthrough) _type = tpassthrough else if leftType == rightType and leftType inherits EnumDecl _type = leftType else if cannotMix .throwError('Cannot mix types [_left.type.name] and [_right.type.name] for arithmetic.') if _type is nil sugg = 'Try finding a method that performs this function.' if leftType == rightType .throwError('Cannot apply [_op] to [leftType.name]. [sugg]') else .throwError('Cannot apply [_op] to [leftType.name] and [rightType.name]. [sugg]') def _ifRightTypeThen(ifThenPairs as vari IType) as bool rightType = _right.type.nonNil i = 0 while i < ifThenPairs.length matchType, resultType = ifThenPairs[i], ifThenPairs[i+1] i += 2 if rightType.isDescendantOf(matchType) _type = resultType return false return true class AugAssignMathExpr inherits NumericPromoExpr is partial """ TODO: How should: someInt /= someInt be handled given that someInt / someInt gives a decimal? """ # TODO: does not inherit AbstractAssignExpr. # TODO: Does not participate in -detailed-stack-trace cue init(opToken as IToken, op as String, left as Expr, right as Expr) base.init(opToken, op, left, right) assert op in ['PLUS_EQUALS', 'MINUS_EQUALS', 'STAR_EQUALS', 'STARSTAR_EQUALS', 'SLASH_EQUALS', 'SLASHSLASH_EQUALS', 'PERCENT_EQUALS'] get canBeStatement as bool is override return true get willChangeVar as bool is override return true def _bindImp base._bindImp # TODO: does NumericPromoExpr cover everything we need? # C# and other languages use += and -= on delegates and events, but Cobra does not. if .left.type inherits MethodSig # TODO: error pass else if .left.type.nonNil.isDescendantOf(.compiler.delegateType) leftSource = .left.toCobraSource rightSource = .right.toCobraSource if not .right inherits RefExpr, rightSource = 'ref ' + rightSource branch .op on 'PLUS_EQUALS' msg = 'Cannot use "+=". ' if .left.definition inherits BoxEvent, msg += 'Use "listen [leftSource], [rightSource]" for events.' else, msg += 'Use "[leftSource] = Delegate.combine([leftSource], [rightSource])" for delegates.' on 'MINUS_EQUALS' msg = 'Cannot use "-=". ' if .left.definition inherits BoxEvent, msg += 'Use "ignore [leftSource], [rightSource]" for events.' else, msg += 'Use "[leftSource] = Delegate.remove([rightSource])" for delegates.' else msg = 'Cannot use the operator "[.token.text]" on events or delegates.' .throwError(msg) class AugAssignBitwiseExpr is partial inherits NumericPromoExpr """ """ # TODO: does not inherit AbstractAssignExpr. Does not participate in super stack trace cue init(opToken as IToken, op as String, left as Expr, right as Expr) base.init(opToken, op, left, right) assert op in ['AMPERSAND_EQUALS', 'VERTICAL_BAR_EQUALS', 'CARET_EQUALS', 'DOUBLE_LT_EQUALS', 'DOUBLE_GT_EQUALS'] get canBeStatement as bool is override return true get willChangeVar as bool is override return true def _bindImp base._bindImp # TODO: does NumericPromoExpr cover everything we need? class BinaryBoolExpr is partial inherits BinaryOpExpr cue init(opToken as IToken, op as String, left as Expr, right as Expr) base.init(opToken, op, left, right) assert op in ['AND', 'OR', 'IMPLIES'] def _bindImp base._bindImp _type = .compiler.boolType if _left.type is not .compiler.boolType _left = TruthExpr(_left).bindImp to TruthExpr # CC: axe cast after "as this" if _right.type is not .compiler.boolType _right = TruthExpr(_right).bindImp to TruthExpr # CC: axe cast after "as this" class BinaryBitwiseExpr is partial inherits NumericPromoExpr cue init(opToken as IToken, op as String, left as Expr, right as Expr) base.init(opToken, op, left, right) assert op in ['AMPERSAND', 'VERTICAL_BAR', 'CARET', 'DOUBLE_LT', 'DOUBLE_GT'] def _bindImp base._bindImp class BinaryMathExpr is partial inherits NumericPromoExpr cue init(opToken as IToken, op as String, left as Expr, right as Expr) base.init(opToken, op, left, right) assert op in ['PLUS', 'MINUS', 'STAR', 'STARSTAR', 'SLASH', 'SLASHSLASH', 'PERCENT'] def _bindImp base._bindImp if .isConcated and .type, return lt = _left.type.nonNil rt = _right.type.nonNil if lt is rt assert _type == lt or (_type is .compiler.numberType and _op=='SLASH') else if lt.isDynamic or rt.isDynamic _type = .compiler.dynamicType else if lt inherits PassThroughType _type = rt else if rt inherits PassThroughType _type = lt else # TODO: this will need to be more sophisticated in the future with different sized ints and floats if lt.isDescendantOf(.compiler.anyIntType) _type = rt else if rt.isDescendantOf(.compiler.anyIntType) _type = lt else if lt.isDescendantOf(.compiler.decimalType) if rt.isDescendantOf(.compiler.anyFloatType) .throwError('Cannot promote between decimal and float for operator "[_op]". Decimal is more accurate and float has more range.') else # int case already taken care of, so... .throwError('Cannot promote between decimal and "[rt.name]".') else if lt.isDescendantOf(.compiler.anyFloatType) if rt.isDescendantOf(.compiler.decimalType) .throwError('Cannot promote between float and decimal for operator "[_op]". Decimal is more accurate and float has more range.') else # int case already taken care of, so... .throwError('Cannot promote between decimal and "[rt.name]".') else throw FallThroughException({'this': this, '_left': _left, 'op': _op, 'lt': lt, 'rt': rt}) class CompareExpr is partial inherits BinaryOpExpr var _items as List var _operations as List cue init(opToken as IToken, op as String, left as Expr, right as Expr) require op in ['IS', 'ISNOT', 'EQ', 'NE', 'LT', 'GT', 'LE', 'GE'] base.init(opToken, op, left, right) _items = [left, right] _operations = [op] if op == 'ISNOT' and .token.text == 'is' .token.text = 'is not' def addComparison(op as String, right as Expr) _operations.add(op) _items.add(right) def _bindImp base._bindImp _type = .compiler.boolType if _items.count > 2 # this transformation is done so that the back ends handle chained comparisons differently than normal comparisons cce = ChainedCompareExpr(.token, _items, _operations) cce.bindImp _transformTo(cce) return if _left.type is nil or _right.type is nil assert .hasError return leftType = _left.type to ! rightType = _right.type to ! innerMsg as String? # cannot invoke .isAssignableTo directly on the types because if one is nilable and the other is not, you can get false, even though that doesn't apply for "is". So use isEquatableTo and isComparableTo if .op in ['EQ', 'NE', 'IS', 'ISNOT'] if not leftType.isEquatableTo(rightType) innerMsg = if(.op.startsWith('IS'), 'can never be identical', 'cannot be equated') else if .op in ['IS', 'ISNOT'] # Essentially "is" and "is not" are for reference types. Give a warning when used for value types. if not leftType.isDynamicOrPassThrough and not rightType.isDynamicOrPassThrough opName = if(.op=='IS', 'is', 'is not') altName = if(.op=='IS', '==', '<>') if leftType inherits NilableType and rightType inherits NilType pass else if leftType inherits NilType and rightType inherits NilableType pass else if not leftType.isReference and not rightType.isReference .compiler.warning(this, 'Both the left and right sides of "[opName]" are value types ("[leftType.name]" and "[rightType.name]"), but "[opName]" applies to reference types. Use "[altName]" instead.') # interestingly, I don't think the following ever happen in practice... else if not leftType.isReference and not rightType.isNilableAndDescendantOf(leftType) .compiler.warning(this, 'The left side of "[opName]" is a value type ("[leftType.name]") while the right side is an incompatible reference type ("[rightType.name]").') else if not rightType.isReference and not leftType.isNilableAndDescendantOf(rightType) .compiler.warning(this, 'The right side of "[opName]" is a value type ("[rightType.name]") while the left side is an incompatible reference type ("[leftType.name]").') else if .left.isObjectLiteral or .right.isObjectLiteral .compiler.warning(this, 'Do not use the identity operator "[opName]" with an object literal. Use an equality operator such as "==" or "<>".') else if not leftType.isComparableTo(rightType) innerMsg = 'cannot be compared' if innerMsg typeNames = if(_left.type.name==_right.type.name, 'type ("[_left.type.name]")', 'types ("[_left.type.name]" and "[_right.type.name]")') msg = 'The left and right sides of the "[.token.text]" expression [innerMsg] because of their [typeNames].' if .op == 'IS' and _right.type.isDescendantOf(.compiler.typeType) # C# msg += ' Maybe you should try "inherits" instead of "is".' .throwError(msg) def _isObjectLiteral(expr as Expr) as bool if expr inherits StringLit, return true if expr inherits CompositeLiteral, return true return false interface IDotRightExpr inherits IExpr """ IDotRightExpr is an expression that can appear on the right hand side of a DotExpr. Implemented by MemberExpr and CallExpr. """ get name as String get memberDefinition as IMember? get args as List class DotExpr inherits BinaryOpExpr is partial implements IPotentialTypeExpr """ When creating a DotExpr, set .isImplicit to true if the original expression did not actually contain a dot (.), but only implied it. For example: `_foo()` When DotExpr's are chained as in "a.b.c.d", the .right will always be an IDotRightExpr (MemberExpr or CallExpr) and the .left will generally by the DotExpr (except for the first one). a.b.c.d ==> . / \ . d / \ . c / \ a b """ var _dotRightExpr as IDotRightExpr cue init(opToken as IToken, op as String, left as Expr, right as Expr) require op == 'DOT' right inherits IDotRightExpr body base.init(opToken, op, left, right) _dotRightExpr = right to IDotRightExpr def addMinFields base.addMinFields .addField('left.toCobraSource', .safeLeftToCobraSource) .addField('right.toCobraSource', .safeRightToCobraSource) def safeLeftToCobraSource as String try return .left.toCobraSource catch exc as Exception return exc.toString def safeRightToCobraSource as String try return .right.toCobraSource catch exc as Exception return exc.toString get canBeStatement as bool is override return true get definition is override """ The definition of a DotExpr is the definition of its .right which will be a CallExpr or MemberExpr (via the interface IDotRightExpr). """ return .right.definition get dotRight as IDotRightExpr """ Returns .right but with the more narrow type of IDotRightExpr. """ return _dotRightExpr def _bindImp base._bindImp if _left inherits BaseLit .compiler.curCodeMember.usesBase # use of base implies override if the containing member is not already marked new else if _left inherits ThisLit if not _left.isImplicit .compiler.warning(this, 'An explicit "this" literal is unnecessary for accessing members of the current object. You can remove "this".') else if not .isImplicit and .dotRight.name.startsWith('_') .compiler.warning(this, 'An explicit dot (".") is unnecessary for accessing underscored members of the current object. You can remove ".".') if not _type assert _right.type _type = _right.type if _left.type.isDynamic _type = _right.type if not _type.isDynamic # interesting example: d.getType # where `d` is dynamic. The resulting type, however, is the .typeType because `getType` is recognized as a method of Object _contextType = _type else if not _left.hasError and not _right.hasError if _left inherits IdentifierExpr # TODO: seems flawed. The error below should take place if left is a DotExpr if _left.namedDefinition inherits Box # CC: combine with above if statement if _dotRightExpr.memberDefinition is not nil and not _dotRightExpr.memberDefinition.isShared # when _dotRightExpr.memberDefinition is nil, it's a nested type if _dotRightExpr.name == 'init' # hack to support Application.init in ../HowTo/390-GTK.cobra # a better hack would be to de the correct binding to the shared init method pass else .throwError('Cannot access instance member "[_dotRightExpr.name]" on type "[_left.name]". Make the member "shared" or create an instance of the type.') if _left inherits ThisOrBaseLit if .curCodeMember and .curCodeMember.isShared and not _dotRightExpr.memberDefinition.isShared # .curCodeMember is nil when bind imp'ing on var init exprs like "var _x = someExpr" .throwError('Cannot access instance member "[_dotRightExpr.name]" from the current shared member. Make the member "shared" or create an instance of the type.') # Check for qualified type such as Foo.Bar if .definition implements IType and .type.isDescendantOf(.compiler.typeType) # A.B where A and B are namespaces or types _transformTo(TypeExpr(.token, .definition to IType).bindImp) else if (right = .right) inherits CallExpr if right.memberDefinition implements IType # A.B.C() where C is a type such as a class or struct te = TypeExpr(.token, right.memberDefinition to IType) te.bindImp pc = PostCallExpr(right.token, te, right.args) pc.bindImp _transformTo(pc) else assert not _dotRightExpr.memberDefinition inherits MemberOverload or .compiler.refExprLevel > 0 # _checkLeftHandNilable -- not ready for this yet def _checkLeftHandNilable if _left inherits IdentifierExpr defi = _left.namedDefinition if defi inherits AbstractLocalVar if _left.type inherits NilableType and not _left.type.isDynamic .compiler.warning(this, 'The receiver "[_left.toCobraSource]" may be nil which will cause an exception. You can guard against this with an "if" statement, an assignment, a coalesce or by changing the type.') def _help(hg as HelpGenerator) is override type = nil try type = .left.receiverType ? .left.type catch pass if type terms = '[type.name] [.dotRight.name]' hg.searchTerms.add(terms) ns = type.parentNameSpace if ns if not ns.isRoot, hg.searchTerms.add(ns.fullName + ' ' + terms) else if type inherits Box pb = type.parentBox if pb, hg.searchTerms.add(pb.qualifiedName + ' ' + terms) _helpType(hg, type) else hg.searchTerms.add(.dotRight.name) base._help(hg) def afterStatementBindImp base.afterStatementBindImp if not .hasError # TODO: inheritance # TODO: test on DLLs member = _dotRightExpr.memberDefinition if member inherits MemberOverload member = member.members[0] if member inherits BoxMember post while member found = false for attrib in member.attributes if attrib.name in ['MustUseResult', 'MustUseResultAttribute'] found = true break if found break member = member.matchingBaseMember if found .compiler.warning(this, 'The result of "[member.name]" should be used in an expression such as assignment or passing arguments (as indicated by its MustUseResult attribute).') get potentialType as IType? # overridden to return the type this dotted expr represents in those cases when it does represent a type assert .didBindImp if .definition inherits IType return .definition else return nil def toCobraSource as String is override if _left inherits ThisLit return '.[_right.toCobraSource]' else return '[_left.toCobraSource].[_right.toCobraSource]' class InExpr inherits BinaryOpExpr is partial """ Syntax: in not in """ cue init(opToken as IToken, op as String, left as Expr, right as Expr) base.init(opToken, op, left, right) assert op in ['IN', 'NOTIN'] get containsExpr from var as Expr? get needsNilCheck from var as bool def _bindImp base._bindImp _type = .compiler.boolType if not .hasError if .right.type.innerType is nil .recordError('The right-hand expression for "in" is of type "[.right.type.name]" which contains nothing.') else if .left.type.nonNil inherits CharType and .right.type is .compiler.stringType # special case because there is no String.contains that takes a char # by not setting _contains, the code gen will use CobraImp.In pass else if not _canContain(.right.type to !, .left.type to !) .recordError('The left expression ([.left.toCobraSource]) cannot be "in" the right-hand expression because the left type is "[.left.type.name]" and the right-hand expression contains type "[.right.type.innerType.name]".') else if _right.type.nonNil inherits ArrayType pass else if not _right.type.isDynamic # try to use right.contains(left) # this is more direct and offers speed up on some classes like Set contains = DotExpr(.token, 'DOT', _right, CallExpr(.token, 'contains', List([_left]), true, isImplicit=true)) try contains.bindImp catch SourceException pass success # TODO: Not sure what's going on here. Cobra thinks these have "contains" and C# says no way # error: "System.Collections.Generic.Dictionary.KeyCollection" does not contain a definition for "Contains" (C#) # error: "System.Collections.Generic.Dictionary.ValueCollection" does not contain a definition for "Contains" (C#) if contains.type is .compiler.boolType and not _right.type.name.startsWith('KeyCollection<') and not _right.type.name.startsWith('ValueCollection<') _containsExpr = contains # optimize `key in dict.keys` to `dict.containsKey(key)` which eliminates a linear search if _containsExpr is nil if _right inherits DotExpr if _right.right inherits MemberExpr and _ (_right.right to MemberExpr).name == 'keys' and _ _right.left.type inherits Box and _ (_right.left.type to Box).isIndirectConstructionOf(.compiler.idictionaryOfType) # then contains = DotExpr(.token, 'DOT', _right.left, CallExpr(.token, 'containsKey', List([_left]), true, isImplicit=true)) contains.bindImp _containsExpr = contains def _canContain(containerType as IType, whatType as IType) as bool it = containerType.innerType if it and _isAssignableTo(whatType, it to !), return true contains = containerType.memberForName('contains') # TODO: handle method overloads if contains inherits Method if contains.params.count == 1 and _isAssignableTo(whatType, contains.params[0].type) return true return false def _isAssignableTo(a as IType, b as IType) as bool if a.isAssignableTo(b), return true if a inherits NilableType if a.theWrappedType.isAssignableTo(b) _needsNilCheck = true return true return false class InheritsExpr inherits BinaryOpExpr is partial cue init(opToken as IToken, op as String, left as Expr, right as Expr) base.init(opToken, op, left, right) assert op in ['INHERITS', 'IMPLEMENTS'] def _bindImp base._bindImp _type = .compiler.boolType if _right inherits IPotentialTypeExpr rightType = _right.potentialType if rightType leftType = _left.type if leftType okay = true if leftType.isDynamic pass else if leftType.isDescendantOf(rightType) .compiler.warning(this, 'The expression is always of type "[.right.toCobraSource]".') else if rightType.isDescendantOf(leftType) # ex: someShape inherits Circle pass else if leftType inherits NilableType and not rightType inherits NilableType and rightType.isStrictDescendantOf(leftType.nonNil) # ex: someNilableShape inherits Circle pass else if leftType inherits NilableType and not rightType inherits NilableType and leftType.nonNil.isDescendantOf(rightType) # ex: someNilableCircle inherits Shape .compiler.warning(this, 'The expression will always be of type "[.right.toCobraSource]" when not nil. You can just check for not nil by removing "inherits [.right.toCobraSource]".') else if rightType inherits Interface if leftType inherits Class pass else if leftType inherits Struct found = false for leftInterface in leftType.baseInterfaces if leftInterface.isDescendantOf(rightType) .compiler.warning(this, 'The expression is always of type "[.right.toCobraSource]".') found = true break if not found .compiler.warning(this, 'The expression (of type "[leftType.name]") is never of type "[.right.toCobraSource]".') else if leftType inherits Interface # inheriting interface IA gives no information about what other interfaces objects of type IA inherit at run-time pass else okay = false if not okay .compiler.warning(this, 'The expression (of type "[leftType.name]") is never of type "[.right.toCobraSource]".') defi = .right.definition if defi inherits Class if .op <> 'INHERITS', .throwError('Use the "inherits" operator when testing objects against classes.') else if defi inherits Struct if .op <> 'INHERITS', .throwError('Use the "inherits" operator when testing objects against structs.') else if defi inherits Interface # got annoying: # if .op <> 'IMPLEMENTS', .throwError('Use the "implements" operator when testing objects against interfaces.') pass else if defi inherits GenericParam pass else if defi inherits IType # int, decimal, etc. if .op <> 'INHERITS', .throwError('Use the "inherits" operator when testing objects against structs.') else if defi .throwError('Cannot test "[.op.toLower]" against a [defi.englishName].') class AbstractToExpr is abstract inherits BinaryOpExpr var _potentialTypeExpr as IPotentialTypeExpr cue init(opToken as IToken, op as String, left as Expr, right as Expr) base.init(opToken, op, left, right) assert right inherits IPotentialTypeExpr _potentialTypeExpr = right to IPotentialTypeExpr def _bindImp base._bindImp if not _right.isKindOf(.compiler.typeType) and not _right.type inherits Box .throwError('The "to" operator does not apply to "[_right.toCobraSource]". Use a type.') _type = _potentialTypeExpr.potentialType if _type is nil .throwError('The expression "[_potentialTypeExpr.toCobraSource]" does not identify a type.') # TODO: not quite ready for the following # at the very least, need to enhance CobraType and descendants to have a superType of .objectType # test case is 208-to-errors.cobra with substring 'can never be cast to a' #else if _left.type and not _type.isDescendantOf(_left.type to !) # .throwError('The given expression, of type "[_left.type.name]", can never be cast to a "[_potentialTypeExpr.toCobraSource]".') class ToExpr is partial inherits AbstractToExpr var _rightTypeExpr as IPotentialTypeExpr cue init(opToken as IToken, op as String, left as Expr, right as Expr) base.init(opToken, op, left, right) assert right inherits IPotentialTypeExpr _rightTypeExpr = right to IPotentialTypeExpr def _bindImp base._bindImp if _left inherits NilLiteral and not (_type inherits NilableType or _type inherits PassThroughType) .throwError('Cannot cast nil to a non-nil type.') rightType = _rightTypeExpr.potentialType if rightType if _left.type inherits NilableType and not rightType inherits NilableType if _left.type.nonNil == rightType .compiler.warning(this, 'The given expression is already a "[_rightTypeExpr.toCobraSource]", but nilable. You can just use "to !".') else if not _left.type inherits NilableType and rightType inherits NilableType if rightType.nonNil == _left.type .compiler.warning(this, 'The given expression is already a "[_left.type.name]", but not nilable. You can just use "to ?".') else if _left.type == rightType .compiler.warning(this, 'The given expression is already a "[_rightTypeExpr.toCobraSource]" so the typecast is redundant. You can remove it.') else if rightType inherits AbstractNumberType and _left.type.isDescendantOf(.compiler.stringType) typeName = rightType.name .recordError('Cannot cast a string to a numeric type. Consider using "[typeName].parse" or "[typeName].tryParse". Use "@help [typeName]" for details.') else .throwError('Cannot locate a type for the type cast "to [_rightTypeExpr.toCobraSource]".') class ToQExpr inherits AbstractToExpr is partial cue init(opToken as IToken, op as String, left as Expr, right as Expr) base.init(opToken, op, left, right) def _bindImp base._bindImp if not _type inherits NilableType _type = .typeProvider.nilableType(_type to !) # warn about redundancy if not .left.hasError and not .right.hasError rightTypeExpr = _right to IPotentialTypeExpr rightType = rightTypeExpr.potentialType if rightType if _left.type inherits NilableType and not rightType inherits NilableType if _left.type.nonNil == rightType .compiler.warning(this, 'The given expression is already a "[rightTypeExpr.toCobraSource]", but nilable. You can use "to !" to remove the nilability or remove the redundant cast.') else if not _left.type inherits NilableType and rightType inherits NilableType pass # TODO: check rhs type is castable to lhs regardless of nilability else if _left.type == rightType .compiler.warning(this, 'The given expression is already a "[rightTypeExpr.toCobraSource]" so the typecast is redundant. You can remove it.') else .throwError('Cannot locate a type for the type cast "to? [rightTypeExpr.toCobraSource]".') class CoalesceExpr is partial inherits BinaryOpExpr """ x ? y ==> if(x is nil, y, x) but without the potential double evaluation of x, which could be a complex expression. """ cue init(opToken as IToken, op as String, left as Expr, right as Expr) base.init(opToken, op, left, right) def _bindImp base._bindImp ptt = .compiler.passThroughType if _left.type is ptt or _right.type is ptt # x ? y ...where either is typed as passthrough _type = ptt else if not _left.type inherits NilableType .throwError('The left hand type of "?" is not nilable (it\'s "[_left.type.name]") so the expression will always evaluate to the left hand side.') else if _left.type inherits NilableType and not _right.type inherits NilableType # x ? y ...where x can be nil, but y cannot _type = _left.type.nonNil # TODO: should be greatest common denominator between the two else # the catch all case _type = _left.type # TODO: should be greatest common denominator between the two class InverseCoalesceExpr is partial inherits BinaryOpExpr """ x ! y ==> if(x is not nil, y, x) but without the potential double evaluation of x, which could be a complex expression. """ cue init(opToken as IToken, op as String, left as Expr, right as Expr) base.init(opToken, op, left, right) def _bindImp base._bindImp left, right = .left, .right ptt = .compiler.passThroughType if left.type is ptt or right.type is ptt # x ! y ...where either is typed as passthrough _type = ptt else if not left.type inherits NilableType .throwError('The left hand type of "!" is not nilable (it\'s "[left.type.name]") so the expression will always evaluate to the right hand side.') # TODO? # else if _right.type inherits NilableType and not _right.type inherits NilableType # # x ? y ...where x can be nil, but y cannot # _type = _left.type.nonNil # TODO: should be greatest common denominator between the two else # the catch all case _type = left.type.greatestCommonDenominatorWith(right.type to !) class CoalesceAssignExpr is partial inherits AbstractAssignExpr """ x ?= y """ cue init(opToken as IToken, op as String, left as Expr, right as Expr) base.init(opToken, op, left, right) def _bindImp base._bindImp if not _left.type inherits NilableType .throwError('The left hand type of "?=" is not nilable (it\'s "[_left.type.name]") so the expression will always evaluate to the left hand side.') # TODO: error check that the right hand type is not nilable? _type = _right.type class InverseCoalesceAssignExpr is partial inherits AbstractAssignExpr """ x != y """ cue init(opToken as IToken, op as String, left as Expr, right as Expr) base.init(opToken, op, left, right) def _bindImp base._bindImp left, right = .left, .right if not left.type inherits NilableType correct = 'bang-equals' in .compiler.options.setValue('correct-source') if correct and left.type.isComparableTo(right.type to !) .compiler.correctSource(.token, '<>') _transformTo(CompareExpr(.token.copy('NE', '<>'), 'NE', left, right)) return else .throwError('The left hand type of "!=" is not nilable (it\'s "[left.type.name]") so the expression will always evaluate to the right hand side. If you meant "does not equal" then use "<>". "a != b" means "a = a ! b" where "!" is an operator concerning nil.') _type = left.type.greatestCommonDenominatorWith(right.type to !)