plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
plum-checker/tests/checker_tests.rs
| 3d6f280 | 1 | #![allow(non_snake_case)] |
| 03e5a61 | 2 | use plum_checker::types::*; |
| 3d6f280 | 3 | use plum_checker::{checkSource, plumTypeFromAst, unify}; |
| 30f1008 | 4 | use plum_core::ast::Type as AstType; |
| 729d7cb | 5 | use plum_core::{ast::*, AstParser}; |
| 729d7cb | 6 | |
| 729d7cb | 7 | fn parse(src: &str) -> Source { |
| 729d7cb | 8 | let mut parser = tree_sitter::Parser::new(); |
| 729d7cb | 9 | parser.set_language(&tree_sitter_plum::LANGUAGE.into()).unwrap(); |
| 729d7cb | 10 | let tree = parser.parse(src, None).unwrap(); |
| 729d7cb | 11 | let ap = AstParser::new(src); |
| 3d6f280 | 12 | ap.parseSource(tree.root_node()) |
| 729d7cb | 13 | } |
| 30f1008 | 14 | |
| 30f1008 | 15 | #[test] |
| 3d6f280 | 16 | fn astTypeIntMapsToTint() { |
| 30f1008 | 17 | let ast_ty = AstType { name: "Int".to_string(), generics: vec![] }; |
| 3d6f280 | 18 | assert_eq!(plumTypeFromAst(&ast_ty), PlumType::TInt); |
| 30f1008 | 19 | } |
| 30f1008 | 20 | |
| 30f1008 | 21 | #[test] |
| 3d6f280 | 22 | fn astTypeUnknownMapsToNamed() { |
| 30f1008 | 23 | let ast_ty = AstType { name: "MyClass".to_string(), generics: vec![] }; |
| 3d6f280 | 24 | assert_eq!(plumTypeFromAst(&ast_ty), PlumType::TNamed("MyClass".to_string())); |
| 30f1008 | 25 | } |
| 30f1008 | 26 | |
| 30f1008 | 27 | #[test] |
| 3d6f280 | 28 | fn unifySameTypesOk() { |
| 30f1008 | 29 | assert!(unify(&PlumType::TInt, &PlumType::TInt).is_ok()); |
| 30f1008 | 30 | assert!(unify(&PlumType::TFloat, &PlumType::TFloat).is_ok()); |
| 30f1008 | 31 | } |
| 30f1008 | 32 | |
| 30f1008 | 33 | #[test] |
| 3d6f280 | 34 | fn unifyDifferentTypesErr() { |
| 30f1008 | 35 | assert!(unify(&PlumType::TInt, &PlumType::TFloat).is_err()); |
| 30f1008 | 36 | } |
| 03e5a61 | 37 | |
| 03e5a61 | 38 | #[test] |
| 3d6f280 | 39 | fn freshVarsAreUnique() { |
| 03e5a61 | 40 | let mut state = InferState::new(); |
| 3d6f280 | 41 | let a = state.freshVar(); |
| 3d6f280 | 42 | let b = state.freshVar(); |
| 03e5a61 | 43 | assert_ne!(a, b); |
| 03e5a61 | 44 | assert_eq!(a, "a0"); |
| 03e5a61 | 45 | assert_eq!(b, "a1"); |
| 03e5a61 | 46 | } |
| 03e5a61 | 47 | |
| 03e5a61 | 48 | #[test] |
| 3d6f280 | 49 | fn monoScheme() { |
| 03e5a61 | 50 | let scheme = TypeScheme::mono(PlumType::TInt); |
| 03e5a61 | 51 | assert!(scheme.vars.is_empty()); |
| 03e5a61 | 52 | assert_eq!(*scheme.body, PlumType::TInt); |
| 03e5a61 | 53 | } |
| 729d7cb | 54 | |
| 729d7cb | 55 | #[test] |
| 3d6f280 | 56 | fn validAddFnPasses() { |
| b48d3a3 | 57 | let src = "fun add(a: Int, b: Int) -> Int =\n a + b\n"; |
| 729d7cb | 58 | let source = parse(src); |
| 3d6f280 | 59 | assert!(checkSource(&source).is_ok(), "expected Ok"); |
| 729d7cb | 60 | } |
| 729d7cb | 61 | |
| 729d7cb | 62 | #[test] |
| 3d6f280 | 63 | fn wrongReturnTypeIsError() { |
| b48d3a3 | 64 | let src = "fun bad() -> Int =\n True\n"; |
| 729d7cb | 65 | let source = parse(src); |
| 3d6f280 | 66 | let result = checkSource(&source); |
| 729d7cb | 67 | assert!(result.is_err()); |
| 729d7cb | 68 | let errs = result.unwrap_err(); |
| 729d7cb | 69 | assert!(errs[0].message.contains("return type mismatch"), "got: {}", errs[0].message); |
| 729d7cb | 70 | } |
| 729d7cb | 71 | |
| 729d7cb | 72 | #[test] |
| 3d6f280 | 73 | fn undeclaredVarIsError() { |
| b48d3a3 | 74 | let src = "fun bad() -> Int =\n x\n"; |
| 729d7cb | 75 | let source = parse(src); |
| 3d6f280 | 76 | let result = checkSource(&source); |
| 729d7cb | 77 | assert!(result.is_err()); |
| 729d7cb | 78 | } |
| 729d7cb | 79 | |
| 729d7cb | 80 | #[test] |
| 3d6f280 | 81 | fn typeMismatchInBinaryOpIsError() { |
| b48d3a3 | 82 | let src = "fun bad() -> Int =\n 1 + 2.0\n"; |
| 729d7cb | 83 | let source = parse(src); |
| 3d6f280 | 84 | let result = checkSource(&source); |
| 729d7cb | 85 | assert!(result.is_err()); |
| 729d7cb | 86 | } |
| d1a4183 | 87 | |
| d1a4183 | 88 | #[test] |
| 3d6f280 | 89 | fn boolLiteralTrueFalseAreBool() { |
| b48d3a3 | 90 | let src = "fun isTrue() -> Bool =\n True\n"; |
| d1a4183 | 91 | let source = parse(src); |
| 3d6f280 | 92 | assert!(checkSource(&source).is_ok(), "expected Ok, got {:?}", checkSource(&source).err()); |
| d1a4183 | 93 | } |
| d1a4183 | 94 | |
| d1a4183 | 95 | #[test] |
| 3d6f280 | 96 | fn boolLiteralWrongReturnTypeIsError() { |
| b48d3a3 | 97 | let src = "fun bad() -> Int =\n False\n"; |
| d1a4183 | 98 | let source = parse(src); |
| 3d6f280 | 99 | let result = checkSource(&source); |
| d1a4183 | 100 | assert!(result.is_err()); |
| d1a4183 | 101 | } |
| d1a4183 | 102 | |
| d1a4183 | 103 | #[test] |
| 3d6f280 | 104 | fn methodSelfFieldAccessPasses() { |
| 805d96d | 105 | let src = "type Cat =\n name: Str\n age: Int\n\n fun getName() -> Str =\n self.name\n"; |
| d1a4183 | 106 | let source = parse(src); |
| 3d6f280 | 107 | let result = checkSource(&source); |
| d1a4183 | 108 | assert!(result.is_ok(), "expected Ok, got {:?}", result.err()); |
| d1a4183 | 109 | } |
| d1a4183 | 110 | |
| d1a4183 | 111 | #[test] |
| 3d6f280 | 112 | fn methodSelfUnknownFieldIsError() { |
| 7056de2 | 113 | let src = "type Cat =\n name: Str\n\n fun getAge() -> Int =\n self.age\n"; |
| d1a4183 | 114 | let source = parse(src); |
| 3d6f280 | 115 | let result = checkSource(&source); |
| d1a4183 | 116 | assert!(result.is_err()); |
| d1a4183 | 117 | } |
| d1a4183 | 118 | |
| d2640d2 | 119 | #[test] |
| 3d6f280 | 120 | fn nestedMethodTypeChecks() { |
| 805d96d | 121 | let src = "type Cat =\n name: Str\n age: Int\n\n fun getName(self) -> Str =\n self.name\n"; |
| 3d6f280 | 122 | let result = checkSource(&parse(src)); |
| 805d96d | 123 | assert!(result.is_ok(), "expected Ok, got {:?}", result.err()); |
| d2640d2 | 124 | } |
| d2640d2 | 125 | |
| d2640d2 | 126 | #[test] |
| 3d6f280 | 127 | fn nestedMethodUnknownFieldIsError() { |
| 805d96d | 128 | let src = "type Cat =\n name: Str\n\n fun getAge(self) -> Int =\n self.age\n"; |
| 3d6f280 | 129 | assert!(checkSource(&parse(src)).is_err()); |
| d2640d2 | 130 | } |
| d2640d2 | 131 | |
| d1a4183 | 132 | #[test] |
| 3d6f280 | 133 | fn selfOutsideMethodIsError() { |
| b48d3a3 | 134 | let src = "fun bad() -> Int =\n self\n"; |
| d1a4183 | 135 | let source = parse(src); |
| 3d6f280 | 136 | let result = checkSource(&source); |
| d1a4183 | 137 | assert!(result.is_err()); |
| d1a4183 | 138 | } |
| d1a4183 | 139 | |
| d1a4183 | 140 | #[test] |
| 3d6f280 | 141 | fn classCallChecksFieldTypes() { |
| b48d3a3 | 142 | let src = "type Cat =\n name: Str\n age: Int\n\nfun makeCat() -> Cat =\n Cat(name: \"x\", age: 1)\n"; |
| d1a4183 | 143 | let source = parse(src); |
| 3d6f280 | 144 | let result = checkSource(&source); |
| d1a4183 | 145 | assert!(result.is_ok(), "expected Ok, got {:?}", result.err()); |
| d1a4183 | 146 | } |
| d1a4183 | 147 | |
| d1a4183 | 148 | #[test] |
| 3d6f280 | 149 | fn classCallWrongFieldTypeIsError() { |
| b48d3a3 | 150 | let src = "type Cat =\n name: Str\n age: Int\n\nfun makeCat() -> Cat =\n Cat(name: \"x\", age: \"y\")\n"; |
| d1a4183 | 151 | let source = parse(src); |
| 3d6f280 | 152 | let result = checkSource(&source); |
| d1a4183 | 153 | assert!(result.is_err()); |
| d1a4183 | 154 | } |
| d1a4183 | 155 | |
| d1a4183 | 156 | #[test] |
| 3d6f280 | 157 | fn classCallUnknownFieldIsError() { |
| b48d3a3 | 158 | let src = "type Cat =\n name: Str\n\nfun makeCat() -> Cat =\n Cat(name: \"x\", age: 1)\n"; |
| d1a4183 | 159 | let source = parse(src); |
| 3d6f280 | 160 | let result = checkSource(&source); |
| d1a4183 | 161 | assert!(result.is_err()); |
| d1a4183 | 162 | } |
| d1a4183 | 163 | |
| d1a4183 | 164 | #[test] |
| 3d6f280 | 165 | fn methodCallViaAttributeTypeChecksArgs() { |
| 7056de2 | 166 | let src = "type Cat =\n name: Str\n\n fun rename(n: Str) -> Str =\n n\n\nfun use(c: Cat) -> Str =\n c.rename(\"x\")\n"; |
| d1a4183 | 167 | let source = parse(src); |
| 3d6f280 | 168 | let result = checkSource(&source); |
| d1a4183 | 169 | assert!(result.is_ok(), "expected Ok, got {:?}", result.err()); |
| d1a4183 | 170 | } |
| d1a4183 | 171 | |
| d1a4183 | 172 | #[test] |
| 3d6f280 | 173 | fn matchBindsNamePatternToSubjectType() { |
| b48d3a3 | 174 | let src = "fun main(a: Int) -> Int =\n match a\n x =>\n x\n _ =>\n 0\n"; |
| d1a4183 | 175 | let source = parse(src); |
| 3d6f280 | 176 | let result = checkSource(&source); |
| d1a4183 | 177 | assert!(result.is_ok(), "expected Ok, got {:?}", result.err()); |
| d1a4183 | 178 | } |
| d1a4183 | 179 | |
| 660674c | 180 | #[test] |
| 3d6f280 | 181 | fn matchInlineCaseBodyTypeChecks() { |
| 660674c | 182 | // Case bodies can be a single inline expression, not just an indented block. |
| b48d3a3 | 183 | let src = "fun main(a: Int) -> Int =\n match a\n 1 => 10\n _ => 0\n"; |
| 660674c | 184 | let source = parse(src); |
| 3d6f280 | 185 | let result = checkSource(&source); |
| 660674c | 186 | assert!(result.is_ok(), "expected Ok, got {:?}", result.err()); |
| 660674c | 187 | } |
| 660674c | 188 | |
| d1a4183 | 189 | #[test] |
| 3d6f280 | 190 | fn matchTrueFalseAreVariantPatternsNotBindingsWithoutEnumDecl() { |
| d1a4183 | 191 | // True/False are built-in Bool variants — they must be recognized as tag |
| d1a4183 | 192 | // comparisons even when the source doesn't redeclare `enum Bool`, so a |
| d1a4183 | 193 | // later `_` wildcard arm remains reachable (each pattern binds/compares, |
| d1a4183 | 194 | // it doesn't just re-bind the subject under the name "True"). |
| b48d3a3 | 195 | let src = "fun pick(a: Bool) -> Int =\n match a\n True =>\n 1\n False =>\n 0\n"; |
| d1a4183 | 196 | let source = parse(src); |
| 3d6f280 | 197 | let result = checkSource(&source); |
| d1a4183 | 198 | assert!(result.is_ok(), "expected Ok, got {:?}", result.err()); |
| d1a4183 | 199 | } |
| d1a4183 | 200 | |
| d1a4183 | 201 | #[test] |
| 3d6f280 | 202 | fn matchIntPatternAgainstStrSubjectIsError() { |
| b48d3a3 | 203 | let src = "fun main(a: Str) -> Int =\n match a\n 1 =>\n 1\n _ =>\n 0\n"; |
| d1a4183 | 204 | let source = parse(src); |
| 3d6f280 | 205 | let result = checkSource(&source); |
| d1a4183 | 206 | assert!(result.is_err()); |
| d1a4183 | 207 | } |
| 7ac1d37 | 208 | |
| 7ac1d37 | 209 | #[test] |
| 3d6f280 | 210 | fn bareEnumTagUnifiesWithOwningEnumType() { |
| 7ac1d37 | 211 | // Regression: a bare non-Bool tag like `None` used to type as `TNamed("None")` |
| 7ac1d37 | 212 | // (itself, not its enum), so comparing it against an `Option` value would wrongly |
| 7ac1d37 | 213 | // fail with a type mismatch. |
| 7ac1d37 | 214 | let src = "\ |
| 7ac1d37 | 215 | enum Option = |
| 9d5550e | 216 | | Some[Int] |
| 7ac1d37 | 217 | | None |
| 7ac1d37 | 218 | |
| b48d3a3 | 219 | fun isNone(o: Option) -> Bool = o == None |
| 7ac1d37 | 220 | "; |
| 7ac1d37 | 221 | let source = parse(src); |
| 3d6f280 | 222 | let result = checkSource(&source); |
| 7ac1d37 | 223 | assert!(result.is_ok(), "expected Ok, got {:?}", result.err()); |
| 7ac1d37 | 224 | } |
| 7ac1d37 | 225 | |
| 7ac1d37 | 226 | #[test] |
| 3d6f280 | 227 | fn variantConstructionChecksArgCountAndTypes() { |
| 7ac1d37 | 228 | let src = "\ |
| 7ac1d37 | 229 | enum Option = |
| 30ae945 | 230 | | Some[Int] |
| 7ac1d37 | 231 | | None |
| 7ac1d37 | 232 | |
| b48d3a3 | 233 | fun makeSome(v: Int) -> Option = |
| 7ac1d37 | 234 | Some(v) |
| 7ac1d37 | 235 | "; |
| 7ac1d37 | 236 | let source = parse(src); |
| 3d6f280 | 237 | let result = checkSource(&source); |
| 7ac1d37 | 238 | assert!(result.is_ok(), "expected Ok, got {:?}", result.err()); |
| 7ac1d37 | 239 | } |
| 7ac1d37 | 240 | |
| 7ac1d37 | 241 | #[test] |
| 3d6f280 | 242 | fn variantConstructionWrongArgTypeIsError() { |
| 7ac1d37 | 243 | let src = "\ |
| 7ac1d37 | 244 | enum Option = |
| 7ac1d37 | 245 | | Some(Int) |
| 7ac1d37 | 246 | | None |
| 7ac1d37 | 247 | |
| b48d3a3 | 248 | fun bad() -> Option = |
| 7ac1d37 | 249 | Some(\"x\") |
| 7ac1d37 | 250 | "; |
| 7ac1d37 | 251 | let source = parse(src); |
| 3d6f280 | 252 | let result = checkSource(&source); |
| 7ac1d37 | 253 | assert!(result.is_err()); |
| 7ac1d37 | 254 | } |
| 7ac1d37 | 255 | |
| 7ac1d37 | 256 | #[test] |
| 3d6f280 | 257 | fn variantConstructionWrongArgCountIsError() { |
| 7ac1d37 | 258 | let src = "\ |
| 7ac1d37 | 259 | enum Shape = |
| 30ae945 | 260 | | Rect[Float, Float] |
| 30ae945 | 261 | | Circle[Float] |
| 7ac1d37 | 262 | |
| b48d3a3 | 263 | fun bad() -> Shape = |
| 7ac1d37 | 264 | Rect(1.0) |
| 7ac1d37 | 265 | "; |
| 7ac1d37 | 266 | let source = parse(src); |
| 3d6f280 | 267 | let result = checkSource(&source); |
| 7ac1d37 | 268 | assert!(result.is_err()); |
| 7ac1d37 | 269 | } |
| 7ac1d37 | 270 | |
| 4fda634 | 271 | #[test] |
| 3d6f280 | 272 | fn enumDiscriminantValuesTypeCheckWithNoErrors() { |
| 4fda634 | 273 | let src = "\ |
| 4fda634 | 274 | enum Step(n: Int) = |
| 4fda634 | 275 | | ReadMin(0) |
| 4fda634 | 276 | | ReadMax(1) |
| 4fda634 | 277 | "; |
| 4fda634 | 278 | let source = parse(src); |
| 3d6f280 | 279 | let result = checkSource(&source); |
| 4fda634 | 280 | assert!(result.is_ok(), "expected Ok, got {:?}", result.err()); |
| 4fda634 | 281 | } |
| 4fda634 | 282 | |
| 4fda634 | 283 | #[test] |
| 3d6f280 | 284 | fn enumDiscriminantWrongValueCountIsError() { |
| 4fda634 | 285 | let src = "\ |
| 4fda634 | 286 | enum Step(n: Int) = |
| 4fda634 | 287 | | ReadMin(0) |
| 4fda634 | 288 | | ReadMax |
| 4fda634 | 289 | "; |
| 4fda634 | 290 | let source = parse(src); |
| 3d6f280 | 291 | let result = checkSource(&source); |
| 4fda634 | 292 | assert!(result.is_err()); |
| 4fda634 | 293 | } |
| 4fda634 | 294 | |
| 4fda634 | 295 | #[test] |
| 3d6f280 | 296 | fn enumDiscriminantWronglyTypedValueIsError() { |
| 4fda634 | 297 | let src = "\ |
| 4fda634 | 298 | enum Step(n: Int) = |
| 4fda634 | 299 | | ReadMin(0) |
| 4fda634 | 300 | | ReadMax(\"x\") |
| 4fda634 | 301 | "; |
| 4fda634 | 302 | let source = parse(src); |
| 3d6f280 | 303 | let result = checkSource(&source); |
| 4fda634 | 304 | assert!(result.is_err()); |
| 4fda634 | 305 | } |
| 4fda634 | 306 | |
| 4fda634 | 307 | #[test] |
| 3d6f280 | 308 | fn enumDiscriminantValueOnParamLessEnumIsError() { |
| 4fda634 | 309 | let src = "\ |
| 4fda634 | 310 | enum Option = |
| 4fda634 | 311 | | Some(5) |
| 4fda634 | 312 | | None |
| 4fda634 | 313 | "; |
| 4fda634 | 314 | let source = parse(src); |
| 3d6f280 | 315 | let result = checkSource(&source); |
| 4fda634 | 316 | assert!(result.is_err()); |
| 4fda634 | 317 | } |
| 4fda634 | 318 | |
| 4fda634 | 319 | #[test] |
| 3d6f280 | 320 | fn fieldAccessOnDiscriminantEnumReceiverTypeChecks() { |
| 4fda634 | 321 | let src = "\ |
| 4fda634 | 322 | enum Step(n: Int) = |
| 4fda634 | 323 | | ReadMin(0) |
| 4fda634 | 324 | | ReadMax(1) |
| 4fda634 | 325 | |
| 4fda634 | 326 | fun toNumber(self) -> Int = |
| 4fda634 | 327 | self.n |
| 4fda634 | 328 | "; |
| 4fda634 | 329 | let source = parse(src); |
| 3d6f280 | 330 | let result = checkSource(&source); |
| 4fda634 | 331 | assert!(result.is_ok(), "expected Ok, got {:?}", result.err()); |
| 4fda634 | 332 | } |
| 4fda634 | 333 | |
| 4fda634 | 334 | #[test] |
| 3d6f280 | 335 | fn fieldAccessOnOrdinaryEnumReceiverIsUnaffectedByEnumParams() { |
| 4fda634 | 336 | // An ordinary enum (no discriminant `params`) isn't in `ctx.classes` OR |
| 4fda634 | 337 | // `ctx.enum_params`, so it falls through to the same permissive "unmodeled |
| 4fda634 | 338 | // type" escape hatch every other type not in `ctx.classes` gets (codegen, |
| 4fda634 | 339 | // not the checker, is what would catch a genuinely bad field access here) — |
| 4fda634 | 340 | // exactly as it did before discriminant enums existed. |
| 4fda634 | 341 | let src = "\ |
| 4fda634 | 342 | enum Option = |
| 4fda634 | 343 | | Some[Int] |
| 4fda634 | 344 | | None |
| 4fda634 | 345 | |
| 4fda634 | 346 | fun bad(self) -> Int = |
| 4fda634 | 347 | self.n |
| 4fda634 | 348 | "; |
| 4fda634 | 349 | let source = parse(src); |
| 3d6f280 | 350 | let result = checkSource(&source); |
| 4fda634 | 351 | assert!(result.is_ok(), "expected Ok, got {:?}", result.err()); |
| 4fda634 | 352 | } |
| 4fda634 | 353 | |
| 7ac1d37 | 354 | #[test] |
| 3d6f280 | 355 | fn constructorPatternBindsFieldsToDeclaredTypes() { |
| 7ac1d37 | 356 | let src = "\ |
| 7ac1d37 | 357 | enum Shape = |
| 30ae945 | 358 | | Rect[Float, Float] |
| 30ae945 | 359 | | Circle[Float] |
| 7ac1d37 | 360 | |
| b48d3a3 | 361 | fun area(s: Shape) -> Float = |
| 7ac1d37 | 362 | match s |
| 7ac1d37 | 363 | Rect(w, h) => |
| 7ac1d37 | 364 | w * h |
| 7ac1d37 | 365 | Circle(r) => |
| 7ac1d37 | 366 | r * r |
| 7ac1d37 | 367 | "; |
| 7ac1d37 | 368 | let source = parse(src); |
| 3d6f280 | 369 | let result = checkSource(&source); |
| 7ac1d37 | 370 | assert!(result.is_ok(), "expected Ok, got {:?}", result.err()); |
| 7ac1d37 | 371 | } |
| 7ac1d37 | 372 | |
| 7ac1d37 | 373 | #[test] |
| 3d6f280 | 374 | fn constructorPatternWrongFieldCountIsError() { |
| 7ac1d37 | 375 | let src = "\ |
| 7ac1d37 | 376 | enum Shape = |
| 30ae945 | 377 | | Rect[Float, Float] |
| 30ae945 | 378 | | Circle[Float] |
| 7ac1d37 | 379 | |
| b48d3a3 | 380 | fun bad(s: Shape) -> Float = |
| 7ac1d37 | 381 | match s |
| 7ac1d37 | 382 | Rect(w) => |
| 7ac1d37 | 383 | w |
| 7ac1d37 | 384 | _ => |
| 7ac1d37 | 385 | 0.0 |
| 7ac1d37 | 386 | "; |
| 7ac1d37 | 387 | let source = parse(src); |
| 3d6f280 | 388 | let result = checkSource(&source); |
| 7ac1d37 | 389 | assert!(result.is_err()); |
| 7ac1d37 | 390 | } |
| 42d88a3 | 391 | |
| 42d88a3 | 392 | #[test] |
| 3d6f280 | 393 | fn classNameCollidingWithEnumVariantIsAClearError() { |
| 42d88a3 | 394 | // `Cat(...)` is ambiguous when `Cat` is both a class and an enum variant: |
| 42d88a3 | 395 | // downstream code consults `enum_variants` first, so the class |
| 42d88a3 | 396 | // constructor would otherwise be silently shadowed with no diagnostic. |
| 42d88a3 | 397 | let src = "\ |
| 42d88a3 | 398 | type Cat = |
| 42d88a3 | 399 | name: Str |
| 42d88a3 | 400 | |
| 42d88a3 | 401 | enum Animal = |
| 42d88a3 | 402 | | Cat |
| 42d88a3 | 403 | | Dog |
| 42d88a3 | 404 | "; |
| 42d88a3 | 405 | let source = parse(src); |
| 3d6f280 | 406 | let result = checkSource(&source); |
| 42d88a3 | 407 | assert!(result.is_err(), "expected Err"); |
| 42d88a3 | 408 | let errs = result.unwrap_err(); |
| 42d88a3 | 409 | assert!( |
| 42d88a3 | 410 | errs.iter().any(|e| e.message.contains("is declared as both a class and an enum variant")), |
| 42d88a3 | 411 | "got: {:?}", |
| 42d88a3 | 412 | errs |
| 42d88a3 | 413 | ); |
| 42d88a3 | 414 | } |
| 22140cf | 415 | |
| 22140cf | 416 | #[test] |
| 3d6f280 | 417 | fn genericClassInstantiatedAtTwoConcreteTypesTypeChecks() { |
| 22140cf | 418 | let src = "\ |
| 30ae945 | 419 | type Box[T] = |
| 30ae945 | 420 | value: T |
| 22140cf | 421 | |
| b48d3a3 | 422 | fun makeIntBox() -> Box = |
| 22140cf | 423 | Box(value: 5) |
| 22140cf | 424 | |
| b48d3a3 | 425 | fun makeStrBox() -> Box = |
| 22140cf | 426 | Box(value: \"x\") |
| 22140cf | 427 | "; |
| 22140cf | 428 | let source = parse(src); |
| 3d6f280 | 429 | let result = checkSource(&source); |
| 22140cf | 430 | assert!(result.is_ok(), "expected Ok, got {:?}", result.err()); |
| 22140cf | 431 | } |
| 22140cf | 432 | |
| 22140cf | 433 | #[test] |
| 3d6f280 | 434 | fn genericFunctionCalledWithDifferentConcreteTypesPerSiteTypeChecks() { |
| 22140cf | 435 | let src = "\ |
| b48d3a3 | 436 | fun wrap(value: T) -> Bool = |
| 22140cf | 437 | True |
| 22140cf | 438 | |
| b48d3a3 | 439 | fun useInt() -> Bool = |
| 22140cf | 440 | wrap(5) |
| 22140cf | 441 | |
| b48d3a3 | 442 | fun useStr() -> Bool = |
| 22140cf | 443 | wrap(\"x\") |
| 22140cf | 444 | "; |
| 22140cf | 445 | let source = parse(src); |
| 3d6f280 | 446 | let result = checkSource(&source); |
| 22140cf | 447 | assert!(result.is_ok(), "expected Ok, got {:?}", result.err()); |
| 22140cf | 448 | } |
| 22140cf | 449 | |
| 22140cf | 450 | #[test] |
| 3d6f280 | 451 | fn genericFunctionWithTwoIndependentTypeParamsTypeChecks() { |
| 22140cf | 452 | let src = "\ |
| b48d3a3 | 453 | fun pair(first: T, second: U) -> Bool = |
| 22140cf | 454 | True |
| 22140cf | 455 | |
| b48d3a3 | 456 | fun use() -> Bool = |
| 22140cf | 457 | pair(1, \"x\") |
| 22140cf | 458 | "; |
| 22140cf | 459 | let source = parse(src); |
| 3d6f280 | 460 | let result = checkSource(&source); |
| 22140cf | 461 | assert!(result.is_ok(), "expected Ok, got {:?}", result.err()); |
| 22140cf | 462 | } |
| 22140cf | 463 | |
| c273ea5 | 464 | #[test] |
| 3d6f280 | 465 | fn genericFunctionWithInternallyInconsistentBodyIsRejectedAfterSpecialization() { |
| c273ea5 | 466 | // A generic function whose body is genuinely inconsistent with its concrete, |
| c273ea5 | 467 | // correct declared return type must still be REJECTED after specialization. |
| c273ea5 | 468 | // The `-> Str` on `useIt` means the buggy (unconditional return-overwrite) |
| c273ea5 | 469 | // behavior — rewriting `wrong$Str`'s `-> Int` to `-> Str` — would make the |
| c273ea5 | 470 | // whole program type-check, masking the real `expected Int, found Str` error. |
| c273ea5 | 471 | let src = "\ |
| b48d3a3 | 472 | fun wrong(x: T) -> Int = |
| c273ea5 | 473 | \"hello\" |
| c273ea5 | 474 | |
| b48d3a3 | 475 | fun useIt() -> Str = |
| c273ea5 | 476 | wrong(\"s\") |
| c273ea5 | 477 | "; |
| c273ea5 | 478 | let source = parse(src); |
| 3d6f280 | 479 | let result = checkSource(&source); |
| c273ea5 | 480 | assert!( |
| c273ea5 | 481 | result.is_err(), |
| c273ea5 | 482 | "expected the internally-inconsistent generic function to be rejected, got Ok" |
| c273ea5 | 483 | ); |
| c273ea5 | 484 | } |
| c273ea5 | 485 | |
| c273ea5 | 486 | #[test] |
| 3d6f280 | 487 | fn genericEnumSingleInstantiationTypeChecks() { |
| c273ea5 | 488 | // A generic Option-shaped enum, constructed at one concrete type (`Some(5)`), |
| 3d6f280 | 489 | // matched, must resolve end-to-end via checkSource. |
| c273ea5 | 490 | let src = "\ |
| c273ea5 | 491 | enum Option = |
| 30ae945 | 492 | | Some[T] |
| c273ea5 | 493 | | None |
| c273ea5 | 494 | |
| b48d3a3 | 495 | fun get() -> Int = |
| c273ea5 | 496 | o = Some(5) |
| c273ea5 | 497 | match o |
| c273ea5 | 498 | Some(v) => |
| c273ea5 | 499 | v |
| c273ea5 | 500 | None => |
| c273ea5 | 501 | 0 |
| c273ea5 | 502 | "; |
| c273ea5 | 503 | let source = parse(src); |
| 3d6f280 | 504 | let result = checkSource(&source); |
| c273ea5 | 505 | assert!(result.is_ok(), "expected Ok, got {:?}", result.err()); |
| c273ea5 | 506 | |
| c273ea5 | 507 | // Directly prove resolution happened: the monomorphized output must contain a |
| 2216237 | 508 | // concrete `Option$Int` enum whose `Some$Int` variant carries an `Int` field |
| 2216237 | 509 | // (not the generic `a`), and must NOT retain the generic `Option` template. |
| 2216237 | 510 | // The variant name is ALSO mangled (`Some` -> `Some$Int`), the same suffix as |
| 2216237 | 511 | // the enum's own name. |
| 3d6f280 | 512 | let mono = plum_checker::monomorphize::monomorphizeSource(&source) |
| c273ea5 | 513 | .expect("monomorphize should succeed"); |
| c273ea5 | 514 | let opt = mono.items.iter().find_map(|it| match it { |
| c273ea5 | 515 | Item::Enum(e) if e.name == "Option$Int" => Some(e), |
| c273ea5 | 516 | _ => None, |
| c273ea5 | 517 | }); |
| c273ea5 | 518 | let opt = opt.expect("expected a specialized `Option$Int` enum in the output"); |
| 2216237 | 519 | let some = opt.variants.iter().find(|v| v.name == "Some$Int") |
| 2216237 | 520 | .expect("expected `Some$Int` (mangled) variant on `Option$Int`"); |
| c273ea5 | 521 | assert_eq!(some.fields, vec!["Int".to_string()], "Some's field should be concrete Int"); |
| c273ea5 | 522 | assert!( |
| c273ea5 | 523 | !mono.items.iter().any(|it| matches!(it, Item::Enum(e) if e.name == "Option")), |
| c273ea5 | 524 | "the generic `Option` template must be dropped from the output" |
| c273ea5 | 525 | ); |
| c273ea5 | 526 | } |
| c273ea5 | 527 | |
| c273ea5 | 528 | #[test] |
| 3d6f280 | 529 | fn genericEnumMultipleInstantiationsCoexistAndTypeCheck() { |
| c273ea5 | 530 | // The SAME generic enum instantiated at two different concrete types in one |
| 2216237 | 531 | // program must now type-check correctly for BOTH instantiations — this is the |
| 2216237 | 532 | // behavior this task adds (previously this was a documented, rejected limitation). |
| c273ea5 | 533 | let src = "\ |
| c273ea5 | 534 | enum Option = |
| 30ae945 | 535 | | Some[T] |
| c273ea5 | 536 | | None |
| c273ea5 | 537 | |
| b48d3a3 | 538 | fun useInt() -> Int = |
| c273ea5 | 539 | o = Some(5) |
| c273ea5 | 540 | match o |
| c273ea5 | 541 | Some(v) => |
| c273ea5 | 542 | v |
| c273ea5 | 543 | None => |
| c273ea5 | 544 | 0 |
| c273ea5 | 545 | |
| b48d3a3 | 546 | fun useStr() -> Str = |
| c273ea5 | 547 | o = Some(\"x\") |
| c273ea5 | 548 | match o |
| c273ea5 | 549 | Some(v) => |
| c273ea5 | 550 | v |
| c273ea5 | 551 | None => |
| c273ea5 | 552 | \"z\" |
| c273ea5 | 553 | "; |
| c273ea5 | 554 | let source = parse(src); |
| 3d6f280 | 555 | let result = checkSource(&source); |
| 2216237 | 556 | assert!(result.is_ok(), "expected Ok, got {:?}", result.err()); |
| 2216237 | 557 | |
| 2216237 | 558 | // Directly prove both specializations exist independently, with distinct |
| 2216237 | 559 | // mangled variant names, so neither collides with the other. |
| 3d6f280 | 560 | let mono = plum_checker::monomorphize::monomorphizeSource(&source) |
| 2216237 | 561 | .expect("monomorphize should succeed"); |
| 2216237 | 562 | let has_enum_with_variant = |enum_name: &str, variant_name: &str| { |
| 2216237 | 563 | mono.items.iter().any(|it| matches!(it, Item::Enum(e) if e.name == enum_name |
| 2216237 | 564 | && e.variants.iter().any(|v| v.name == variant_name))) |
| 2216237 | 565 | }; |
| 2216237 | 566 | assert!(has_enum_with_variant("Option$Int", "Some$Int"), "expected Option$Int with Some$Int"); |
| 2216237 | 567 | assert!(has_enum_with_variant("Option$Str", "Some$Str"), "expected Option$Str with Some$Str"); |
| c273ea5 | 568 | } |
| c273ea5 | 569 | |
| 22140cf | 570 | #[test] |
| 3d6f280 | 571 | fn genericMethodOnGenericClassTypeChecks() { |
| 22140cf | 572 | let src = "\ |
| 30ae945 | 573 | type Box[T] = |
| 30ae945 | 574 | value: T |
| 22140cf | 575 | |
| 7056de2 | 576 | fun getValue() -> T = |
| 7056de2 | 577 | self.value |
| 22140cf | 578 | |
| b48d3a3 | 579 | fun use() -> Int = |
| 22140cf | 580 | b = Box(value: 5) |
| 22140cf | 581 | b.getValue() |
| 22140cf | 582 | "; |
| 22140cf | 583 | let source = parse(src); |
| 3d6f280 | 584 | let result = checkSource(&source); |
| 22140cf | 585 | assert!(result.is_ok(), "expected Ok, got {:?}", result.err()); |
| 22140cf | 586 | } |
| 2e28ecc | 587 | |
| 2e28ecc | 588 | #[test] |
| 6613e6d | 589 | #[ignore = "slow (~100s) — exercises the 10,000-specialization runaway-recursion guard; run explicitly with --ignored when touching monomorphize.rs's guard logic"] |
| 3d6f280 | 590 | fn unboundedRecursiveGenericInstantiationIsAClearError() { |
| 2e28ecc | 591 | // `recurse` re-wraps its argument in a `Box` on every recursive call, so each |
| 2e28ecc | 592 | // specialization's own body demands a specialization of `recurse` at a STRICTLY |
| 2e28ecc | 593 | // bigger type (`recurse$Int`, then `recurse$Box$Int`, then `recurse$Box$Box$Int`, |
| 2e28ecc | 594 | // ...), forever. This genuinely grows the worklist without bound (unlike a |
| 2e28ecc | 595 | // generic class field merely NAMING a recursive generic type in its own |
| 2e28ecc | 596 | // declaration, which is never itself a call site and so never reaches the |
| 2e28ecc | 597 | // worklist at all) and must fail with a clear, bounded error rather than hang. |
| 2e28ecc | 598 | let src = "\ |
| 30ae945 | 599 | type Box[T] = |
| 30ae945 | 600 | value: T |
| 2e28ecc | 601 | |
| b48d3a3 | 602 | fun recurse(v: T) -> Int = |
| 2e28ecc | 603 | b = Box(value: v) |
| 2e28ecc | 604 | recurse(b) |
| 2e28ecc | 605 | |
| b48d3a3 | 606 | fun use() -> Int = |
| 2e28ecc | 607 | recurse(5) |
| 2e28ecc | 608 | "; |
| 2e28ecc | 609 | let source = parse(src); |
| 3d6f280 | 610 | let result = checkSource(&source); |
| 2e28ecc | 611 | assert!(result.is_err()); |
| 2e28ecc | 612 | let errs = result.unwrap_err(); |
| 2e28ecc | 613 | assert!(errs[0].message.contains("monomorphize"), "got: {:?}", errs); |
| 2e28ecc | 614 | } |
| 2216237 | 615 | |
| 2216237 | 616 | |
| 2216237 | 617 | |
| 2216237 | 618 | |
| 2216237 | 619 | #[test] |
| 3d6f280 | 620 | fn ordinaryFunctionWithBareGenericEnumParamTypeChecks() { |
| 2216237 | 621 | // The shape that broke the pre-existing codegen test: an otherwise-ordinary |
| 2216237 | 622 | // function taking a bare generic-enum-typed parameter. |
| 2216237 | 623 | let src = "\ |
| 2216237 | 624 | enum Option = |
| 30ae945 | 625 | | Some[T] |
| 2216237 | 626 | | None |
| 2216237 | 627 | |
| b48d3a3 | 628 | fun unwrapOr(o: Option, default: Int) -> Int = |
| 2216237 | 629 | match o |
| 2216237 | 630 | Some(v) => |
| 2216237 | 631 | v |
| 2216237 | 632 | None => |
| 2216237 | 633 | default |
| 2216237 | 634 | |
| b48d3a3 | 635 | fun use() -> Int = |
| 2216237 | 636 | unwrapOr(Some(5), 0) |
| 2216237 | 637 | "; |
| 2216237 | 638 | let source = parse(src); |
| 3d6f280 | 639 | let result = checkSource(&source); |
| 2216237 | 640 | assert!(result.is_ok(), "expected Ok, got {:?}", result.err()); |
| 2216237 | 641 | |
| 2216237 | 642 | // Directly prove `unwrapOr` itself got specialized (not left bare/unresolved). |
| 3d6f280 | 643 | let mono = plum_checker::monomorphize::monomorphizeSource(&source) |
| 2216237 | 644 | .expect("monomorphize should succeed"); |
| 2216237 | 645 | let has_specialized_unwrap_or = mono.items.iter().any(|it| matches!(it, Item::Fn(f) |
| 2216237 | 646 | if f.name.starts_with("unwrapOr$") && f.type_param.is_none())); |
| 2216237 | 647 | assert!(has_specialized_unwrap_or, "expected a specialized `unwrapOr$...` function in the output"); |
| 2216237 | 648 | } |
| 2216237 | 649 | |
| 2216237 | 650 | #[test] |
| 3d6f280 | 651 | fn ordinaryFunctionWithBareGenericClassParamTypeChecks() { |
| 2216237 | 652 | // The same shape, for a generic CLASS param instead of an enum — untested until |
| 2216237 | 653 | // now, but the identical root cause: `Box` is dropped from the monomorphized |
| 2216237 | 654 | // output, so a bare `Box`-typed param would otherwise reference nothing. |
| 2216237 | 655 | let src = "\ |
| 30ae945 | 656 | type Box[T] = |
| 30ae945 | 657 | value: T |
| 2216237 | 658 | |
| 7056de2 | 659 | fun getBoxValue() -> T = |
| 7056de2 | 660 | self.value |
| 2216237 | 661 | |
| b48d3a3 | 662 | fun sumBox(b: Box) -> Int = |
| 2216237 | 663 | b.getBoxValue() |
| 2216237 | 664 | |
| b48d3a3 | 665 | fun use() -> Int = |
| 2216237 | 666 | sumBox(Box(value: 5)) |
| 2216237 | 667 | "; |
| 2216237 | 668 | let source = parse(src); |
| 3d6f280 | 669 | let result = checkSource(&source); |
| 2216237 | 670 | assert!(result.is_ok(), "expected Ok, got {:?}", result.err()); |
| 2216237 | 671 | } |
| d1ea2ff | 672 | |
| d1ea2ff | 673 | #[test] |
| 3d6f280 | 674 | fn closureLiteralInfersAsAFunctionType() { |
| d1ea2ff | 675 | let src = "\ |
| b48d3a3 | 676 | fun useClosure() -> Bool = |
| d1ea2ff | 677 | cb = |v| |
| d1ea2ff | 678 | True |
| d1ea2ff | 679 | cb(5) |
| d1ea2ff | 680 | "; |
| d1ea2ff | 681 | let source = parse(src); |
| 3d6f280 | 682 | let result = checkSource(&source); |
| d1ea2ff | 683 | assert!(result.is_ok(), "expected Ok, got {:?}", result.err()); |
| d1ea2ff | 684 | } |
| d1ea2ff | 685 | |
| d1ea2ff | 686 | #[test] |
| 3d6f280 | 687 | fn fnValueTypedParamCanBeCalled() { |
| d1ea2ff | 688 | let src = "\ |
| b48d3a3 | 689 | fun each(cb: fn(Int) -> Bool) -> Bool = |
| d1ea2ff | 690 | cb(5) |
| d1ea2ff | 691 | "; |
| d1ea2ff | 692 | let source = parse(src); |
| 3d6f280 | 693 | let result = checkSource(&source); |
| d1ea2ff | 694 | assert!(result.is_ok(), "expected Ok, got {:?}", result.err()); |
| d1ea2ff | 695 | } |
| d1ea2ff | 696 | |
| 47abc49 | 697 | #[test] |
| 3d6f280 | 698 | fn fieldAssignmentTargetWithMatchingTypePasses() { |
| 47abc49 | 699 | let src = "\ |
| 47abc49 | 700 | type Cat = |
| 47abc49 | 701 | name: Str |
| 47abc49 | 702 | age: Int |
| 47abc49 | 703 | |
| 805d96d | 704 | fun haveBirthday() = |
| 805d96d | 705 | self.age = self.age + 1 |
| 47abc49 | 706 | "; |
| 47abc49 | 707 | let source = parse(src); |
| 3d6f280 | 708 | assert!(checkSource(&source).is_ok(), "expected Ok"); |
| 47abc49 | 709 | } |
| 47abc49 | 710 | |
| 47abc49 | 711 | #[test] |
| 3d6f280 | 712 | fn fieldAssignmentTargetWithMismatchedTypeIsError() { |
| 47abc49 | 713 | let src = "\ |
| 47abc49 | 714 | type Cat = |
| 47abc49 | 715 | name: Str |
| 47abc49 | 716 | age: Int |
| 47abc49 | 717 | |
| 7056de2 | 718 | fun breakCat() = |
| 7056de2 | 719 | self.age = \"oops\" |
| 47abc49 | 720 | "; |
| 47abc49 | 721 | let source = parse(src); |
| 3d6f280 | 722 | let result = checkSource(&source); |
| 47abc49 | 723 | assert!(result.is_err()); |
| 47abc49 | 724 | } |
| 47abc49 | 725 | |
| 47abc49 | 726 | #[test] |
| 3d6f280 | 727 | fn fieldAssignmentTargetUnknownFieldIsError() { |
| 47abc49 | 728 | let src = "\ |
| 47abc49 | 729 | type Cat = |
| 47abc49 | 730 | name: Str |
| 47abc49 | 731 | age: Int |
| 47abc49 | 732 | |
| 7056de2 | 733 | fun breakCat() = |
| 7056de2 | 734 | self.nope = 1 |
| 47abc49 | 735 | "; |
| 47abc49 | 736 | let source = parse(src); |
| 3d6f280 | 737 | let result = checkSource(&source); |
| 47abc49 | 738 | assert!(result.is_err()); |
| 47abc49 | 739 | } |
| 47abc49 | 740 | |
| d1ea2ff | 741 | #[test] |
| 3d6f280 | 742 | fn closurePassedToFnValueTypedParamTypeChecks() { |
| d1ea2ff | 743 | let src = "\ |
| b48d3a3 | 744 | fun each(cb: fn(Int) -> Bool) -> Bool = |
| d1ea2ff | 745 | cb(5) |
| d1ea2ff | 746 | |
| b48d3a3 | 747 | fun use() -> Bool = |
| d1ea2ff | 748 | each(|v| |
| d1ea2ff | 749 | True) |
| d1ea2ff | 750 | "; |
| d1ea2ff | 751 | let source = parse(src); |
| 3d6f280 | 752 | let result = checkSource(&source); |
| d1ea2ff | 753 | assert!(result.is_ok(), "expected Ok, got {:?}", result.err()); |
| d1ea2ff | 754 | } |
| 4a494a8 | 755 | |
| 4a494a8 | 756 | #[test] |
| 3d6f280 | 757 | fn variadicCallWithZeroTrailingArgsPasses() { |
| 4a494a8 | 758 | let src = "\ |
| b48d3a3 | 759 | fun sumAll(nums: ...Int) -> Int = |
| 4a494a8 | 760 | 0 |
| 4a494a8 | 761 | |
| b48d3a3 | 762 | fun useSumAll() -> Int = |
| 4a494a8 | 763 | sumAll() |
| 4a494a8 | 764 | "; |
| 4a494a8 | 765 | let source = parse(src); |
| 3d6f280 | 766 | assert!(checkSource(&source).is_ok(), "expected Ok"); |
| 4a494a8 | 767 | } |
| 4a494a8 | 768 | |
| 4a494a8 | 769 | #[test] |
| 3d6f280 | 770 | fn variadicCallWithSeveralTrailingArgsPasses() { |
| 4a494a8 | 771 | let src = "\ |
| b48d3a3 | 772 | fun sumAll(nums: ...Int) -> Int = |
| 4a494a8 | 773 | 0 |
| 4a494a8 | 774 | |
| b48d3a3 | 775 | fun useSumAll() -> Int = |
| 4a494a8 | 776 | sumAll(1, 2, 3) |
| 4a494a8 | 777 | "; |
| 4a494a8 | 778 | let source = parse(src); |
| 3d6f280 | 779 | assert!(checkSource(&source).is_ok(), "expected Ok"); |
| 4a494a8 | 780 | } |
| 4a494a8 | 781 | |
| 4a494a8 | 782 | #[test] |
| 3d6f280 | 783 | fn variadicCallWithMismatchedTrailingArgTypeIsError() { |
| 4a494a8 | 784 | let src = "\ |
| b48d3a3 | 785 | fun sumAll(nums: ...Int) -> Int = |
| 4a494a8 | 786 | 0 |
| 4a494a8 | 787 | |
| b48d3a3 | 788 | fun useSumAll() -> Int = |
| 4a494a8 | 789 | sumAll(1, \"two\") |
| 4a494a8 | 790 | "; |
| 4a494a8 | 791 | let source = parse(src); |
| 3d6f280 | 792 | assert!(checkSource(&source).is_err()); |
| 4a494a8 | 793 | } |
| 4a494a8 | 794 | |
| 4a494a8 | 795 | #[test] |
| 3d6f280 | 796 | fn variadicCallWithFixedPrefixPasses() { |
| 4a494a8 | 797 | let src = "\ |
| b48d3a3 | 798 | fun combine(prefix: Int, rest: ...Int) -> Int = |
| 4a494a8 | 799 | prefix |
| 4a494a8 | 800 | |
| b48d3a3 | 801 | fun useCombine() -> Int = |
| 4a494a8 | 802 | combine(1, 2, 3) |
| 4a494a8 | 803 | "; |
| 4a494a8 | 804 | let source = parse(src); |
| 3d6f280 | 805 | assert!(checkSource(&source).is_ok(), "expected Ok"); |
| 4a494a8 | 806 | } |
| 4a494a8 | 807 | |
| 4a494a8 | 808 | #[test] |
| 3d6f280 | 809 | fn twoVariadicParamsIsError() { |
| 4a494a8 | 810 | let src = "\ |
| b48d3a3 | 811 | fun bad(a: ...Int, b: ...Int) -> Int = |
| 4a494a8 | 812 | 0 |
| 4a494a8 | 813 | "; |
| 4a494a8 | 814 | let source = parse(src); |
| 3d6f280 | 815 | assert!(checkSource(&source).is_err()); |
| 4a494a8 | 816 | } |
| 4a494a8 | 817 | |
| 4a494a8 | 818 | #[test] |
| 3d6f280 | 819 | fn variadicParamNotLastIsError() { |
| 4a494a8 | 820 | let src = "\ |
| b48d3a3 | 821 | fun bad(a: ...Int, b: Int) -> Int = |
| 4a494a8 | 822 | 0 |
| 4a494a8 | 823 | "; |
| 4a494a8 | 824 | let source = parse(src); |
| 3d6f280 | 825 | assert!(checkSource(&source).is_err()); |
| 4a494a8 | 826 | } |
| 4a494a8 | 827 | |
| 4a494a8 | 828 | #[test] |
| 3d6f280 | 829 | fn forLoopOverVariadicBindsElementType() { |
| 4a494a8 | 830 | let src = "\ |
| b48d3a3 | 831 | fun sumAll(nums: ...Int) -> Int = |
| 4a494a8 | 832 | total = 0 |
| 0000000 | 833 | for v := range nums |
| 4a494a8 | 834 | total = total + v |
| 4a494a8 | 835 | total |
| 4a494a8 | 836 | "; |
| 4a494a8 | 837 | let source = parse(src); |
| 3d6f280 | 838 | assert!(checkSource(&source).is_ok(), "expected Ok"); |
| 4a494a8 | 839 | } |
| 4a494a8 | 840 | |
| 4a494a8 | 841 | #[test] |
| 3d6f280 | 842 | fn forLoopOverVariadicWithTwoVarsIsError() { |
| 4a494a8 | 843 | let src = "\ |
| b48d3a3 | 844 | fun bad(nums: ...Int) -> Int = |
| 0000000 | 845 | for v, i := range nums |
| 4a494a8 | 846 | v |
| 4a494a8 | 847 | 0 |
| 4a494a8 | 848 | "; |
| 4a494a8 | 849 | let source = parse(src); |
| 3d6f280 | 850 | assert!(checkSource(&source).is_err()); |
| 4a494a8 | 851 | } |