|
15 | 15 | import re |
16 | 16 | import sys |
17 | 17 | import unittest |
| 18 | +import warnings |
18 | 19 |
|
19 | 20 | test_tools.skip_if_missing('clinic') |
20 | 21 | with test_tools.imports_under_tool('clinic'): |
@@ -2305,6 +2306,128 @@ def test_depr_slash_duplicate2(self): |
2305 | 2306 | err = "Function 'bar': '/ [from 3.14]' must precede '/ [from 3.15]'" |
2306 | 2307 | self.expect_failure(block, err, lineno=5) |
2307 | 2308 |
|
| 2309 | + def test_alias(self): |
| 2310 | + function = self.parse_function(""" |
| 2311 | + module foo |
| 2312 | + foo.bar |
| 2313 | + a: int |
| 2314 | + * |
| 2315 | + b as a: int = 0 |
| 2316 | + Docstring. |
| 2317 | + """) |
| 2318 | + _, a, b = function.parameters.values() |
| 2319 | + self.assertIsNone(a.converter.alias_of) |
| 2320 | + self.assertIs(b.converter.alias_of, a) |
| 2321 | + self.assertEqual(function.docstring.splitlines()[0], |
| 2322 | + "bar($module, /, a)") |
| 2323 | + |
| 2324 | + def test_alias_must_be_keyword_only(self): |
| 2325 | + block = """ |
| 2326 | + module foo |
| 2327 | + foo.bar |
| 2328 | + a: int |
| 2329 | + b as a: int = 0 |
| 2330 | + Docstring. |
| 2331 | + """ |
| 2332 | + err = "Alias 'b' of the parameter 'a' must be keyword-only." |
| 2333 | + self.expect_failure(block, err, lineno=3) |
| 2334 | + |
| 2335 | + def test_alias_must_have_default(self): |
| 2336 | + block = """ |
| 2337 | + module foo |
| 2338 | + foo.bar |
| 2339 | + a: int |
| 2340 | + * |
| 2341 | + b as a: int |
| 2342 | + Docstring. |
| 2343 | + """ |
| 2344 | + err = "Alias 'b' of the parameter 'a' must have a default value." |
| 2345 | + self.expect_failure(block, err, lineno=4) |
| 2346 | + |
| 2347 | + def test_alias_deprecated(self): |
| 2348 | + function = self.parse_function(""" |
| 2349 | + module foo |
| 2350 | + foo.bar |
| 2351 | + a: int |
| 2352 | + * |
| 2353 | + [until 3.14] b as a: int = 0 |
| 2354 | + Docstring. |
| 2355 | + """) |
| 2356 | + _, a, b = function.parameters.values() |
| 2357 | + self.assertIsNone(a.deprecated_until) |
| 2358 | + self.assertEqual(b.deprecated_until, (3, 14)) |
| 2359 | + |
| 2360 | + def test_deprecated_last_positional_only_parameters(self): |
| 2361 | + function = self.parse_function(""" |
| 2362 | + module foo |
| 2363 | + foo.bar |
| 2364 | + a: int = 0 |
| 2365 | + [until 3.14] b: int = 0 |
| 2366 | + [until 3.14] c: int = 0 |
| 2367 | + / |
| 2368 | + d: int = 0 |
| 2369 | + Docstring. |
| 2370 | + """) |
| 2371 | + _, a, b, c, d = function.parameters.values() |
| 2372 | + self.assertIsNone(a.deprecated_until) |
| 2373 | + self.assertEqual(b.deprecated_until, (3, 14)) |
| 2374 | + self.assertEqual(c.deprecated_until, (3, 14)) |
| 2375 | + self.assertIsNone(d.deprecated_until) |
| 2376 | + |
| 2377 | + def test_deprecated_non_last_positional_only_parameter(self): |
| 2378 | + block = """ |
| 2379 | + module foo |
| 2380 | + foo.bar |
| 2381 | + [until 3.14] a: int = 0 |
| 2382 | + b: int = 0 |
| 2383 | + / |
| 2384 | + Docstring. |
| 2385 | + """ |
| 2386 | + err = ("Parameter 'b' cannot follow the deprecated parameter 'a': " |
| 2387 | + "only the last positional-only parameters can be deprecated.") |
| 2388 | + self.expect_failure(block, err, lineno=4) |
| 2389 | + |
| 2390 | + def test_deprecated_non_positional_only_parameters(self): |
| 2391 | + # The following parameters can still be passed by keyword. |
| 2392 | + function = self.parse_function(""" |
| 2393 | + module foo |
| 2394 | + foo.bar |
| 2395 | + [until 3.14] a: int = 0 |
| 2396 | + b: int = 0 |
| 2397 | + * |
| 2398 | + [until 3.14] c: int = 0 |
| 2399 | + d: int = 0 |
| 2400 | + Docstring. |
| 2401 | + """) |
| 2402 | + _, a, b, c, d = function.parameters.values() |
| 2403 | + self.assertEqual(a.deprecated_until, (3, 14)) |
| 2404 | + self.assertIsNone(b.deprecated_until) |
| 2405 | + self.assertEqual(c.deprecated_until, (3, 14)) |
| 2406 | + self.assertIsNone(d.deprecated_until) |
| 2407 | + |
| 2408 | + def test_deprecated_parameter_without_default(self): |
| 2409 | + block = """ |
| 2410 | + module foo |
| 2411 | + foo.bar |
| 2412 | + [until 3.14] a: int |
| 2413 | + Docstring. |
| 2414 | + """ |
| 2415 | + err = "Deprecated parameter 'a' must have a default value." |
| 2416 | + self.expect_failure(block, err, lineno=2) |
| 2417 | + |
| 2418 | + def test_deprecated_invalid_format(self): |
| 2419 | + block = """ |
| 2420 | + module foo |
| 2421 | + foo.bar |
| 2422 | + [until 3] a: int = 0 |
| 2423 | + Docstring. |
| 2424 | + """ |
| 2425 | + err = ( |
| 2426 | + "Function 'bar': expected format '[until major.minor]' " |
| 2427 | + "where 'major' and 'minor' are integers; got '3'" |
| 2428 | + ) |
| 2429 | + self.expect_failure(block, err, lineno=2) |
| 2430 | + |
2308 | 2431 | def test_single_slash(self): |
2309 | 2432 | block = """ |
2310 | 2433 | module foo |
@@ -5072,6 +5195,58 @@ def test_depr_multi(self): |
5072 | 5195 | check("a", b="b", c="c", d="d", e="e", f="f", g="g") |
5073 | 5196 | self.assertRaises(TypeError, fn, a="a", b="b", c="c", d="d", e="e", f="f", g="g") |
5074 | 5197 |
|
| 5198 | + def test_alias_pos(self): |
| 5199 | + fn = ac_tester.alias_pos |
| 5200 | + self.assertIsNone(fn()) |
| 5201 | + self.assertEqual(fn(1), 1) |
| 5202 | + self.assertEqual(fn(a=1), 1) |
| 5203 | + self.assertEqual(fn(b=1), 1) |
| 5204 | + self.assertEqual(fn.__text_signature__, "($module, /, a=None)") |
| 5205 | + errmsg = re.escape( |
| 5206 | + "argument for alias_pos() given by name ('b') and position (1)") |
| 5207 | + self.assertRaisesRegex(TypeError, errmsg, fn, 1, b=2) |
| 5208 | + errmsg = re.escape( |
| 5209 | + "argument for alias_pos() given by name ('b') and name ('a')") |
| 5210 | + self.assertRaisesRegex(TypeError, errmsg, fn, a=1, b=2) |
| 5211 | + |
| 5212 | + def test_alias_kwonly(self): |
| 5213 | + fn = ac_tester.alias_kwonly |
| 5214 | + self.assertIsNone(fn()) |
| 5215 | + self.assertEqual(fn(a=1), 1) |
| 5216 | + self.assertEqual(fn(b=1), 1) |
| 5217 | + self.assertEqual(fn.__text_signature__, "($module, /, *, a=None)") |
| 5218 | + self.assertRaises(TypeError, fn, 1) |
| 5219 | + errmsg = re.escape( |
| 5220 | + "argument for alias_kwonly() given by name ('b') and name ('a')") |
| 5221 | + self.assertRaisesRegex(TypeError, errmsg, fn, a=1, b=2) |
| 5222 | + |
| 5223 | + def test_depr_alias(self): |
| 5224 | + fn = ac_tester.depr_alias |
| 5225 | + self.assertEqual(fn(1), 1) |
| 5226 | + self.assertEqual(fn(a=1), 1) |
| 5227 | + errmsg = ("Passing the argument 'b' to depr_alias() is deprecated. " |
| 5228 | + "Use 'a' instead. It will be removed in Python 3.14.") |
| 5229 | + self.check_depr(re.escape(errmsg), fn, b=1) |
| 5230 | + |
| 5231 | + def test_depr_param(self): |
| 5232 | + fn = ac_tester.depr_param |
| 5233 | + self.assertEqual(fn(), (None, None, None, None)) |
| 5234 | + self.assertEqual(fn(1), (1, None, None, None)) |
| 5235 | + def errmsg(name): |
| 5236 | + return re.escape(f"Passing the argument {name!r} to depr_param() " |
| 5237 | + f"is deprecated. " |
| 5238 | + f"It will be removed in Python 3.14.") |
| 5239 | + self.check_depr(errmsg('b'), fn, 1, 2) |
| 5240 | + self.check_depr(errmsg('d'), fn, 1, d=4) |
| 5241 | + # Each deprecated parameter is reported on its own. |
| 5242 | + with warnings.catch_warnings(record=True) as caught: |
| 5243 | + warnings.simplefilter("always") |
| 5244 | + self.assertEqual(fn(1, 2, 3), (1, 2, 3, None)) |
| 5245 | + self.assertEqual(len(caught), 2) |
| 5246 | + for warning, name in zip(caught, 'bc'): |
| 5247 | + self.assertIs(warning.category, DeprecationWarning) |
| 5248 | + self.assertRegex(str(warning.message), errmsg(name)) |
| 5249 | + |
5075 | 5250 | def test_lone_kwds(self): |
5076 | 5251 | with self.assertRaises(TypeError): |
5077 | 5252 | ac_tester.lone_kwds(1, 2) |
@@ -5266,6 +5441,26 @@ def test_limited_capi_double(self): |
5266 | 5441 | self.assertIn("double f;", generated) |
5267 | 5442 | self.assertIn("f = PyFloat_AsDouble", generated) |
5268 | 5443 |
|
| 5444 | + def test_limited_capi_alias(self): |
| 5445 | + block = self.wrap_clinic_input(""" |
| 5446 | + func |
| 5447 | + a: object = None |
| 5448 | + * |
| 5449 | + b as a: object = None |
| 5450 | + """) |
| 5451 | + err = ("Parameter 'b' cannot be an alias: " |
| 5452 | + "the arguments are not parsed one by one.") |
| 5453 | + _expect_failure(self, self.clinic.parse, block, err) |
| 5454 | + |
| 5455 | + def test_limited_capi_deprecated(self): |
| 5456 | + block = self.wrap_clinic_input(""" |
| 5457 | + func |
| 5458 | + [until 3.14] a: object = None |
| 5459 | + """) |
| 5460 | + err = ("Parameter 'a' cannot be deprecated: " |
| 5461 | + "the arguments are not parsed one by one.") |
| 5462 | + _expect_failure(self, self.clinic.parse, block, err) |
| 5463 | + |
5269 | 5464 |
|
5270 | 5465 | try: |
5271 | 5466 | import _testclinic_limited |
|
0 commit comments