| 1 | """ |
|---|
| 2 | Class hierarchy: |
|---|
| 3 | |
|---|
| 4 | BinaryOpExpr |
|---|
| 5 | AbstractAssignExpr |
|---|
| 6 | AssignExpr |
|---|
| 7 | CoalesceAssignExpr |
|---|
| 8 | InverseCoalesceAssignExpr |
|---|
| 9 | NumericPromoExpr |
|---|
| 10 | AugAssignMathExpr |
|---|
| 11 | AugAssignBitwiseExpr |
|---|
| 12 | BinaryBitwiseExpr |
|---|
| 13 | BinaryMathExpr |
|---|
| 14 | BinaryBoolExpr |
|---|
| 15 | CompareExpr |
|---|
| 16 | DotExpr |
|---|
| 17 | InExpr |
|---|
| 18 | InheritsExpr |
|---|
| 19 | AbstractToExpr |
|---|
| 20 | ToExpr |
|---|
| 21 | ToQExpr |
|---|
| 22 | CoalesceExpr |
|---|
| 23 | InverseCoalesceExpr |
|---|
| 24 | """ |
|---|
| 25 | |
|---|
| 26 | |
|---|
| 27 | class BinaryOpExpr |
|---|
| 28 | is abstract, partial |
|---|
| 29 | inherits Expr |
|---|
| 30 | |
|---|
| 31 | shared |
|---|
| 32 | def make(opToken as IToken, op as String, left as Expr, right as Expr) as BinaryOpExpr |
|---|
| 33 | """ |
|---|
| 34 | Instantiates the correct class for the given operator and returns it. |
|---|
| 35 | """ |
|---|
| 36 | branch op |
|---|
| 37 | on 'TO' |
|---|
| 38 | return ToExpr(opToken, op, left, right) |
|---|
| 39 | on 'TOQ' |
|---|
| 40 | return ToQExpr(opToken, op, left, right) |
|---|
| 41 | on 'QUESTION' |
|---|
| 42 | return CoalesceExpr(opToken, op, left, right) |
|---|
| 43 | on 'QUESTION_EQUALS' |
|---|
| 44 | return CoalesceAssignExpr(opToken, op, left, right) |
|---|
| 45 | on 'BANG' |
|---|
| 46 | return InverseCoalesceExpr(opToken, op, left, right) |
|---|
| 47 | on 'BANG_EQUALS' |
|---|
| 48 | return InverseCoalesceAssignExpr(opToken, op, left, right) |
|---|
| 49 | on 'ASSIGN' |
|---|
| 50 | return AssignExpr(opToken, op, left, right) |
|---|
| 51 | on 'DOT' |
|---|
| 52 | return DotExpr(opToken, op, left, right) |
|---|
| 53 | on 'INHERITS' or 'IMPLEMENTS' |
|---|
| 54 | return InheritsExpr(opToken, op, left, right) |
|---|
| 55 | on 'IN' or 'NOTIN' |
|---|
| 56 | return InExpr(opToken, op, left, right) |
|---|
| 57 | on 'IS' or 'ISNOT' or 'EQ' or 'NE' or 'LT' or 'GT' or 'LE' or 'GE' |
|---|
| 58 | if left inherits CompareExpr |
|---|
| 59 | if not left.isParened |
|---|
| 60 | left.addComparison(op, right) |
|---|
| 61 | return left |
|---|
| 62 | return CompareExpr(opToken, op, left, right) |
|---|
| 63 | on 'AND' or 'OR' or 'IMPLIES' |
|---|
| 64 | return BinaryBoolExpr(opToken, op, left, right) |
|---|
| 65 | on 'PLUS' or 'MINUS' or 'STAR' or 'STARSTAR' or 'SLASH' or 'SLASHSLASH' or 'PERCENT' |
|---|
| 66 | return BinaryMathExpr(opToken, op, left, right) |
|---|
| 67 | on 'PLUS_EQUALS' or 'MINUS_EQUALS' or 'STAR_EQUALS' or 'STARSTAR_EQUALS' or 'SLASH_EQUALS' or 'SLASHSLASH_EQUALS' or 'PERCENT_EQUALS' |
|---|
| 68 | return AugAssignMathExpr(opToken, op, left, right) |
|---|
| 69 | on 'AMPERSAND' or 'VERTICAL_BAR' or 'CARET' or 'DOUBLE_LT' or 'DOUBLE_GT' |
|---|
| 70 | return BinaryBitwiseExpr(opToken, op, left, right) |
|---|
| 71 | on 'AMPERSAND_EQUALS' or 'VERTICAL_BAR_EQUALS' or 'CARET_EQUALS' or 'DOUBLE_LT_EQUALS' or 'DOUBLE_GT_EQUALS' |
|---|
| 72 | return AugAssignBitwiseExpr(opToken, op, left, right) |
|---|
| 73 | else |
|---|
| 74 | throw FallThroughException([opToken, op, left, right]) |
|---|
| 75 | |
|---|
| 76 | var _op as String |
|---|
| 77 | var _left as Expr |
|---|
| 78 | var _right as Expr |
|---|
| 79 | |
|---|
| 80 | cue init(opToken as IToken, op as String, left as Expr, right as Expr) |
|---|
| 81 | base.init(opToken) |
|---|
| 82 | _op = op |
|---|
| 83 | _left = left |
|---|
| 84 | _left.superNode = this |
|---|
| 85 | _right = right |
|---|
| 86 | _right.superNode = this |
|---|
| 87 | |
|---|
| 88 | get allExprs as Expr* |
|---|
| 89 | for expr in base.allExprs, yield expr |
|---|
| 90 | for expr in .left.allExprs, yield expr |
|---|
| 91 | for expr in .right.allExprs, yield expr |
|---|
| 92 | |
|---|
| 93 | get op from var |
|---|
| 94 | |
|---|
| 95 | get left from var |
|---|
| 96 | |
|---|
| 97 | get right from var |
|---|
| 98 | |
|---|
| 99 | get willChangeVar as bool is override |
|---|
| 100 | if _left.willChangeVar, return true |
|---|
| 101 | if _right.willChangeVar, return true |
|---|
| 102 | return false |
|---|
| 103 | |
|---|
| 104 | def _innerClone |
|---|
| 105 | base._innerClone |
|---|
| 106 | _left = _left.clone to Expr |
|---|
| 107 | _right = _right.clone to Expr |
|---|
| 108 | |
|---|
| 109 | def bindImp as dynamic |
|---|
| 110 | base.bindImp |
|---|
| 111 | assert .didBindImp |
|---|
| 112 | # TODO: maybe checks should only be invoked if left and right were bound without error |
|---|
| 113 | .checkAfterBindImp |
|---|
| 114 | .checkForVoids |
|---|
| 115 | return .bindImpResult |
|---|
| 116 | |
|---|
| 117 | def _bindImp |
|---|
| 118 | base._bindImp |
|---|
| 119 | assert _left is not this |
|---|
| 120 | assert _right is not this |
|---|
| 121 | # The try..catch below enables errors to be detected on both sides of the operator |
|---|
| 122 | try |
|---|
| 123 | _left.bindImp |
|---|
| 124 | catch ne1 as NodeException |
|---|
| 125 | gotError = true |
|---|
| 126 | if _left.type is nil |
|---|
| 127 | # assert _left.hasError to-do |
|---|
| 128 | _left.type = .compiler.passThroughType |
|---|
| 129 | |
|---|
| 130 | try |
|---|
| 131 | _right.bindImp |
|---|
| 132 | catch ne2 as NodeException |
|---|
| 133 | gotError = true |
|---|
| 134 | if _right.type is nil |
|---|
| 135 | # assert _left.hasError to-do |
|---|
| 136 | _right.type = .compiler.passThroughType |
|---|
| 137 | |
|---|
| 138 | if gotError |
|---|
| 139 | _type = .compiler.passThroughType |
|---|
| 140 | # to-do CC: past an exception catching block, the type of the var (eg ne1 and ne2) |
|---|
| 141 | # must be considered nilable |
|---|
| 142 | throw NodeMultiException(ne1, ne2) |
|---|
| 143 | assert _left.didBindImp |
|---|
| 144 | assert _right.didBindImp |
|---|
| 145 | |
|---|
| 146 | def checkAfterBindImp |
|---|
| 147 | """ |
|---|
| 148 | Invoked after `bindImp` in order to do more error checking. |
|---|
| 149 | So this is a hook for subclasses to do error checking after |
|---|
| 150 | left and right have been successfully bound. |
|---|
| 151 | """ |
|---|
| 152 | require .didBindImp |
|---|
| 153 | pass |
|---|
| 154 | |
|---|
| 155 | def checkForVoids |
|---|
| 156 | left = _left |
|---|
| 157 | right = _right |
|---|
| 158 | voidType = .compiler.voidType |
|---|
| 159 | # check for void on either side of the expression |
|---|
| 160 | sugg = 'Use a different method or change that method to return something.' |
|---|
| 161 | if left.type is voidType |
|---|
| 162 | if left inherits BinaryOpExpr and (left to BinaryOpExpr).op=='DOT' and (left to BinaryOpExpr).right inherits CallExpr # CC: axe casts |
|---|
| 163 | append = ' because "[((left to BinaryOpExpr).right to CallExpr).name]" does not return anything.' # CC: axe BinaryOp cast |
|---|
| 164 | else |
|---|
| 165 | append = '.' |
|---|
| 166 | .throwError('There is no type for the left hand side of "[.token.text]"[append] [sugg]') |
|---|
| 167 | if right.type is voidType and not right.isCalling |
|---|
| 168 | if right inherits BinaryOpExpr and (right to BinaryOpExpr).op=='DOT' and (right to BinaryOpExpr).right inherits CallExpr # CC: axe cast |
|---|
| 169 | append = ' because "[((right to BinaryOpExpr).right to CallExpr).name]" does not return anything.' # CC: axe BinaryOp cast |
|---|
| 170 | else |
|---|
| 171 | append = '.' |
|---|
| 172 | .throwError('There is no type for the right hand side of "[.token.text]"[append] [sugg]') |
|---|
| 173 | |
|---|
| 174 | def addSubFields |
|---|
| 175 | base.addSubFields |
|---|
| 176 | .addField('op', _op) |
|---|
| 177 | .addField('left', _left) |
|---|
| 178 | .addField('right', _right) |
|---|
| 179 | |
|---|
| 180 | def toCobraSource as String is override |
|---|
| 181 | return '([_left.toCobraSource] [.token.text] [_right.toCobraSource])' |
|---|
| 182 | |
|---|
| 183 | |
|---|
| 184 | class AbstractAssignExpr |
|---|
| 185 | is partial |
|---|
| 186 | inherits BinaryOpExpr |
|---|
| 187 | """ |
|---|
| 188 | The abstract base class for AssignExpr, AugAssignExpr, CoalesceAssignExpr and InverseCoalesceAssignExpr. |
|---|
| 189 | """ |
|---|
| 190 | |
|---|
| 191 | var _trackLocal as bool # for the detailed stack trace |
|---|
| 192 | var _trackName as String? |
|---|
| 193 | |
|---|
| 194 | cue init(opToken as IToken, op as String, left as Expr, right as Expr) |
|---|
| 195 | base.init(opToken, op, left, right) |
|---|
| 196 | |
|---|
| 197 | get canBeStatement as bool is override |
|---|
| 198 | return true |
|---|
| 199 | |
|---|
| 200 | get willChangeVar as bool is override |
|---|
| 201 | return true |
|---|
| 202 | |
|---|
| 203 | def checkAfterBindImp |
|---|
| 204 | base.checkAfterBindImp |
|---|
| 205 | # can't do the following in a `_bindImp` override since type inference is implemented in |
|---|
| 206 | # a subclass (_left may not have its _definition until after that executes) |
|---|
| 207 | if .compiler.willTrackLocals |
|---|
| 208 | if _left inherits NameExpr |
|---|
| 209 | _trackLocal = _left.definition inherits AbstractLocalVar |
|---|
| 210 | if _trackLocal, _trackName = _left.namedDefinition.name |
|---|
| 211 | if .curCodeMember and not .curCodeMember inherits Initializer and _left.definition inherits BoxVar and _left.definition.isReadOnly |
|---|
| 212 | .throwError('The readonly field "[_left.definition.name]" can only be assigned to in an initializer or the field\'s declaration.') |
|---|
| 213 | |
|---|
| 214 | |
|---|
| 215 | |
|---|
| 216 | class AssignExpr inherits AbstractAssignExpr is partial |
|---|
| 217 | |
|---|
| 218 | var _backUpIfInheritsStack as List<of IType>? |
|---|
| 219 | |
|---|
| 220 | cue init(opToken as IToken, op as String, left as Expr, right as Expr) |
|---|
| 221 | require op == 'ASSIGN' |
|---|
| 222 | base.init(opToken, op, left, right) |
|---|
| 223 | assert op == 'ASSIGN' |
|---|
| 224 | |
|---|
| 225 | def _bindImp |
|---|
| 226 | try |
|---|
| 227 | base._bindImp |
|---|
| 228 | catch NodeMultiException # TODO: try non multi error instead |
|---|
| 229 | # for a type inference situation, create a var of type passthrough for the left hand side |
|---|
| 230 | # to prevent superfluous errors in subsequent statements |
|---|
| 231 | left = _left |
|---|
| 232 | if left inherits IdentifierExpr |
|---|
| 233 | if left.definition is nil |
|---|
| 234 | definition = LocalVar(left.token, .compiler.passThroughType).bindAll to LocalVar # CC: axe cast when supporting 'as this' |
|---|
| 235 | .compiler.codeMemberStack.peek.addLocal(definition) |
|---|
| 236 | left.setDefinition(definition) |
|---|
| 237 | left.type = definition.type |
|---|
| 238 | if _type is nil |
|---|
| 239 | _type = .compiler.passThroughType |
|---|
| 240 | throw |
|---|
| 241 | if _left inherits DotExpr and _left.definition inherits Property and _left.definition.setPart is nil |
|---|
| 242 | .throwError('Cannot assign to property "[_left.definition.name]" because it is read only.') |
|---|
| 243 | left = _left |
|---|
| 244 | right = _right |
|---|
| 245 | assert right.type |
|---|
| 246 | if left inherits IdentifierExpr |
|---|
| 247 | if left.namedDefinition is nil |
|---|
| 248 | # type inference |
|---|
| 249 | assert right.type |
|---|
| 250 | if right.type is .compiler.passThroughType # TODO: ? change to unspecifiedType |
|---|
| 251 | if right.hasError |
|---|
| 252 | # for a type inference situation, create a var of type passthrough for the left hand side |
|---|
| 253 | # to prevent superfluous errors in subsequent statements |
|---|
| 254 | definition = LocalVar(left.token, .compiler.passThroughType).bindAll to LocalVar # CC: axe cast when supporting 'as this' |
|---|
| 255 | .compiler.codeMemberStack.peek.addLocal(definition) |
|---|
| 256 | left.setDefinition(definition) |
|---|
| 257 | left.type = definition.type |
|---|
| 258 | _type = left.type |
|---|
| 259 | return |
|---|
| 260 | else |
|---|
| 261 | .throwError('Cannot infer type for "[left.name]" because the type of the right hand expression is unknown.') |
|---|
| 262 | if left.name.startsWith('_') |
|---|
| 263 | .throwError('No class variable named "[left.name]" exists and local variable declarations cannot start with an underscore (_).') |
|---|
| 264 | inferredType = if(right.type inherits NilType, .compiler.nilableDynamicType, right.type) # `x = nil` for a new local var `x` is inferred as `dynamic?` |
|---|
| 265 | definition = LocalVar(left.token, inferredType).bindAll to LocalVar # CC: axe cast when supporting 'as this' |
|---|
| 266 | .compiler.codeMemberStack.peek.addLocal(definition) |
|---|
| 267 | left.setDefinition(definition) |
|---|
| 268 | left.type = definition.type |
|---|
| 269 | if left.definition is nil |
|---|
| 270 | left.throwUnknownIdError |
|---|
| 271 | if left.definition inherits LocalVar and (left.definition to LocalVar).name.startsWithNonLowerLetter |
|---|
| 272 | (left.definition to LocalVar).isUsed = true # suppress subsequent warning about unused variable |
|---|
| 273 | .throwError(ErrorMessages.localVariablesMustStartLowercase) |
|---|
| 274 | if not right.canBeAssignedTo(left.type to !) |
|---|
| 275 | okay = false |
|---|
| 276 | if left inherits IdentifierExpr |
|---|
| 277 | if (defi = left.namedDefinition) inherits IVar |
|---|
| 278 | if defi.attemptAssignmentOf(right.type to !) |
|---|
| 279 | _backUpIfInheritsStack = List<of IType>(defi.ifInheritsStack) # will need this later for code gen |
|---|
| 280 | okay = true |
|---|
| 281 | if not okay |
|---|
| 282 | .throwError('Incompatible types. Cannot assign value of type [right.type.name] on the right to [left.type.name] on the left.') |
|---|
| 283 | _type = left.type |
|---|
| 284 | right.contextType = _type |
|---|
| 285 | |
|---|
| 286 | def checkAfterBindImp |
|---|
| 287 | base.checkAfterBindImp |
|---|
| 288 | if not .hasError and _left.definition inherits Param |
|---|
| 289 | if _right.definition inherits BoxVar |
|---|
| 290 | # 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 |
|---|
| 291 | # TODO: this has a test case that generates no warnings. so the cobra test cases should probably barf on unexpected warnings |
|---|
| 292 | isException = false |
|---|
| 293 | if .parent inherits BlockStmt and (.parent to BlockStmt).parent inherits IfStmt |
|---|
| 294 | ifStmt = (.parent to BlockStmt).parent to IfStmt |
|---|
| 295 | if (bop = ifStmt.cond) inherits BinaryOpExpr |
|---|
| 296 | if bop.left.definition is _left.definition, isException = true |
|---|
| 297 | else if bop.right.definition is _right.definition, isException = true |
|---|
| 298 | if not isException |
|---|
| 299 | leftName = _left.definition.name |
|---|
| 300 | rightName = _right.definition.name |
|---|
| 301 | .compiler.warning(this, 'Setting a parameter ("[leftName]") to a class variable ("[rightName]") is often a mistake. You may want to reverse the assignment.') |
|---|
| 302 | |
|---|
| 303 | |
|---|
| 304 | class NumericPromoExpr inherits BinaryOpExpr is partial |
|---|
| 305 | |
|---|
| 306 | cue init(opToken as IToken, op as String, left as Expr, right as Expr) |
|---|
| 307 | base.init(opToken, op, left, right) |
|---|
| 308 | |
|---|
| 309 | get isConcated from var as bool |
|---|
| 310 | |
|---|
| 311 | def _bindImp |
|---|
| 312 | base._bindImp |
|---|
| 313 | if _left.type.isDynamic or _right.type.isDynamic |
|---|
| 314 | _type = .compiler.dynamicType |
|---|
| 315 | return |
|---|
| 316 | tint = .compiler.anyIntType |
|---|
| 317 | tdecimal = .compiler.decimalType |
|---|
| 318 | tfloat = .compiler.anyFloatType |
|---|
| 319 | tpassthrough = .compiler.passThroughType |
|---|
| 320 | tstring = .compiler.stringType |
|---|
| 321 | cannotMix = false |
|---|
| 322 | leftType = _left.type.nonNil |
|---|
| 323 | rightType = _right.type.nonNil |
|---|
| 324 | |
|---|
| 325 | tgcd = leftType.greatestCommonDenominatorWith(rightType) |
|---|
| 326 | |
|---|
| 327 | if leftType.isDescendantOf(tint) |
|---|
| 328 | if rightType.isDescendantOf(tint) |
|---|
| 329 | # special case: true division |
|---|
| 330 | # if both sides are ints, the type becomes decimal |
|---|
| 331 | if _op == 'SLASH' |
|---|
| 332 | _type = .compiler.numberType |
|---|
| 333 | else |
|---|
| 334 | _type = tgcd |
|---|
| 335 | else |
|---|
| 336 | cannotMix = _ifRightTypeThen(tdecimal, tdecimal, tfloat, tgcd) |
|---|
| 337 | else if leftType.isDescendantOf(tdecimal) |
|---|
| 338 | cannotMix = _ifRightTypeThen(tdecimal, tdecimal, tint, tdecimal) |
|---|
| 339 | else if leftType.isDescendantOf(tfloat) |
|---|
| 340 | cannotMix = _ifRightTypeThen(tfloat, tgcd, tint, tgcd) |
|---|
| 341 | else if leftType.isDescendantOf(tstring) |
|---|
| 342 | cannotMix = _ifRightTypeThen(tstring, tstring) |
|---|
| 343 | else if leftType inherits Box |
|---|
| 344 | if leftType.isGeneric and leftType.isDescendantOf(.compiler.collectionOfType.constructedTypeFor([leftType.genericParams[0]])) |
|---|
| 345 | if leftType.genericParams.count > 1 |
|---|
| 346 | .throwError('Cannot concatenate [leftType.name] on the left side of "+" because it has more than one generic parameter.') |
|---|
| 347 | if rightType inherits Box |
|---|
| 348 | if rightType.isGeneric and rightType.isDescendantOf(.compiler.enumerableOfType.constructedTypeFor([rightType.genericParams[0]])) |
|---|
| 349 | if rightType.genericParams.count > 1 |
|---|
| 350 | .throwError('Cannot concatenate [rightType.name] on the left side of "+" because it has more than one generic parameter.') |
|---|
| 351 | leftInner = leftType.genericParams[0] |
|---|
| 352 | rightInner = rightType.genericParams[0] |
|---|
| 353 | newInner = leftInner.greatestCommonDenominatorWith(rightInner) |
|---|
| 354 | _type = leftType.genericDef.constructedTypeFor([newInner]) |
|---|
| 355 | _isConcated = true |
|---|
| 356 | else if rightType.isDescendantOf(.compiler.enumerableType) |
|---|
| 357 | # casting will be done at run-time and either succeed or throw an exception |
|---|
| 358 | _type = leftType |
|---|
| 359 | _isConcated = true |
|---|
| 360 | else |
|---|
| 361 | cannotMix = true |
|---|
| 362 | else |
|---|
| 363 | cannotMix = true |
|---|
| 364 | if _type is nil |
|---|
| 365 | if _left.isKindOf(tpassthrough) and not _right.isKindOf(tpassthrough) |
|---|
| 366 | _type = rightType |
|---|
| 367 | else if _right.isKindOf(tpassthrough) and not _left.isKindOf(tpassthrough) |
|---|
| 368 | _type = leftType |
|---|
| 369 | else if _left.isKindOf(tpassthrough) or _right.isKindOf(tpassthrough) |
|---|
| 370 | _type = tpassthrough |
|---|
| 371 | else if leftType == rightType and leftType inherits EnumDecl |
|---|
| 372 | _type = leftType |
|---|
| 373 | else if cannotMix |
|---|
| 374 | .throwError('Cannot mix types [_left.type.name] and [_right.type.name] for arithmetic.') |
|---|
| 375 | if _type is nil |
|---|
| 376 | sugg = 'Try finding a method that performs this function.' |
|---|
| 377 | if leftType == rightType |
|---|
| 378 | .throwError('Cannot apply [_op] to [leftType.name]. [sugg]') |
|---|
| 379 | else |
|---|
| 380 | .throwError('Cannot apply [_op] to [leftType.name] and [rightType.name]. [sugg]') |
|---|
| 381 | |
|---|
| 382 | def _ifRightTypeThen(ifThenPairs as vari IType) as bool |
|---|
| 383 | rightType = _right.type.nonNil |
|---|
| 384 | i = 0 |
|---|
| 385 | while i < ifThenPairs.length |
|---|
| 386 | matchType, resultType = ifThenPairs[i], ifThenPairs[i+1] |
|---|
| 387 | i += 2 |
|---|
| 388 | if rightType.isDescendantOf(matchType) |
|---|
| 389 | _type = resultType |
|---|
| 390 | return false |
|---|
| 391 | return true |
|---|
| 392 | |
|---|
| 393 | |
|---|
| 394 | |
|---|
| 395 | class AugAssignMathExpr inherits NumericPromoExpr is partial |
|---|
| 396 | """ |
|---|
| 397 | TODO: How should: |
|---|
| 398 | someInt /= someInt |
|---|
| 399 | be handled given that |
|---|
| 400 | someInt / someInt |
|---|
| 401 | gives a decimal? |
|---|
| 402 | """ |
|---|
| 403 | |
|---|
| 404 | # TODO: does not inherit AbstractAssignExpr. |
|---|
| 405 | # TODO: Does not participate in -detailed-stack-trace |
|---|
| 406 | |
|---|
| 407 | cue init(opToken as IToken, op as String, left as Expr, right as Expr) |
|---|
| 408 | base.init(opToken, op, left, right) |
|---|
| 409 | assert op in ['PLUS_EQUALS', 'MINUS_EQUALS', 'STAR_EQUALS', 'STARSTAR_EQUALS', 'SLASH_EQUALS', 'SLASHSLASH_EQUALS', 'PERCENT_EQUALS'] |
|---|
| 410 | |
|---|
| 411 | get canBeStatement as bool is override |
|---|
| 412 | return true |
|---|
| 413 | |
|---|
| 414 | get willChangeVar as bool is override |
|---|
| 415 | return true |
|---|
| 416 | |
|---|
| 417 | def _bindImp |
|---|
| 418 | base._bindImp |
|---|
| 419 | # TODO: does NumericPromoExpr cover everything we need? |
|---|
| 420 | # C# and other languages use += and -= on delegates and events, but Cobra does not. |
|---|
| 421 | if .left.type inherits MethodSig |
|---|
| 422 | # TODO: error |
|---|
| 423 | pass |
|---|
| 424 | else if .left.type.nonNil.isDescendantOf(.compiler.delegateType) |
|---|
| 425 | leftSource = .left.toCobraSource |
|---|
| 426 | rightSource = .right.toCobraSource |
|---|
| 427 | if not .right inherits RefExpr, rightSource = 'ref ' + rightSource |
|---|
| 428 | branch .op |
|---|
| 429 | on 'PLUS_EQUALS' |
|---|
| 430 | msg = 'Cannot use "+=". ' |
|---|
| 431 | if .left.definition inherits BoxEvent, msg += 'Use "listen [leftSource], [rightSource]" for events.' |
|---|
| 432 | else, msg += 'Use "[leftSource] = Delegate.combine([leftSource], [rightSource])" for delegates.' |
|---|
| 433 | on 'MINUS_EQUALS' |
|---|
| 434 | msg = 'Cannot use "-=". ' |
|---|
| 435 | if .left.definition inherits BoxEvent, msg += 'Use "ignore [leftSource], [rightSource]" for events.' |
|---|
| 436 | else, msg += 'Use "[leftSource] = Delegate.remove([rightSource])" for delegates.' |
|---|
| 437 | else |
|---|
| 438 | msg = 'Cannot use the operator "[.token.text]" on events or delegates.' |
|---|
| 439 | .throwError(msg) |
|---|
| 440 | |
|---|
| 441 | class AugAssignBitwiseExpr |
|---|
| 442 | is partial |
|---|
| 443 | inherits NumericPromoExpr |
|---|
| 444 | """ |
|---|
| 445 | """ |
|---|
| 446 | |
|---|
| 447 | # TODO: does not inherit AbstractAssignExpr. Does not participate in super stack trace |
|---|
| 448 | |
|---|
| 449 | cue init(opToken as IToken, op as String, left as Expr, right as Expr) |
|---|
| 450 | base.init(opToken, op, left, right) |
|---|
| 451 | assert op in ['AMPERSAND_EQUALS', 'VERTICAL_BAR_EQUALS', 'CARET_EQUALS', 'DOUBLE_LT_EQUALS', 'DOUBLE_GT_EQUALS'] |
|---|
| 452 | |
|---|
| 453 | get canBeStatement as bool is override |
|---|
| 454 | return true |
|---|
| 455 | |
|---|
| 456 | get willChangeVar as bool is override |
|---|
| 457 | return true |
|---|
| 458 | |
|---|
| 459 | def _bindImp |
|---|
| 460 | base._bindImp |
|---|
| 461 | # TODO: does NumericPromoExpr cover everything we need? |
|---|
| 462 | |
|---|
| 463 | |
|---|
| 464 | class BinaryBoolExpr |
|---|
| 465 | is partial |
|---|
| 466 | inherits BinaryOpExpr |
|---|
| 467 | |
|---|
| 468 | cue init(opToken as IToken, op as String, left as Expr, right as Expr) |
|---|
| 469 | base.init(opToken, op, left, right) |
|---|
| 470 | assert op in ['AND', 'OR', 'IMPLIES'] |
|---|
| 471 | |
|---|
| 472 | def _bindImp |
|---|
| 473 | base._bindImp |
|---|
| 474 | _type = .compiler.boolType |
|---|
| 475 | if _left.type is not .compiler.boolType |
|---|
| 476 | _left = TruthExpr(_left).bindImp to TruthExpr # CC: axe cast after "as this" |
|---|
| 477 | if _right.type is not .compiler.boolType |
|---|
| 478 | _right = TruthExpr(_right).bindImp to TruthExpr # CC: axe cast after "as this" |
|---|
| 479 | |
|---|
| 480 | |
|---|
| 481 | class BinaryBitwiseExpr |
|---|
| 482 | is partial |
|---|
| 483 | inherits NumericPromoExpr |
|---|
| 484 | |
|---|
| 485 | cue init(opToken as IToken, op as String, left as Expr, right as Expr) |
|---|
| 486 | base.init(opToken, op, left, right) |
|---|
| 487 | assert op in ['AMPERSAND', 'VERTICAL_BAR', 'CARET', 'DOUBLE_LT', 'DOUBLE_GT'] |
|---|
| 488 | |
|---|
| 489 | def _bindImp |
|---|
| 490 | base._bindImp |
|---|
| 491 | |
|---|
| 492 | |
|---|
| 493 | class BinaryMathExpr |
|---|
| 494 | is partial |
|---|
| 495 | inherits NumericPromoExpr |
|---|
| 496 | |
|---|
| 497 | cue init(opToken as IToken, op as String, left as Expr, right as Expr) |
|---|
| 498 | base.init(opToken, op, left, right) |
|---|
| 499 | assert op in ['PLUS', 'MINUS', 'STAR', 'STARSTAR', 'SLASH', 'SLASHSLASH', 'PERCENT'] |
|---|
| 500 | |
|---|
| 501 | def _bindImp |
|---|
| 502 | base._bindImp |
|---|
| 503 | if .isConcated and .type, return |
|---|
| 504 | lt = _left.type.nonNil |
|---|
| 505 | rt = _right.type.nonNil |
|---|
| 506 | if lt is rt |
|---|
| 507 | assert _type == lt or (_type is .compiler.numberType and _op=='SLASH') |
|---|
| 508 | else if lt.isDynamic or rt.isDynamic |
|---|
| 509 | _type = .compiler.dynamicType |
|---|
| 510 | else if lt inherits PassThroughType |
|---|
| 511 | _type = rt |
|---|
| 512 | else if rt inherits PassThroughType |
|---|
| 513 | _type = lt |
|---|
| 514 | else |
|---|
| 515 | # TODO: this will need to be more sophisticated in the future with different sized ints and floats |
|---|
| 516 | if lt.isDescendantOf(.compiler.anyIntType) |
|---|
| 517 | _type = rt |
|---|
| 518 | else if rt.isDescendantOf(.compiler.anyIntType) |
|---|
| 519 | _type = lt |
|---|
| 520 | else if lt.isDescendantOf(.compiler.decimalType) |
|---|
| 521 | if rt.isDescendantOf(.compiler.anyFloatType) |
|---|
| 522 | .throwError('Cannot promote between decimal and float for operator "[_op]". Decimal is more accurate and float has more range.') |
|---|
| 523 | else |
|---|
| 524 | # int case already taken care of, so... |
|---|
| 525 | .throwError('Cannot promote between decimal and "[rt.name]".') |
|---|
| 526 | else if lt.isDescendantOf(.compiler.anyFloatType) |
|---|
| 527 | if rt.isDescendantOf(.compiler.decimalType) |
|---|
| 528 | .throwError('Cannot promote between float and decimal for operator "[_op]". Decimal is more accurate and float has more range.') |
|---|
| 529 | else |
|---|
| 530 | # int case already taken care of, so... |
|---|
| 531 | .throwError('Cannot promote between decimal and "[rt.name]".') |
|---|
| 532 | else |
|---|
| 533 | throw FallThroughException({'this': this, '_left': _left, 'op': _op, 'lt': lt, 'rt': rt}) |
|---|
| 534 | |
|---|
| 535 | |
|---|
| 536 | class CompareExpr |
|---|
| 537 | is partial |
|---|
| 538 | inherits BinaryOpExpr |
|---|
| 539 | |
|---|
| 540 | var _items as List<of Expr> |
|---|
| 541 | var _operations as List<of String> |
|---|
| 542 | |
|---|
| 543 | cue init(opToken as IToken, op as String, left as Expr, right as Expr) |
|---|
| 544 | require op in ['IS', 'ISNOT', 'EQ', 'NE', 'LT', 'GT', 'LE', 'GE'] |
|---|
| 545 | base.init(opToken, op, left, right) |
|---|
| 546 | _items = [left, right] |
|---|
| 547 | _operations = [op] |
|---|
| 548 | if op == 'ISNOT' and .token.text == 'is' |
|---|
| 549 | .token.text = 'is not' |
|---|
| 550 | |
|---|
| 551 | def addComparison(op as String, right as Expr) |
|---|
| 552 | _operations.add(op) |
|---|
| 553 | _items.add(right) |
|---|
| 554 | |
|---|
| 555 | def _bindImp |
|---|
| 556 | base._bindImp |
|---|
| 557 | _type = .compiler.boolType |
|---|
| 558 | if _items.count > 2 |
|---|
| 559 | # this transformation is done so that the back ends handle chained comparisons differently than normal comparisons |
|---|
| 560 | cce = ChainedCompareExpr(.token, _items, _operations) |
|---|
| 561 | cce.bindImp |
|---|
| 562 | _transformTo(cce) |
|---|
| 563 | return |
|---|
| 564 | if _left.type is nil or _right.type is nil |
|---|
| 565 | assert .hasError |
|---|
| 566 | return |
|---|
| 567 | leftType = _left.type to ! |
|---|
| 568 | rightType = _right.type to ! |
|---|
| 569 | innerMsg as String? |
|---|
| 570 | # 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 |
|---|
| 571 | if .op in ['EQ', 'NE', 'IS', 'ISNOT'] |
|---|
| 572 | if not leftType.isEquatableTo(rightType) |
|---|
| 573 | innerMsg = if(.op.startsWith('IS'), 'can never be identical', 'cannot be equated') |
|---|
| 574 | else if .op in ['IS', 'ISNOT'] |
|---|
| 575 | # Essentially "is" and "is not" are for reference types. Give a warning when used for value types. |
|---|
| 576 | if not leftType.isDynamicOrPassThrough and not rightType.isDynamicOrPassThrough |
|---|
| 577 | opName = if(.op=='IS', 'is', 'is not') |
|---|
| 578 | altName = if(.op=='IS', '==', '<>') |
|---|
| 579 | if leftType inherits NilableType and rightType inherits NilType |
|---|
| 580 | pass |
|---|
| 581 | else if leftType inherits NilType and rightType inherits NilableType |
|---|
| 582 | pass |
|---|
| 583 | else if not leftType.isReference and not rightType.isReference |
|---|
| 584 | .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.') |
|---|
| 585 | # interestingly, I don't think the following ever happen in practice... |
|---|
| 586 | else if not leftType.isReference and not rightType.isNilableAndDescendantOf(leftType) |
|---|
| 587 | .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]").') |
|---|
| 588 | else if not rightType.isReference and not leftType.isNilableAndDescendantOf(rightType) |
|---|
| 589 | .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]").') |
|---|
| 590 | else if .left.isObjectLiteral or .right.isObjectLiteral |
|---|
| 591 | .compiler.warning(this, 'Do not use the identity operator "[opName]" with an object literal. Use an equality operator such as "==" or "<>".') |
|---|
| 592 | else |
|---|
| 593 | if not leftType.isComparableTo(rightType) |
|---|
| 594 | innerMsg = 'cannot be compared' |
|---|
| 595 | if innerMsg |
|---|
| 596 | typeNames = if(_left.type.name==_right.type.name, 'type ("[_left.type.name]")', 'types ("[_left.type.name]" and "[_right.type.name]")') |
|---|
| 597 | msg = 'The left and right sides of the "[.token.text]" expression [innerMsg] because of their [typeNames].' |
|---|
| 598 | if .op == 'IS' and _right.type.isDescendantOf(.compiler.typeType) # C# |
|---|
| 599 | msg += ' Maybe you should try "inherits" instead of "is".' |
|---|
| 600 | .throwError(msg) |
|---|
| 601 | |
|---|
| 602 | def _isObjectLiteral(expr as Expr) as bool |
|---|
| 603 | if expr inherits StringLit, return true |
|---|
| 604 | if expr inherits CompositeLiteral, return true |
|---|
| 605 | return false |
|---|
| 606 | |
|---|
| 607 | interface IDotRightExpr |
|---|
| 608 | inherits IExpr |
|---|
| 609 | """ |
|---|
| 610 | IDotRightExpr is an expression that can appear on the right hand side of a DotExpr. |
|---|
| 611 | Implemented by MemberExpr and CallExpr. |
|---|
| 612 | """ |
|---|
| 613 | get name as String |
|---|
| 614 | get memberDefinition as IMember? |
|---|
| 615 | get args as List<of Expr> |
|---|
| 616 | |
|---|
| 617 | |
|---|
| 618 | class DotExpr inherits BinaryOpExpr is partial |
|---|
| 619 | implements IPotentialTypeExpr |
|---|
| 620 | """ |
|---|
| 621 | When creating a DotExpr, set .isImplicit to true if the original expression did not actually |
|---|
| 622 | contain a dot (.), but only implied it. For example: `_foo()` |
|---|
| 623 | |
|---|
| 624 | When DotExpr's are chained as in "a.b.c.d", the .right will always be an |
|---|
| 625 | IDotRightExpr (MemberExpr or CallExpr) and the .left will generally by the DotExpr |
|---|
| 626 | (except for the first one). |
|---|
| 627 | |
|---|
| 628 | a.b.c.d ==> |
|---|
| 629 | |
|---|
| 630 | . |
|---|
| 631 | / \ |
|---|
| 632 | . d |
|---|
| 633 | / \ |
|---|
| 634 | . c |
|---|
| 635 | / \ |
|---|
| 636 | a b |
|---|
| 637 | """ |
|---|
| 638 | |
|---|
| 639 | var _dotRightExpr as IDotRightExpr |
|---|
| 640 | |
|---|
| 641 | cue init(opToken as IToken, op as String, left as Expr, right as Expr) |
|---|
| 642 | require |
|---|
| 643 | op == 'DOT' |
|---|
| 644 | right inherits IDotRightExpr |
|---|
| 645 | body |
|---|
| 646 | base.init(opToken, op, left, right) |
|---|
| 647 | _dotRightExpr = right to IDotRightExpr |
|---|
| 648 | |
|---|
| 649 | def addMinFields |
|---|
| 650 | base.addMinFields |
|---|
| 651 | .addField('left.toCobraSource', .safeLeftToCobraSource) |
|---|
| 652 | .addField('right.toCobraSource', .safeRightToCobraSource) |
|---|
| 653 | |
|---|
| 654 | def safeLeftToCobraSource as String |
|---|
| 655 | try |
|---|
| 656 | return .left.toCobraSource |
|---|
| 657 | catch exc as Exception |
|---|
| 658 | return exc.toString |
|---|
| 659 | |
|---|
| 660 | def safeRightToCobraSource as String |
|---|
| 661 | try |
|---|
| 662 | return .right.toCobraSource |
|---|
| 663 | catch exc as Exception |
|---|
| 664 | return exc.toString |
|---|
| 665 | |
|---|
| 666 | get canBeStatement as bool is override |
|---|
| 667 | return true |
|---|
| 668 | |
|---|
| 669 | get definition is override |
|---|
| 670 | """ |
|---|
| 671 | The definition of a DotExpr is the definition of its .right which will be |
|---|
| 672 | a CallExpr or MemberExpr (via the interface IDotRightExpr). |
|---|
| 673 | """ |
|---|
| 674 | return .right.definition |
|---|
| 675 | |
|---|
| 676 | get dotRight as IDotRightExpr |
|---|
| 677 | """ |
|---|
| 678 | Returns .right but with the more narrow type of IDotRightExpr. |
|---|
| 679 | """ |
|---|
| 680 | return _dotRightExpr |
|---|
| 681 | |
|---|
| 682 | def _bindImp |
|---|
| 683 | base._bindImp |
|---|
| 684 | if _left inherits BaseLit |
|---|
| 685 | .compiler.curCodeMember.usesBase # use of base implies override if the containing member is not already marked new |
|---|
| 686 | else if _left inherits ThisLit |
|---|
| 687 | if not _left.isImplicit |
|---|
| 688 | .compiler.warning(this, 'An explicit "this" literal is unnecessary for accessing members of the current object. You can remove "this".') |
|---|
| 689 | else if not .isImplicit and .dotRight.name.startsWith('_') |
|---|
| 690 | .compiler.warning(this, 'An explicit dot (".") is unnecessary for accessing underscored members of the current object. You can remove ".".') |
|---|
| 691 | if not _type |
|---|
| 692 | assert _right.type |
|---|
| 693 | _type = _right.type |
|---|
| 694 | if _left.type.isDynamic |
|---|
| 695 | _type = _right.type |
|---|
| 696 | if not _type.isDynamic |
|---|
| 697 | # interesting example: d.getType |
|---|
| 698 | # where `d` is dynamic. The resulting type, however, is the .typeType because `getType` is recognized as a method of Object |
|---|
| 699 | _contextType = _type |
|---|
| 700 | else if not _left.hasError and not _right.hasError |
|---|
| 701 | if _left inherits IdentifierExpr # TODO: seems flawed. The error below should take place if left is a DotExpr |
|---|
| 702 | if _left.namedDefinition inherits Box # CC: combine with above if statement |
|---|
| 703 | if _dotRightExpr.memberDefinition is not nil and not _dotRightExpr.memberDefinition.isShared # when _dotRightExpr.memberDefinition is nil, it's a nested type |
|---|
| 704 | if _dotRightExpr.name == 'init' |
|---|
| 705 | # hack to support Application.init in ../HowTo/390-GTK.cobra |
|---|
| 706 | # a better hack would be to de the correct binding to the shared init method |
|---|
| 707 | pass |
|---|
| 708 | else |
|---|
| 709 | .throwError('Cannot access instance member "[_dotRightExpr.name]" on type "[_left.name]". Make the member "shared" or create an instance of the type.') |
|---|
| 710 | if _left inherits ThisOrBaseLit |
|---|
| 711 | 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" |
|---|
| 712 | .throwError('Cannot access instance member "[_dotRightExpr.name]" from the current shared member. Make the member "shared" or create an instance of the type.') |
|---|
| 713 | # Check for qualified type such as Foo.Bar |
|---|
| 714 | if .definition implements IType and .type.isDescendantOf(.compiler.typeType) |
|---|
| 715 | # A.B where A and B are namespaces or types |
|---|
| 716 | _transformTo(TypeExpr(.token, .definition to IType).bindImp) |
|---|
| 717 | else if (right = .right) inherits CallExpr |
|---|
| 718 | if right.memberDefinition implements IType |
|---|
| 719 | # A.B.C() where C is a type such as a class or struct |
|---|
| 720 | te = TypeExpr(.token, right.memberDefinition to IType) |
|---|
| 721 | te.bindImp |
|---|
| 722 | pc = PostCallExpr(right.token, te, right.args) |
|---|
| 723 | pc.bindImp |
|---|
| 724 | _transformTo(pc) |
|---|
| 725 | else |
|---|
| 726 | assert not _dotRightExpr.memberDefinition inherits MemberOverload or .compiler.refExprLevel > 0 |
|---|
| 727 | # _checkLeftHandNilable -- not ready for this yet |
|---|
| 728 | |
|---|
| 729 | def _checkLeftHandNilable |
|---|
| 730 | if _left inherits IdentifierExpr |
|---|
| 731 | defi = _left.namedDefinition |
|---|
| 732 | if defi inherits AbstractLocalVar |
|---|
| 733 | if _left.type inherits NilableType and not _left.type.isDynamic |
|---|
| 734 | .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.') |
|---|
| 735 | |
|---|
| 736 | def _help(hg as HelpGenerator) is override |
|---|
| 737 | type = nil |
|---|
| 738 | try |
|---|
| 739 | type = .left.receiverType ? .left.type |
|---|
| 740 | catch |
|---|
| 741 | pass |
|---|
| 742 | if type |
|---|
| 743 | terms = '[type.name] [.dotRight.name]' |
|---|
| 744 | hg.searchTerms.add(terms) |
|---|
| 745 | ns = type.parentNameSpace |
|---|
| 746 | if ns |
|---|
| 747 | if not ns.isRoot, hg.searchTerms.add(ns.fullName + ' ' + terms) |
|---|
| 748 | else if type inherits Box |
|---|
| 749 | pb = type.parentBox |
|---|
| 750 | if pb, hg.searchTerms.add(pb.qualifiedName + ' ' + terms) |
|---|
| 751 | _helpType(hg, type) |
|---|
| 752 | else |
|---|
| 753 | hg.searchTerms.add(.dotRight.name) |
|---|
| 754 | base._help(hg) |
|---|
| 755 | |
|---|
| 756 | def afterStatementBindImp |
|---|
| 757 | base.afterStatementBindImp |
|---|
| 758 | if not .hasError |
|---|
| 759 | # TODO: inheritance |
|---|
| 760 | # TODO: test on DLLs |
|---|
| 761 | member = _dotRightExpr.memberDefinition |
|---|
| 762 | if member inherits MemberOverload |
|---|
| 763 | member = member.members[0] |
|---|
| 764 | if member inherits BoxMember |
|---|
| 765 | post while member |
|---|
| 766 | found = false |
|---|
| 767 | for attrib in member.attributes |
|---|
| 768 | if attrib.name in ['MustUseResult', 'MustUseResultAttribute'] |
|---|
| 769 | found = true |
|---|
| 770 | break |
|---|
| 771 | if found |
|---|
| 772 | break |
|---|
| 773 | member = member.matchingBaseMember |
|---|
| 774 | if found |
|---|
| 775 | .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).') |
|---|
| 776 | |
|---|
| 777 | get potentialType as IType? |
|---|
| 778 | # overridden to return the type this dotted expr represents in those cases when it does represent a type |
|---|
| 779 | assert .didBindImp |
|---|
| 780 | if .definition inherits IType |
|---|
| 781 | return .definition |
|---|
| 782 | else |
|---|
| 783 | return nil |
|---|
| 784 | |
|---|
| 785 | def toCobraSource as String is override |
|---|
| 786 | if _left inherits ThisLit |
|---|
| 787 | return '.[_right.toCobraSource]' |
|---|
| 788 | else |
|---|
| 789 | return '[_left.toCobraSource].[_right.toCobraSource]' |
|---|
| 790 | |
|---|
| 791 | |
|---|
| 792 | class InExpr inherits BinaryOpExpr is partial |
|---|
| 793 | """ |
|---|
| 794 | Syntax: |
|---|
| 795 | <left> in <right> |
|---|
| 796 | <left> not in <right> |
|---|
| 797 | """ |
|---|
| 798 | |
|---|
| 799 | cue init(opToken as IToken, op as String, left as Expr, right as Expr) |
|---|
| 800 | base.init(opToken, op, left, right) |
|---|
| 801 | assert op in ['IN', 'NOTIN'] |
|---|
| 802 | |
|---|
| 803 | get containsExpr from var as Expr? |
|---|
| 804 | |
|---|
| 805 | get needsNilCheck from var as bool |
|---|
| 806 | |
|---|
| 807 | def _bindImp |
|---|
| 808 | base._bindImp |
|---|
| 809 | _type = .compiler.boolType |
|---|
| 810 | if not .hasError |
|---|
| 811 | if .right.type.innerType is nil |
|---|
| 812 | .recordError('The right-hand expression for "in" is of type "[.right.type.name]" which contains nothing.') |
|---|
| 813 | else if .left.type.nonNil inherits CharType and .right.type is .compiler.stringType |
|---|
| 814 | # special case because there is no String.contains that takes a char |
|---|
| 815 | # by not setting _contains, the code gen will use CobraImp.In |
|---|
| 816 | pass |
|---|
| 817 | else if not _canContain(.right.type to !, .left.type to !) |
|---|
| 818 | .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]".') |
|---|
| 819 | else if _right.type.nonNil inherits ArrayType |
|---|
| 820 | pass |
|---|
| 821 | else if not _right.type.isDynamic |
|---|
| 822 | # try to use right.contains(left) |
|---|
| 823 | # this is more direct and offers speed up on some classes like Set |
|---|
| 824 | contains = DotExpr(.token, 'DOT', _right, CallExpr(.token, 'contains', List<of Expr>([_left]), true, isImplicit=true)) |
|---|
| 825 | try |
|---|
| 826 | contains.bindImp |
|---|
| 827 | catch SourceException |
|---|
| 828 | pass |
|---|
| 829 | success |
|---|
| 830 | # TODO: Not sure what's going on here. Cobra thinks these have "contains" and C# says no way |
|---|
| 831 | # error: "System.Collections.Generic.Dictionary<string,int>.KeyCollection" does not contain a definition for "Contains" (C#) |
|---|
| 832 | # error: "System.Collections.Generic.Dictionary<string,int>.ValueCollection" does not contain a definition for "Contains" (C#) |
|---|
| 833 | if contains.type is .compiler.boolType and not _right.type.name.startsWith('KeyCollection<') and not _right.type.name.startsWith('ValueCollection<') |
|---|
| 834 | _containsExpr = contains |
|---|
| 835 | # optimize `key in dict.keys` to `dict.containsKey(key)` which eliminates a linear search |
|---|
| 836 | if _containsExpr is nil |
|---|
| 837 | if _right inherits DotExpr |
|---|
| 838 | if _right.right inherits MemberExpr and _ |
|---|
| 839 | (_right.right to MemberExpr).name == 'keys' and _ |
|---|
| 840 | _right.left.type inherits Box and _ |
|---|
| 841 | (_right.left.type to Box).isIndirectConstructionOf(.compiler.idictionaryOfType) |
|---|
| 842 | # then |
|---|
| 843 | contains = DotExpr(.token, 'DOT', _right.left, CallExpr(.token, 'containsKey', List<of Expr>([_left]), true, isImplicit=true)) |
|---|
| 844 | contains.bindImp |
|---|
| 845 | _containsExpr = contains |
|---|
| 846 | |
|---|
| 847 | def _canContain(containerType as IType, whatType as IType) as bool |
|---|
| 848 | it = containerType.innerType |
|---|
| 849 | if it and _isAssignableTo(whatType, it to !), return true |
|---|
| 850 | contains = containerType.memberForName('contains') |
|---|
| 851 | # TODO: handle method overloads |
|---|
| 852 | if contains inherits Method |
|---|
| 853 | if contains.params.count == 1 and _isAssignableTo(whatType, contains.params[0].type) |
|---|
| 854 | return true |
|---|
| 855 | return false |
|---|
| 856 | |
|---|
| 857 | def _isAssignableTo(a as IType, b as IType) as bool |
|---|
| 858 | if a.isAssignableTo(b), return true |
|---|
| 859 | if a inherits NilableType |
|---|
| 860 | if a.theWrappedType.isAssignableTo(b) |
|---|
| 861 | _needsNilCheck = true |
|---|
| 862 | return true |
|---|
| 863 | return false |
|---|
| 864 | |
|---|
| 865 | |
|---|
| 866 | class InheritsExpr inherits BinaryOpExpr is partial |
|---|
| 867 | |
|---|
| 868 | cue init(opToken as IToken, op as String, left as Expr, right as Expr) |
|---|
| 869 | base.init(opToken, op, left, right) |
|---|
| 870 | assert op in ['INHERITS', 'IMPLEMENTS'] |
|---|
| 871 | |
|---|
| 872 | def _bindImp |
|---|
| 873 | base._bindImp |
|---|
| 874 | _type = .compiler.boolType |
|---|
| 875 | if _right inherits IPotentialTypeExpr |
|---|
| 876 | rightType = _right.potentialType |
|---|
| 877 | if rightType |
|---|
| 878 | leftType = _left.type |
|---|
| 879 | if leftType |
|---|
| 880 | okay = true |
|---|
| 881 | if leftType.isDynamic |
|---|
| 882 | pass |
|---|
| 883 | else if leftType.isDescendantOf(rightType) |
|---|
| 884 | .compiler.warning(this, 'The expression is always of type "[.right.toCobraSource]".') |
|---|
| 885 | else if rightType.isDescendantOf(leftType) |
|---|
| 886 | # ex: someShape inherits Circle |
|---|
| 887 | pass |
|---|
| 888 | else if leftType inherits NilableType and not rightType inherits NilableType and rightType.isStrictDescendantOf(leftType.nonNil) |
|---|
| 889 | # ex: someNilableShape inherits Circle |
|---|
| 890 | pass |
|---|
| 891 | else if leftType inherits NilableType and not rightType inherits NilableType and leftType.nonNil.isDescendantOf(rightType) |
|---|
| 892 | # ex: someNilableCircle inherits Shape |
|---|
| 893 | .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]".') |
|---|
| 894 | else if rightType inherits Interface |
|---|
| 895 | if leftType inherits Class |
|---|
| 896 | pass |
|---|
| 897 | else if leftType inherits Struct |
|---|
| 898 | found = false |
|---|
| 899 | for leftInterface in leftType.baseInterfaces |
|---|
| 900 | if leftInterface.isDescendantOf(rightType) |
|---|
| 901 | .compiler.warning(this, 'The expression is always of type "[.right.toCobraSource]".') |
|---|
| 902 | found = true |
|---|
| 903 | break |
|---|
| 904 | if not found |
|---|
| 905 | .compiler.warning(this, 'The expression (of type "[leftType.name]") is never of type "[.right.toCobraSource]".') |
|---|
| 906 | else if leftType inherits Interface |
|---|
| 907 | # inheriting interface IA gives no information about what other interfaces objects of type IA inherit at run-time |
|---|
| 908 | pass |
|---|
| 909 | else |
|---|
| 910 | okay = false |
|---|
| 911 | if not okay |
|---|
| 912 | .compiler.warning(this, 'The expression (of type "[leftType.name]") is never of type "[.right.toCobraSource]".') |
|---|
| 913 | defi = .right.definition |
|---|
| 914 | if defi inherits Class |
|---|
| 915 | if .op <> 'INHERITS', .throwError('Use the "inherits" operator when testing objects against classes.') |
|---|
| 916 | else if defi inherits Struct |
|---|
| 917 | if .op <> 'INHERITS', .throwError('Use the "inherits" operator when testing objects against structs.') |
|---|
| 918 | else if defi inherits Interface |
|---|
| 919 | # got annoying: |
|---|
| 920 | # if .op <> 'IMPLEMENTS', .throwError('Use the "implements" operator when testing objects against interfaces.') |
|---|
| 921 | pass |
|---|
| 922 | else if defi inherits GenericParam |
|---|
| 923 | pass |
|---|
| 924 | else if defi inherits IType # int, decimal, etc. |
|---|
| 925 | if .op <> 'INHERITS', .throwError('Use the "inherits" operator when testing objects against structs.') |
|---|
| 926 | else if defi |
|---|
| 927 | .throwError('Cannot test "[.op.toLower]" against a [defi.englishName].') |
|---|
| 928 | |
|---|
| 929 | |
|---|
| 930 | class AbstractToExpr |
|---|
| 931 | is abstract |
|---|
| 932 | inherits BinaryOpExpr |
|---|
| 933 | |
|---|
| 934 | var _potentialTypeExpr as IPotentialTypeExpr |
|---|
| 935 | |
|---|
| 936 | cue init(opToken as IToken, op as String, left as Expr, right as Expr) |
|---|
| 937 | base.init(opToken, op, left, right) |
|---|
| 938 | assert right inherits IPotentialTypeExpr |
|---|
| 939 | _potentialTypeExpr = right to IPotentialTypeExpr |
|---|
| 940 | |
|---|
| 941 | def _bindImp |
|---|
| 942 | base._bindImp |
|---|
| 943 | if not _right.isKindOf(.compiler.typeType) and not _right.type inherits Box |
|---|
| 944 | .throwError('The "to" operator does not apply to "[_right.toCobraSource]". Use a type.') |
|---|
| 945 | _type = _potentialTypeExpr.potentialType |
|---|
| 946 | if _type is nil |
|---|
| 947 | .throwError('The expression "[_potentialTypeExpr.toCobraSource]" does not identify a type.') |
|---|
| 948 | # TODO: not quite ready for the following |
|---|
| 949 | # at the very least, need to enhance CobraType and descendants to have a superType of .objectType |
|---|
| 950 | # test case is 208-to-errors.cobra with substring 'can never be cast to a' |
|---|
| 951 | #else if _left.type and not _type.isDescendantOf(_left.type to !) |
|---|
| 952 | # .throwError('The given expression, of type "[_left.type.name]", can never be cast to a "[_potentialTypeExpr.toCobraSource]".') |
|---|
| 953 | |
|---|
| 954 | |
|---|
| 955 | class ToExpr |
|---|
| 956 | is partial |
|---|
| 957 | inherits AbstractToExpr |
|---|
| 958 | |
|---|
| 959 | var _rightTypeExpr as IPotentialTypeExpr |
|---|
| 960 | |
|---|
| 961 | cue init(opToken as IToken, op as String, left as Expr, right as Expr) |
|---|
| 962 | base.init(opToken, op, left, right) |
|---|
| 963 | assert right inherits IPotentialTypeExpr |
|---|
| 964 | _rightTypeExpr = right to IPotentialTypeExpr |
|---|
| 965 | |
|---|
| 966 | def _bindImp |
|---|
| 967 | base._bindImp |
|---|
| 968 | if _left inherits NilLiteral and not (_type inherits NilableType or _type inherits PassThroughType) |
|---|
| 969 | .throwError('Cannot cast nil to a non-nil type.') |
|---|
| 970 | rightType = _rightTypeExpr.potentialType |
|---|
| 971 | if rightType |
|---|
| 972 | if _left.type inherits NilableType and not rightType inherits NilableType |
|---|
| 973 | if _left.type.nonNil == rightType |
|---|
| 974 | .compiler.warning(this, 'The given expression is already a "[_rightTypeExpr.toCobraSource]", but nilable. You can just use "to !".') |
|---|
| 975 | else if not _left.type inherits NilableType and rightType inherits NilableType |
|---|
| 976 | if rightType.nonNil == _left.type |
|---|
| 977 | .compiler.warning(this, 'The given expression is already a "[_left.type.name]", but not nilable. You can just use "to ?".') |
|---|
| 978 | else if _left.type == rightType |
|---|
| 979 | .compiler.warning(this, 'The given expression is already a "[_rightTypeExpr.toCobraSource]" so the typecast is redundant. You can remove it.') |
|---|
| 980 | else if rightType inherits AbstractNumberType and _left.type.isDescendantOf(.compiler.stringType) |
|---|
| 981 | typeName = rightType.name |
|---|
| 982 | .recordError('Cannot cast a string to a numeric type. Consider using "[typeName].parse" or "[typeName].tryParse". Use "@help [typeName]" for details.') |
|---|
| 983 | else |
|---|
| 984 | .throwError('Cannot locate a type for the type cast "to [_rightTypeExpr.toCobraSource]".') |
|---|
| 985 | |
|---|
| 986 | |
|---|
| 987 | class ToQExpr inherits AbstractToExpr is partial |
|---|
| 988 | |
|---|
| 989 | cue init(opToken as IToken, op as String, left as Expr, right as Expr) |
|---|
| 990 | base.init(opToken, op, left, right) |
|---|
| 991 | |
|---|
| 992 | def _bindImp |
|---|
| 993 | base._bindImp |
|---|
| 994 | if not _type inherits NilableType |
|---|
| 995 | _type = .typeProvider.nilableType(_type to !) |
|---|
| 996 | # warn about redundancy |
|---|
| 997 | if not .left.hasError and not .right.hasError |
|---|
| 998 | rightTypeExpr = _right to IPotentialTypeExpr |
|---|
| 999 | rightType = rightTypeExpr.potentialType |
|---|
| 1000 | if rightType |
|---|
| 1001 | if _left.type inherits NilableType and not rightType inherits NilableType |
|---|
| 1002 | if _left.type.nonNil == rightType |
|---|
| 1003 | .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.') |
|---|
| 1004 | else if not _left.type inherits NilableType and rightType inherits NilableType |
|---|
| 1005 | pass # TODO: check rhs type is castable to lhs regardless of nilability |
|---|
| 1006 | else if _left.type == rightType |
|---|
| 1007 | .compiler.warning(this, 'The given expression is already a "[rightTypeExpr.toCobraSource]" so the typecast is redundant. You can remove it.') |
|---|
| 1008 | else |
|---|
| 1009 | .throwError('Cannot locate a type for the type cast "to? [rightTypeExpr.toCobraSource]".') |
|---|
| 1010 | |
|---|
| 1011 | |
|---|
| 1012 | class CoalesceExpr |
|---|
| 1013 | is partial |
|---|
| 1014 | inherits BinaryOpExpr |
|---|
| 1015 | """ |
|---|
| 1016 | x ? y ==> if(x is nil, y, x) |
|---|
| 1017 | but without the potential double evaluation of x, which could be a complex expression. |
|---|
| 1018 | """ |
|---|
| 1019 | |
|---|
| 1020 | cue init(opToken as IToken, op as String, left as Expr, right as Expr) |
|---|
| 1021 | base.init(opToken, op, left, right) |
|---|
| 1022 | |
|---|
| 1023 | def _bindImp |
|---|
| 1024 | base._bindImp |
|---|
| 1025 | ptt = .compiler.passThroughType |
|---|
| 1026 | if _left.type is ptt or _right.type is ptt |
|---|
| 1027 | # x ? y ...where either is typed as passthrough |
|---|
| 1028 | _type = ptt |
|---|
| 1029 | else if not _left.type inherits NilableType |
|---|
| 1030 | .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.') |
|---|
| 1031 | else if _left.type inherits NilableType and not _right.type inherits NilableType |
|---|
| 1032 | # x ? y ...where x can be nil, but y cannot |
|---|
| 1033 | _type = _left.type.nonNil # TODO: should be greatest common denominator between the two |
|---|
| 1034 | else |
|---|
| 1035 | # the catch all case |
|---|
| 1036 | _type = _left.type # TODO: should be greatest common denominator between the two |
|---|
| 1037 | |
|---|
| 1038 | |
|---|
| 1039 | class InverseCoalesceExpr |
|---|
| 1040 | is partial |
|---|
| 1041 | inherits BinaryOpExpr |
|---|
| 1042 | """ |
|---|
| 1043 | x ! y ==> if(x is not nil, y, x) |
|---|
| 1044 | but without the potential double evaluation of x, which could be a complex expression. |
|---|
| 1045 | """ |
|---|
| 1046 | |
|---|
| 1047 | cue init(opToken as IToken, op as String, left as Expr, right as Expr) |
|---|
| 1048 | base.init(opToken, op, left, right) |
|---|
| 1049 | |
|---|
| 1050 | def _bindImp |
|---|
| 1051 | base._bindImp |
|---|
| 1052 | left, right = .left, .right |
|---|
| 1053 | ptt = .compiler.passThroughType |
|---|
| 1054 | if left.type is ptt or right.type is ptt |
|---|
| 1055 | # x ! y ...where either is typed as passthrough |
|---|
| 1056 | _type = ptt |
|---|
| 1057 | else if not left.type inherits NilableType |
|---|
| 1058 | .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.') |
|---|
| 1059 | # TODO? |
|---|
| 1060 | # else if _right.type inherits NilableType and not _right.type inherits NilableType |
|---|
| 1061 | # # x ? y ...where x can be nil, but y cannot |
|---|
| 1062 | # _type = _left.type.nonNil # TODO: should be greatest common denominator between the two |
|---|
| 1063 | else |
|---|
| 1064 | # the catch all case |
|---|
| 1065 | _type = left.type.greatestCommonDenominatorWith(right.type to !) |
|---|
| 1066 | |
|---|
| 1067 | |
|---|
| 1068 | class CoalesceAssignExpr |
|---|
| 1069 | is partial |
|---|
| 1070 | inherits AbstractAssignExpr |
|---|
| 1071 | """ |
|---|
| 1072 | x ?= y |
|---|
| 1073 | """ |
|---|
| 1074 | |
|---|
| 1075 | cue init(opToken as IToken, op as String, left as Expr, right as Expr) |
|---|
| 1076 | base.init(opToken, op, left, right) |
|---|
| 1077 | |
|---|
| 1078 | def _bindImp |
|---|
| 1079 | base._bindImp |
|---|
| 1080 | if not _left.type inherits NilableType |
|---|
| 1081 | .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.') |
|---|
| 1082 | # TODO: error check that the right hand type is not nilable? |
|---|
| 1083 | _type = _right.type |
|---|
| 1084 | |
|---|
| 1085 | |
|---|
| 1086 | class InverseCoalesceAssignExpr |
|---|
| 1087 | is partial |
|---|
| 1088 | inherits AbstractAssignExpr |
|---|
| 1089 | """ |
|---|
| 1090 | x != y |
|---|
| 1091 | """ |
|---|
| 1092 | |
|---|
| 1093 | cue init(opToken as IToken, op as String, left as Expr, right as Expr) |
|---|
| 1094 | base.init(opToken, op, left, right) |
|---|
| 1095 | |
|---|
| 1096 | def _bindImp |
|---|
| 1097 | base._bindImp |
|---|
| 1098 | left, right = .left, .right |
|---|
| 1099 | if not left.type inherits NilableType |
|---|
| 1100 | correct = 'bang-equals' in .compiler.options.setValue('correct-source') |
|---|
| 1101 | if correct and left.type.isComparableTo(right.type to !) |
|---|
| 1102 | .compiler.correctSource(.token, '<>') |
|---|
| 1103 | _transformTo(CompareExpr(.token.copy('NE', '<>'), 'NE', left, right)) |
|---|
| 1104 | return |
|---|
| 1105 | else |
|---|
| 1106 | .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.') |
|---|
| 1107 | _type = left.type.greatestCommonDenominatorWith(right.type to !) |
|---|